diff --git a/dawn.json b/dawn.json index 2e3d4b52a8..3005dfa49e 100644 --- a/dawn.json +++ b/dawn.json @@ -290,7 +290,8 @@ {"value": 32, "name": "vertex"}, {"value": 64, "name": "uniform"}, {"value": 128, "name": "storage"}, - {"value": 256, "name": "indirect"} + {"value": 256, "name": "indirect"}, + {"value": 512, "name": "query resolve"} ] }, "char": { @@ -411,6 +412,16 @@ {"name": "group label", "type": "char", "annotation": "const*", "length": "strlen"} ] }, + { + "name": "resolve query set", + "args": [ + {"name": "query set", "type": "query set"}, + {"name": "first query", "type": "uint32_t"}, + {"name": "query count", "type": "uint32_t"}, + {"name": "destination", "type": "buffer"}, + {"name": "destination offset", "type": "uint64_t"} + ] + }, { "name": "write timestamp", "args": [ diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index f228cb32df..da99bbd650 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -435,6 +435,44 @@ namespace dawn_native { return fixedView; } + MaybeError ValidateQuerySetResolve(const QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + const BufferBase* destination, + uint64_t destinationOffset) { + if (firstQuery >= querySet->GetQueryCount()) { + return DAWN_VALIDATION_ERROR("Query index out of bounds"); + } + + if (queryCount > querySet->GetQueryCount() - firstQuery) { + return DAWN_VALIDATION_ERROR( + "The sum of firstQuery and queryCount exceeds the number of queries in query " + "set"); + } + + // TODO(hao.x.li@intel.com): Validate that the queries between [firstQuery, firstQuery + + // queryCount - 1] must be available(written by query operations). + + // The destinationOffset must be a multiple of 8 bytes on D3D12 and Vulkan + if (destinationOffset % 8 != 0) { + return DAWN_VALIDATION_ERROR( + "The alignment offset into the destination buffer must be a multiple of 8 " + "bytes"); + } + + uint64_t bufferSize = destination->GetSize(); + // The destination buffer must have enough storage, from destination offset, to contain + // the result of resolved queries + bool fitsInBuffer = destinationOffset <= bufferSize && + (static_cast(queryCount) * sizeof(uint64_t) <= + (bufferSize - destinationOffset)); + if (!fitsInBuffer) { + return DAWN_VALIDATION_ERROR("The resolved query data would overflow the buffer"); + } + + return {}; + } + } // namespace CommandEncoder::CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor*) @@ -783,6 +821,37 @@ namespace dawn_native { }); } + void CommandEncoder::ResolveQuerySet(QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + BufferBase* destination, + uint64_t destinationOffset) { + mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { + if (GetDevice()->IsValidationEnabled()) { + DAWN_TRY(GetDevice()->ValidateObject(querySet)); + DAWN_TRY(GetDevice()->ValidateObject(destination)); + + DAWN_TRY(ValidateQuerySetResolve(querySet, firstQuery, queryCount, destination, + destinationOffset)); + + DAWN_TRY(ValidateCanUseAs(destination, wgpu::BufferUsage::QueryResolve)); + + TrackUsedQuerySet(querySet); + mTopLevelBuffers.insert(destination); + } + + ResolveQuerySetCmd* cmd = + allocator->Allocate(Command::ResolveQuerySet); + cmd->querySet = querySet; + cmd->firstQuery = firstQuery; + cmd->queryCount = queryCount; + cmd->destination = destination; + cmd->destinationOffset = destinationOffset; + + return {}; + }); + } + void CommandEncoder::WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { if (GetDevice()->IsValidationEnabled()) { @@ -884,6 +953,11 @@ namespace dawn_native { break; } + case Command::ResolveQuerySet: { + commands->NextCommand(); + break; + } + case Command::WriteTimestamp: { commands->NextCommand(); break; diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h index 9caa1b230c..2fe801042c 100644 --- a/src/dawn_native/CommandEncoder.h +++ b/src/dawn_native/CommandEncoder.h @@ -60,6 +60,11 @@ namespace dawn_native { void PopDebugGroup(); void PushDebugGroup(const char* groupLabel); + void ResolveQuerySet(QuerySetBase* querySet, + uint32_t firstQuery, + uint32_t queryCount, + BufferBase* destination, + uint64_t destinationOffset); void WriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex); CommandBufferBase* Finish(const CommandBufferDescriptor* descriptor); diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index 7da04adc1d..9528e82ed8 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -128,6 +128,11 @@ namespace dawn_native { cmd->~PushDebugGroupCmd(); break; } + case Command::ResolveQuerySet: { + ResolveQuerySetCmd* cmd = commands->NextCommand(); + cmd->~ResolveQuerySetCmd(); + break; + } case Command::SetComputePipeline: { SetComputePipelineCmd* cmd = commands->NextCommand(); cmd->~SetComputePipelineCmd(); @@ -266,6 +271,11 @@ namespace dawn_native { break; } + case Command::ResolveQuerySet: { + commands->NextCommand(); + break; + } + case Command::SetComputePipeline: commands->NextCommand(); break; diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 70214ff204..85e462bc6a 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -51,6 +51,7 @@ namespace dawn_native { InsertDebugMarker, PopDebugGroup, PushDebugGroup, + ResolveQuerySet, SetComputePipeline, SetRenderPipeline, SetStencilReference, @@ -186,6 +187,14 @@ namespace dawn_native { uint32_t length; }; + struct ResolveQuerySetCmd { + Ref querySet; + uint32_t firstQuery; + uint32_t queryCount; + Ref destination; + uint64_t destinationOffset; + }; + struct SetComputePipelineCmd { Ref pipeline; }; diff --git a/src/dawn_native/d3d12/CommandBufferD3D12.cpp b/src/dawn_native/d3d12/CommandBufferD3D12.cpp index 834b57a231..1c301d21ed 100644 --- a/src/dawn_native/d3d12/CommandBufferD3D12.cpp +++ b/src/dawn_native/d3d12/CommandBufferD3D12.cpp @@ -836,6 +836,10 @@ namespace dawn_native { namespace d3d12 { break; } + case Command::ResolveQuerySet: { + return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); + } + case Command::WriteTimestamp: { return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); } diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm index 97c0ad1b5d..0e4d439b31 100644 --- a/src/dawn_native/metal/CommandBufferMTL.mm +++ b/src/dawn_native/metal/CommandBufferMTL.mm @@ -716,6 +716,10 @@ namespace dawn_native { namespace metal { break; } + case Command::ResolveQuerySet: { + return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); + } + case Command::WriteTimestamp: { return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); } diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp index 3689277efa..b06dac47a7 100644 --- a/src/dawn_native/opengl/CommandBufferGL.cpp +++ b/src/dawn_native/opengl/CommandBufferGL.cpp @@ -716,6 +716,12 @@ namespace dawn_native { namespace opengl { break; } + case Command::ResolveQuerySet: { + // TODO(hao.x.li@intel.com): Resolve non-precise occlusion query. + SkipCommand(&mCommands, type); + break; + } + case Command::WriteTimestamp: { // WriteTimestamp is not supported on OpenGL UNREACHABLE(); diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index 1159a3e45b..f7fb0a3cd8 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -599,6 +599,10 @@ namespace dawn_native { namespace vulkan { break; } + case Command::ResolveQuerySet: { + return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); + } + case Command::WriteTimestamp: { return DAWN_UNIMPLEMENTED_ERROR("Waiting for implementation."); } diff --git a/src/tests/unittests/validation/QuerySetValidationTests.cpp b/src/tests/unittests/validation/QuerySetValidationTests.cpp index 185c047879..5bf7babdd3 100644 --- a/src/tests/unittests/validation/QuerySetValidationTests.cpp +++ b/src/tests/unittests/validation/QuerySetValidationTests.cpp @@ -314,3 +314,149 @@ TEST_F(TimestampQueryValidationTest, WriteTimestampOnRenderPassEncoder) { ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); } } + +class ResolveQuerySetValidationTest : public QuerySetValidationTest { + protected: + wgpu::Buffer CreateBuffer(wgpu::Device cDevice, uint64_t size, wgpu::BufferUsage usage) { + wgpu::BufferDescriptor descriptor; + descriptor.size = size; + descriptor.usage = usage; + + return cDevice.CreateBuffer(&descriptor); + } +}; + +// Test resolve query set with invalid query set, first query and query count +TEST_F(ResolveQuerySetValidationTest, ResolveInvalidQuerySetAndIndexCount) { + constexpr uint32_t kQueryCount = 4; + + wgpu::QuerySet querySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, kQueryCount); + wgpu::Buffer destination = + CreateBuffer(device, kQueryCount * sizeof(uint64_t), wgpu::BufferUsage::QueryResolve); + + // Success + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.GetDefaultQueue(); + queue.Submit(1, &commands); + } + + // Fail to resolve query set from another device + { + wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set if first query out of range + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, kQueryCount, 0, destination, 0); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set if the sum of first query and query count is larger than queries + // number in the query set + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 1, kQueryCount, destination, 0); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve a destroyed query set + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.GetDefaultQueue(); + querySet.Destroy(); + ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); + } +} + +// Test resolve query set with invalid query set, first query and query count +TEST_F(ResolveQuerySetValidationTest, ResolveToInvalidBufferAndOffset) { + constexpr uint32_t kQueryCount = 4; + constexpr uint64_t kBufferSize = kQueryCount * sizeof(uint64_t); + + wgpu::QuerySet querySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, kQueryCount); + wgpu::Buffer destination = CreateBuffer(device, kBufferSize, wgpu::BufferUsage::QueryResolve); + + // Success + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 1, kQueryCount - 1, destination, 8); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.GetDefaultQueue(); + queue.Submit(1, &commands); + } + + // Fail to resolve query set to a buffer created from another device + { + wgpu::Buffer bufferOnTimestamp = + CreateBuffer(deviceWithTimestamp, kBufferSize, wgpu::BufferUsage::QueryResolve); + wgpu::CommandEncoder encoder = deviceWithTimestamp.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, bufferOnTimestamp, 0); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set to a buffer if offset is not a multiple of 8 bytes + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 4); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set to a buffer if the data size overflow the buffer + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 8); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set to a buffer if the offset is past the end of the buffer + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, 1, destination, kBufferSize); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set to a buffer does not have the usage of QueryResolve + { + wgpu::Buffer dstBuffer = CreateBuffer(device, kBufferSize, wgpu::BufferUsage::CopyDst); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, dstBuffer, 0); + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + + // Fail to resolve query set to a destroyed buffer. + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.GetDefaultQueue(); + destination.Destroy(); + ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); + } +} + +// Check that in 32bit mode the computation of queryCount * sizeof(uint64_t) doesn't overflow (which +// would skip validation). +TEST_F(ResolveQuerySetValidationTest, BufferOverflowOn32Bits) { + // If compiling for 32-bits mode, the data size calculated by queryCount * sizeof(uint64_t) + // is 8, which is less than the buffer size. + constexpr uint32_t kQueryCount = std::numeric_limits::max() / sizeof(uint64_t) + 2; + + wgpu::QuerySet querySet = CreateQuerySet(device, wgpu::QueryType::Occlusion, kQueryCount); + wgpu::Buffer destination = CreateBuffer(device, 1024, wgpu::BufferUsage::QueryResolve); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + encoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + + ASSERT_DEVICE_ERROR(encoder.Finish()); +}