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:
Hao Li 2020-08-04 06:41:56 +00:00 committed by Commit Bot service account
parent 1ea3a22f52
commit c0acb25318
6 changed files with 237 additions and 43 deletions

View File

@ -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;
}

View File

@ -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: {

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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());