Query API: Timestamp Query on D3D12
- Add implementation of WriteTimestamp and ResolveQuerySet on D3D12, but not add compute shader to post-process the result yet. - Add end2end tests for timestamp query on command encoder/render pass/compute pass. Bug: dawn:434 Change-Id: I7f763bc46d651818da3f69bc72ea2e403cf2674d Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/25845 Commit-Queue: Hao Li <hao.x.li@intel.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
This commit is contained in:
parent
1ea3a22f52
commit
c0acb25318
|
@ -63,6 +63,13 @@ namespace dawn_native { namespace d3d12 {
|
|||
if (usage & wgpu::BufferUsage::Indirect) {
|
||||
resourceState |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
|
||||
}
|
||||
if (usage & wgpu::BufferUsage::QueryResolve) {
|
||||
// D3D12_RESOURCE_STATE_COPY_DEST is required by ResolveQueryData but we also add
|
||||
// D3D12_RESOURCE_STATE_UNORDERED_ACCESS because the queries will be post-processed
|
||||
// by a compute shader and written to this buffer via a UAV.
|
||||
resourceState |=
|
||||
(D3D12_RESOURCE_STATE_UNORDERED_ACCESS | D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
}
|
||||
|
||||
return resourceState;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "dawn_native/d3d12/DeviceD3D12.h"
|
||||
#include "dawn_native/d3d12/PipelineLayoutD3D12.h"
|
||||
#include "dawn_native/d3d12/PlatformFunctions.h"
|
||||
#include "dawn_native/d3d12/QuerySetD3D12.h"
|
||||
#include "dawn_native/d3d12/RenderPassBuilderD3D12.h"
|
||||
#include "dawn_native/d3d12/RenderPipelineD3D12.h"
|
||||
#include "dawn_native/d3d12/SamplerD3D12.h"
|
||||
|
@ -55,6 +56,19 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
}
|
||||
|
||||
D3D12_QUERY_TYPE D3D12QueryType(wgpu::QueryType type) {
|
||||
switch (type) {
|
||||
case wgpu::QueryType::Occlusion:
|
||||
return D3D12_QUERY_TYPE_OCCLUSION;
|
||||
case wgpu::QueryType::PipelineStatistics:
|
||||
return D3D12_QUERY_TYPE_PIPELINE_STATISTICS;
|
||||
case wgpu::QueryType::Timestamp:
|
||||
return D3D12_QUERY_TYPE_TIMESTAMP;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
bool CanUseCopyResource(const Texture* src, const Texture* dst, const Extent3D& copySize) {
|
||||
// Checked by validation
|
||||
ASSERT(src->GetSampleCount() == dst->GetSampleCount());
|
||||
|
@ -141,6 +155,14 @@ namespace dawn_native { namespace d3d12 {
|
|||
&textureLocation, &sourceRegion);
|
||||
}
|
||||
}
|
||||
|
||||
void RecordWriteTimestampCmd(ID3D12GraphicsCommandList* commandList,
|
||||
WriteTimestampCmd* cmd) {
|
||||
QuerySet* querySet = ToBackend(cmd->querySet.Get());
|
||||
ASSERT(D3D12QueryType(querySet->GetQueryType()) == D3D12_QUERY_TYPE_TIMESTAMP);
|
||||
commandList->EndQuery(querySet->GetQueryHeap(), D3D12_QUERY_TYPE_TIMESTAMP,
|
||||
cmd->queryIndex);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
class BindGroupStateTracker : public BindGroupAndStorageBarrierTrackerBase<false, uint64_t> {
|
||||
|
@ -846,11 +868,26 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
|
||||
case Command::ResolveQuerySet: {
|
||||
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
|
||||
ResolveQuerySetCmd* cmd = mCommands.NextCommand<ResolveQuerySetCmd>();
|
||||
QuerySet* querySet = ToBackend(cmd->querySet.Get());
|
||||
Buffer* destination = ToBackend(cmd->destination.Get());
|
||||
|
||||
commandList->ResolveQueryData(
|
||||
querySet->GetQueryHeap(), D3D12QueryType(querySet->GetQueryType()),
|
||||
cmd->firstQuery, cmd->queryCount, destination->GetD3D12Resource(),
|
||||
cmd->destinationOffset);
|
||||
|
||||
// TODO(hao.x.li@intel.com): Add compute shader to convert the query result
|
||||
// (ticks) to timestamp (ns)
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Command::WriteTimestamp: {
|
||||
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
|
||||
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();
|
||||
|
||||
RecordWriteTimestampCmd(commandList, cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@ -964,7 +1001,10 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
|
||||
case Command::WriteTimestamp: {
|
||||
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
|
||||
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();
|
||||
|
||||
RecordWriteTimestampCmd(commandList, cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@ -1384,7 +1424,10 @@ namespace dawn_native { namespace d3d12 {
|
|||
}
|
||||
|
||||
case Command::WriteTimestamp: {
|
||||
return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation.");
|
||||
WriteTimestampCmd* cmd = mCommands.NextCommand<WriteTimestampCmd>();
|
||||
|
||||
RecordWriteTimestampCmd(commandList, cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
|
|
|
@ -19,6 +19,21 @@
|
|||
|
||||
namespace dawn_native { namespace d3d12 {
|
||||
|
||||
namespace {
|
||||
D3D12_QUERY_HEAP_TYPE D3D12QueryHeapType(wgpu::QueryType type) {
|
||||
switch (type) {
|
||||
case wgpu::QueryType::Occlusion:
|
||||
return D3D12_QUERY_HEAP_TYPE_OCCLUSION;
|
||||
case wgpu::QueryType::PipelineStatistics:
|
||||
return D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS;
|
||||
case wgpu::QueryType::Timestamp:
|
||||
return D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
ResultOrError<QuerySet*> QuerySet::Create(Device* device,
|
||||
const QuerySetDescriptor* descriptor) {
|
||||
|
@ -29,20 +44,7 @@ namespace dawn_native { namespace d3d12 {
|
|||
|
||||
MaybeError QuerySet::Initialize() {
|
||||
D3D12_QUERY_HEAP_DESC queryHeapDesc = {};
|
||||
switch (GetQueryType()) {
|
||||
case wgpu::QueryType::Occlusion:
|
||||
queryHeapDesc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION;
|
||||
break;
|
||||
case wgpu::QueryType::PipelineStatistics:
|
||||
queryHeapDesc.Type = D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS;
|
||||
break;
|
||||
case wgpu::QueryType::Timestamp:
|
||||
queryHeapDesc.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
queryHeapDesc.Type = D3D12QueryHeapType(GetQueryType());
|
||||
queryHeapDesc.Count = GetQueryCount();
|
||||
|
||||
ID3D12Device* d3d12Device = ToBackend(GetDevice())->GetD3D12Device();
|
||||
|
|
|
@ -1157,6 +1157,7 @@ namespace detail {
|
|||
template class ExpectEq<uint8_t>;
|
||||
template class ExpectEq<uint16_t>;
|
||||
template class ExpectEq<uint32_t>;
|
||||
template class ExpectEq<uint64_t>;
|
||||
template class ExpectEq<RGBA8>;
|
||||
template class ExpectEq<float>;
|
||||
} // namespace detail
|
||||
|
|
|
@ -30,29 +30,34 @@
|
|||
// until the end of the test. Also expectations use a copy to a MapRead buffer to get the data
|
||||
// so resources should have the CopySrc allowed usage bit if you want to add expectations on
|
||||
// them.
|
||||
#define EXPECT_BUFFER_U16_EQ(expected, buffer, offset) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint16_t), \
|
||||
new ::detail::ExpectEq<uint16_t>(expected))
|
||||
|
||||
#define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint16_t) * (count), \
|
||||
new ::detail::ExpectEq<uint16_t>(expected, count))
|
||||
#define EXPECT_BUFFER(buffer, offset, size, expectation) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, size, expectation)
|
||||
|
||||
#define EXPECT_BUFFER_U32_EQ(expected, buffer, offset) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t), \
|
||||
new ::detail::ExpectEq<uint32_t>(expected))
|
||||
#define EXPECT_BUFFER_U16_EQ(expected, buffer, offset) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(uint16_t), new ::detail::ExpectEq<uint16_t>(expected))
|
||||
|
||||
#define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * (count), \
|
||||
new ::detail::ExpectEq<uint32_t>(expected, count))
|
||||
#define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(uint16_t) * (count), \
|
||||
new ::detail::ExpectEq<uint16_t>(expected, count))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t), \
|
||||
new ::detail::ExpectEq<float>(expected))
|
||||
#define EXPECT_BUFFER_U32_EQ(expected, buffer, offset) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(uint32_t), new ::detail::ExpectEq<uint32_t>(expected))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \
|
||||
AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * (count), \
|
||||
new ::detail::ExpectEq<float>(expected, count))
|
||||
#define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(uint32_t) * (count), \
|
||||
new ::detail::ExpectEq<uint32_t>(expected, count))
|
||||
|
||||
#define EXPECT_BUFFER_U64_RANGE_EQ(expected, buffer, offset, count) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(uint64_t) * (count), \
|
||||
new ::detail::ExpectEq<uint64_t>(expected, count))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(float), new ::detail::ExpectEq<float>(expected))
|
||||
|
||||
#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \
|
||||
EXPECT_BUFFER(buffer, offset, sizeof(float) * (count), \
|
||||
new ::detail::ExpectEq<float>(expected, count))
|
||||
|
||||
// Test a pixel of the mip level 0 of a 2D texture.
|
||||
#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \
|
||||
|
@ -436,6 +441,7 @@ namespace detail {
|
|||
extern template class ExpectEq<uint8_t>;
|
||||
extern template class ExpectEq<int16_t>;
|
||||
extern template class ExpectEq<uint32_t>;
|
||||
extern template class ExpectEq<uint64_t>;
|
||||
extern template class ExpectEq<RGBA8>;
|
||||
extern template class ExpectEq<float>;
|
||||
} // namespace detail
|
||||
|
|
|
@ -19,7 +19,26 @@
|
|||
|
||||
#include "tests/DawnTest.h"
|
||||
|
||||
class OcclusionQueryTests : public DawnTest {};
|
||||
#include "utils/WGPUHelpers.h"
|
||||
|
||||
class QueryTests : public DawnTest {
|
||||
protected:
|
||||
wgpu::Buffer CreateResolveBuffer(uint64_t size) {
|
||||
wgpu::BufferDescriptor descriptor;
|
||||
descriptor.size = size;
|
||||
descriptor.usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc |
|
||||
wgpu::BufferUsage::CopyDst;
|
||||
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
|
||||
|
||||
// Initialize the buffer values to 0.
|
||||
std::vector<uint64_t> myData = {0, 0};
|
||||
device.GetDefaultQueue().WriteBuffer(buffer, 0, myData.data(), size);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
|
||||
class OcclusionQueryTests : public QueryTests {};
|
||||
|
||||
// Test creating query set with the type of Occlusion
|
||||
TEST_P(OcclusionQueryTests, QuerySetCreation) {
|
||||
|
@ -40,7 +59,7 @@ TEST_P(OcclusionQueryTests, QuerySetDestroy) {
|
|||
|
||||
DAWN_INSTANTIATE_TEST(OcclusionQueryTests, D3D12Backend());
|
||||
|
||||
class PipelineStatisticsQueryTests : public DawnTest {
|
||||
class PipelineStatisticsQueryTests : public QueryTests {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
DawnTest::SetUp();
|
||||
|
@ -74,7 +93,26 @@ TEST_P(PipelineStatisticsQueryTests, QuerySetCreation) {
|
|||
|
||||
DAWN_INSTANTIATE_TEST(PipelineStatisticsQueryTests, D3D12Backend());
|
||||
|
||||
class TimestampQueryTests : public DawnTest {
|
||||
class TimestampExpectation : public detail::Expectation {
|
||||
public:
|
||||
~TimestampExpectation() override = default;
|
||||
|
||||
// Expect the timestamp results are greater than 0.
|
||||
testing::AssertionResult Check(const void* data, size_t size) override {
|
||||
ASSERT(size % sizeof(uint64_t) == 0);
|
||||
const uint64_t* timestamps = static_cast<const uint64_t*>(data);
|
||||
for (size_t i = 0; i < size / sizeof(uint64_t); i++) {
|
||||
if (timestamps[i] == 0) {
|
||||
return testing::AssertionFailure()
|
||||
<< "Expected data[" << i << "] to be greater than 0." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return testing::AssertionSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
class TimestampQueryTests : public QueryTests {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
DawnTest::SetUp();
|
||||
|
@ -90,14 +128,111 @@ class TimestampQueryTests : public DawnTest {
|
|||
}
|
||||
return requiredExtensions;
|
||||
}
|
||||
|
||||
wgpu::QuerySet CreateQuerySetForTimestamp(uint32_t queryCount) {
|
||||
wgpu::QuerySetDescriptor descriptor;
|
||||
descriptor.count = queryCount;
|
||||
descriptor.type = wgpu::QueryType::Timestamp;
|
||||
return device.CreateQuerySet(&descriptor);
|
||||
}
|
||||
};
|
||||
|
||||
// Test creating query set with the type of Timestamp
|
||||
TEST_P(TimestampQueryTests, QuerySetCreation) {
|
||||
wgpu::QuerySetDescriptor descriptor;
|
||||
descriptor.count = 1;
|
||||
descriptor.type = wgpu::QueryType::Timestamp;
|
||||
device.CreateQuerySet(&descriptor);
|
||||
CreateQuerySetForTimestamp(1);
|
||||
}
|
||||
|
||||
// Test calling timestamp query from command encoder
|
||||
TEST_P(TimestampQueryTests, TimestampOnCommandEncoder) {
|
||||
constexpr uint32_t kQueryCount = 2;
|
||||
|
||||
wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
|
||||
wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
|
||||
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.WriteTimestamp(querySet, 0);
|
||||
encoder.WriteTimestamp(querySet, 1);
|
||||
encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
|
||||
wgpu::CommandBuffer commands = encoder.Finish();
|
||||
queue.Submit(1, &commands);
|
||||
|
||||
EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
|
||||
}
|
||||
|
||||
// Test calling timestamp query from render pass encoder
|
||||
TEST_P(TimestampQueryTests, TimestampOnRenderPass) {
|
||||
constexpr uint32_t kQueryCount = 2;
|
||||
|
||||
wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
|
||||
wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
|
||||
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
|
||||
pass.WriteTimestamp(querySet, 0);
|
||||
pass.WriteTimestamp(querySet, 1);
|
||||
pass.EndPass();
|
||||
encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
|
||||
wgpu::CommandBuffer commands = encoder.Finish();
|
||||
queue.Submit(1, &commands);
|
||||
|
||||
EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
|
||||
}
|
||||
|
||||
// Test calling timestamp query from compute pass encoder
|
||||
TEST_P(TimestampQueryTests, TimestampOnComputePass) {
|
||||
constexpr uint32_t kQueryCount = 2;
|
||||
|
||||
wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
|
||||
wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
|
||||
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
pass.WriteTimestamp(querySet, 0);
|
||||
pass.WriteTimestamp(querySet, 1);
|
||||
pass.EndPass();
|
||||
encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0);
|
||||
wgpu::CommandBuffer commands = encoder.Finish();
|
||||
queue.Submit(1, &commands);
|
||||
|
||||
EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), new TimestampExpectation);
|
||||
}
|
||||
|
||||
// Test resolving timestamp query to one slot in the buffer
|
||||
TEST_P(TimestampQueryTests, ResolveToBufferWithOffset) {
|
||||
constexpr uint32_t kQueryCount = 2;
|
||||
constexpr uint64_t kZero = 0;
|
||||
|
||||
wgpu::QuerySet querySet = CreateQuerySetForTimestamp(kQueryCount);
|
||||
|
||||
// Resolve the query result to first slot in the buffer, other slots should not be written
|
||||
{
|
||||
wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.WriteTimestamp(querySet, 0);
|
||||
encoder.WriteTimestamp(querySet, 1);
|
||||
encoder.ResolveQuerySet(querySet, 0, 1, destination, 0);
|
||||
wgpu::CommandBuffer commands = encoder.Finish();
|
||||
queue.Submit(1, &commands);
|
||||
|
||||
EXPECT_BUFFER(destination, 0, sizeof(uint64_t), new TimestampExpectation);
|
||||
EXPECT_BUFFER_U64_RANGE_EQ(&kZero, destination, sizeof(uint64_t), 1);
|
||||
}
|
||||
|
||||
// Resolve the query result to the buffer with offset, the slots before the offset
|
||||
// should not be written
|
||||
{
|
||||
wgpu::Buffer destination = CreateResolveBuffer(kQueryCount * sizeof(uint64_t));
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.WriteTimestamp(querySet, 0);
|
||||
encoder.WriteTimestamp(querySet, 1);
|
||||
encoder.ResolveQuerySet(querySet, 0, 1, destination, sizeof(uint64_t));
|
||||
wgpu::CommandBuffer commands = encoder.Finish();
|
||||
queue.Submit(1, &commands);
|
||||
|
||||
EXPECT_BUFFER_U64_RANGE_EQ(&kZero, destination, 0, 1);
|
||||
EXPECT_BUFFER(destination, sizeof(uint64_t), sizeof(uint64_t), new TimestampExpectation);
|
||||
}
|
||||
}
|
||||
|
||||
DAWN_INSTANTIATE_TEST(TimestampQueryTests, D3D12Backend());
|
||||
|
|
Loading…
Reference in New Issue