From c0acb25318013bb10ee4fd6b3961316e76cf310a Mon Sep 17 00:00:00 2001 From: Hao Li Date: Tue, 4 Aug 2020 06:41:56 +0000 Subject: [PATCH] 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 Reviewed-by: Corentin Wallez --- src/dawn_native/d3d12/BufferD3D12.cpp | 7 + src/dawn_native/d3d12/CommandBufferD3D12.cpp | 51 ++++++- src/dawn_native/d3d12/QuerySetD3D12.cpp | 30 ++-- src/tests/DawnTest.cpp | 1 + src/tests/DawnTest.h | 42 +++--- src/tests/end2end/QueryTests.cpp | 149 ++++++++++++++++++- 6 files changed, 237 insertions(+), 43 deletions(-) diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp index 877069b29f..712c896952 100644 --- a/src/dawn_native/d3d12/BufferD3D12.cpp +++ b/src/dawn_native/d3d12/BufferD3D12.cpp @@ -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; } diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 151ab6edfc..a1baf89104 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -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 { @@ -846,11 +868,26 @@ namespace dawn_native { namespace d3d12 { } case Command::ResolveQuerySet: { - return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); + ResolveQuerySetCmd* cmd = mCommands.NextCommand(); + 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(); + + 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(); + + 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(); + + RecordWriteTimestampCmd(commandList, cmd); + break; } default: { diff --git a/src/dawn_native/d3d12/QuerySetD3D12.cpp b/src/dawn_native/d3d12/QuerySetD3D12.cpp index e4a6a4191c..2bea526316 100644 --- a/src/dawn_native/d3d12/QuerySetD3D12.cpp +++ b/src/dawn_native/d3d12/QuerySetD3D12.cpp @@ -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::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(); diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp index 613a4dd615..8b515424e8 100644 --- a/src/tests/DawnTest.cpp +++ b/src/tests/DawnTest.cpp @@ -1157,6 +1157,7 @@ namespace detail { template class ExpectEq; template class ExpectEq; template class ExpectEq; + template class ExpectEq; template class ExpectEq; template class ExpectEq; } // namespace detail diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h index 096abcf69d..40f0fe2c42 100644 --- a/src/tests/DawnTest.h +++ b/src/tests/DawnTest.h @@ -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(expected)) -#define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \ - AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint16_t) * (count), \ - new ::detail::ExpectEq(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(expected)) +#define EXPECT_BUFFER_U16_EQ(expected, buffer, offset) \ + EXPECT_BUFFER(buffer, offset, sizeof(uint16_t), new ::detail::ExpectEq(expected)) -#define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \ - AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * (count), \ - new ::detail::ExpectEq(expected, count)) +#define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \ + EXPECT_BUFFER(buffer, offset, sizeof(uint16_t) * (count), \ + new ::detail::ExpectEq(expected, count)) -#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \ - AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t), \ - new ::detail::ExpectEq(expected)) +#define EXPECT_BUFFER_U32_EQ(expected, buffer, offset) \ + EXPECT_BUFFER(buffer, offset, sizeof(uint32_t), new ::detail::ExpectEq(expected)) -#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \ - AddBufferExpectation(__FILE__, __LINE__, buffer, offset, sizeof(uint32_t) * (count), \ - new ::detail::ExpectEq(expected, count)) +#define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \ + EXPECT_BUFFER(buffer, offset, sizeof(uint32_t) * (count), \ + new ::detail::ExpectEq(expected, count)) + +#define EXPECT_BUFFER_U64_RANGE_EQ(expected, buffer, offset, count) \ + EXPECT_BUFFER(buffer, offset, sizeof(uint64_t) * (count), \ + new ::detail::ExpectEq(expected, count)) + +#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \ + EXPECT_BUFFER(buffer, offset, sizeof(float), new ::detail::ExpectEq(expected)) + +#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \ + EXPECT_BUFFER(buffer, offset, sizeof(float) * (count), \ + new ::detail::ExpectEq(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; extern template class ExpectEq; extern template class ExpectEq; + extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; } // namespace detail diff --git a/src/tests/end2end/QueryTests.cpp b/src/tests/end2end/QueryTests.cpp index fc244f21c3..5436021842 100644 --- a/src/tests/end2end/QueryTests.cpp +++ b/src/tests/end2end/QueryTests.cpp @@ -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 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(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());