diff --git a/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp b/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp index 2bad6273f7..a6681e390b 100644 --- a/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp +++ b/src/dawn/tests/white_box/D3D12GPUTimestampCalibrationTests.cpp @@ -22,23 +22,54 @@ namespace dawn::native::d3d12 { namespace { + +using FeatureName = wgpu::FeatureName; +enum class EncoderType { NonPass, ComputePass, RenderPass }; + +std::ostream& operator<<(std::ostream& o, EncoderType type) { + switch (type) { + case EncoderType::NonPass: + o << "NonPass"; + break; + case EncoderType::ComputePass: + o << "ComputePass"; + break; + case EncoderType::RenderPass: + o << "RenderPass"; + break; + default: + UNREACHABLE(); + break; + } + + return o; +} + +DAWN_TEST_PARAM_STRUCT(GPUTimestampCalibrationTestParams, FeatureName, EncoderType); + class ExpectBetweenTimestamps : public ::detail::Expectation { public: ~ExpectBetweenTimestamps() override = default; - ExpectBetweenTimestamps(uint64_t value0, uint64_t value1) { - mValue0 = value0; - mValue1 = value1; - } + ExpectBetweenTimestamps(uint64_t minValue, uint64_t maxValue, float errorToleranceRatio = 0.0f) + : mMinValue(minValue), mMaxValue(maxValue), mErrorToleranceRatio(errorToleranceRatio) {} - // Expect the actual results are between mValue0 and mValue1. + // Expect the actual results are between mMinValue and mMaxValue with error tolerance ratio. testing::AssertionResult Check(const void* data, size_t size) override { const uint64_t* actual = static_cast(data); + + if (mErrorToleranceRatio != 0.0f) { + mMinValue -= + static_cast(static_cast(mMinValue * mErrorToleranceRatio)); + mMaxValue += + static_cast(static_cast(mMaxValue * mErrorToleranceRatio)); + } + for (size_t i = 0; i < size / sizeof(uint64_t); ++i) { - if (actual[i] < mValue0 || actual[i] > mValue1) { + if (actual[i] < mMinValue || actual[i] > mMaxValue) { return testing::AssertionFailure() - << "Expected data[" << i << "] to be between " << mValue0 << " and " - << mValue1 << ", actual " << actual[i] << std::endl; + << "Expected data[" << i << "] to be between " << mMinValue << " and " + << mMaxValue << ", actual " << actual[i] << std::endl; } } @@ -46,75 +77,186 @@ class ExpectBetweenTimestamps : public ::detail::Expectation { } private: - uint64_t mValue0; - uint64_t mValue1; + uint64_t mMinValue; + uint64_t mMaxValue; + float mErrorToleranceRatio; }; } // anonymous namespace -class D3D12GPUTimestampCalibrationTests : public DawnTest { +class D3D12GPUTimestampCalibrationTests + : public DawnTestWithParams { protected: void SetUp() override { - DawnTest::SetUp(); + DawnTestWithParams::SetUp(); DAWN_TEST_UNSUPPORTED_IF(UsesWire()); // Requires that timestamp query feature is enabled and timestamp query conversion is // disabled. - DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::TimestampQuery}) || - !HasToggleEnabled("disable_timestamp_query_conversion")); + DAWN_TEST_UNSUPPORTED_IF(!mIsFeatureSupported); + // The "timestamp-query-inside-passes" feature is not supported on command encoder. + DAWN_TEST_UNSUPPORTED_IF(GetParam().mFeatureName == + wgpu::FeatureName::TimestampQueryInsidePasses && + GetParam().mEncoderType == EncoderType::NonPass); } std::vector GetRequiredFeatures() override { std::vector requiredFeatures = {}; - if (SupportsFeatures({wgpu::FeatureName::TimestampQuery})) { - requiredFeatures.push_back(wgpu::FeatureName::TimestampQuery); + if (SupportsFeatures({GetParam().mFeatureName})) { + requiredFeatures.push_back(GetParam().mFeatureName); + mIsFeatureSupported = true; } return requiredFeatures; } + + void EncodeTimestampQueryOnComputePass(const wgpu::CommandEncoder& encoder, + const wgpu::QuerySet& querySet) { + switch (GetParam().mFeatureName) { + case wgpu::FeatureName::TimestampQuery: { + std::vector timestampWrites; + timestampWrites.push_back( + {querySet, 0, wgpu::ComputePassTimestampLocation::Beginning}); + timestampWrites.push_back({querySet, 1, wgpu::ComputePassTimestampLocation::End}); + + wgpu::ComputePassDescriptor descriptor; + descriptor.timestampWriteCount = timestampWrites.size(); + descriptor.timestampWrites = timestampWrites.data(); + + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(&descriptor); + pass.End(); + break; + } + case wgpu::FeatureName::TimestampQueryInsidePasses: { + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.WriteTimestamp(querySet, 0); + pass.WriteTimestamp(querySet, 1); + pass.End(); + break; + } + default: + break; + } + } + + void EncodeTimestampQueryOnRenderPass(const wgpu::CommandEncoder& encoder, + const wgpu::QuerySet& querySet) { + constexpr static unsigned int kRTSize = 4; + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); + + switch (GetParam().mFeatureName) { + case wgpu::FeatureName::TimestampQuery: { + std::vector timestampWrites; + timestampWrites.push_back( + {querySet, 0, wgpu::RenderPassTimestampLocation::Beginning}); + timestampWrites.push_back({querySet, 1, wgpu::RenderPassTimestampLocation::End}); + + renderPass.renderPassInfo.timestampWriteCount = timestampWrites.size(); + renderPass.renderPassInfo.timestampWrites = timestampWrites.data(); + + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.End(); + break; + } + case wgpu::FeatureName::TimestampQueryInsidePasses: { + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.WriteTimestamp(querySet, 0); + pass.WriteTimestamp(querySet, 1); + pass.End(); + break; + } + default: + break; + } + } + + void RunTest() { + constexpr uint32_t kQueryCount = 2; + + // Create query set + wgpu::QuerySetDescriptor querySetDescriptor; + querySetDescriptor.count = kQueryCount; + querySetDescriptor.type = wgpu::QueryType::Timestamp; + wgpu::QuerySet querySet = device.CreateQuerySet(&querySetDescriptor); + + // Create resolve buffer + wgpu::BufferDescriptor bufferDescriptor; + bufferDescriptor.size = kQueryCount * sizeof(uint64_t); + bufferDescriptor.usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc | + wgpu::BufferUsage::CopyDst; + wgpu::Buffer destination = device.CreateBuffer(&bufferDescriptor); + + // Encode timestamp query + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + switch (GetParam().mEncoderType) { + case EncoderType::NonPass: { + // The "timestamp-query-inside-passes" feature is not supported on command encoder + ASSERT(GetParam().mFeatureName != wgpu::FeatureName::TimestampQueryInsidePasses); + encoder.WriteTimestamp(querySet, 0); + encoder.WriteTimestamp(querySet, 1); + break; + } + case EncoderType::ComputePass: { + EncodeTimestampQueryOnComputePass(encoder, querySet); + break; + } + case EncoderType::RenderPass: { + EncodeTimestampQueryOnRenderPass(encoder, querySet); + break; + } + } + wgpu::CommandBuffer commands = encoder.Finish(); + + // Start calibration between GPU timestamp and CPU timestamp + Device* d3DDevice = reinterpret_cast(device.Get()); + uint64_t gpuTimestamp0, gpuTimestamp1; + uint64_t cpuTimestamp0, cpuTimestamp1; + d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp0, &cpuTimestamp0); + queue.Submit(1, &commands); + WaitForAllOperations(); + d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp1, &cpuTimestamp1); + + // Separate resolve queryset to reduce the execution time of the queue with WriteTimestamp, + // so that the timestamp in the querySet will be closer to both gpuTimestamps from + // GetClockCalibration. + wgpu::CommandEncoder resolveEncoder = device.CreateCommandEncoder(); + resolveEncoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); + wgpu::CommandBuffer resolveCommands = resolveEncoder.Finish(); + queue.Submit(1, &resolveCommands); + + float errorToleranceRatio = 0.0f; + if (!HasToggleEnabled("disable_timestamp_query_conversion")) { + uint64_t gpuFrequency; + d3DDevice->GetCommandQueue()->GetTimestampFrequency(&gpuFrequency); + float period = static_cast(1e9) / gpuFrequency; + gpuTimestamp0 = static_cast(static_cast(gpuTimestamp0 * period)); + gpuTimestamp1 = static_cast(static_cast(gpuTimestamp1 * period)); + + // We have 15 bits of precision in the timestamp query conversion so we + // expect that for the error tolerance. + errorToleranceRatio = 1.0 / (1 << 15); // about 3e-5. + } + + EXPECT_BUFFER( + destination, 0, kQueryCount * sizeof(uint64_t), + new ExpectBetweenTimestamps(gpuTimestamp0, gpuTimestamp1, errorToleranceRatio)); + } + + private: + bool mIsFeatureSupported = false; }; // Check that the timestamps got by timestamp query are between the two timestamps from -// GetClockCalibration() after the timestamp conversion is disabled. -TEST_P(D3D12GPUTimestampCalibrationTests, TimestampsInOrder) { - constexpr uint32_t kQueryCount = 2; - - wgpu::QuerySetDescriptor querySetDescriptor; - querySetDescriptor.count = kQueryCount; - querySetDescriptor.type = wgpu::QueryType::Timestamp; - wgpu::QuerySet querySet = device.CreateQuerySet(&querySetDescriptor); - - wgpu::BufferDescriptor bufferDescriptor; - bufferDescriptor.size = kQueryCount * sizeof(uint64_t); - bufferDescriptor.usage = - wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; - wgpu::Buffer destination = device.CreateBuffer(&bufferDescriptor); - - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - encoder.WriteTimestamp(querySet, 0); - encoder.WriteTimestamp(querySet, 1); - wgpu::CommandBuffer commands = encoder.Finish(); - - Device* d3DDevice = reinterpret_cast(device.Get()); - uint64_t gpuTimestamp0, gpuTimestamp1; - uint64_t cpuTimestamp0, cpuTimestamp1; - d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp0, &cpuTimestamp0); - queue.Submit(1, &commands); - WaitForAllOperations(); - d3DDevice->GetCommandQueue()->GetClockCalibration(&gpuTimestamp1, &cpuTimestamp1); - - // Separate resolve queryset to reduce the execution time of the queue with WriteTimestamp, - // so that the timestamp in the querySet will be closer to both gpuTimestamps from - // GetClockCalibration. - wgpu::CommandEncoder resolveEncoder = device.CreateCommandEncoder(); - resolveEncoder.ResolveQuerySet(querySet, 0, kQueryCount, destination, 0); - wgpu::CommandBuffer resolveCommands = resolveEncoder.Finish(); - queue.Submit(1, &resolveCommands); - - EXPECT_BUFFER(destination, 0, kQueryCount * sizeof(uint64_t), - new ExpectBetweenTimestamps(gpuTimestamp0, gpuTimestamp1)); +// GetClockCalibration() with the 'disable_timestamp_query_conversion' toggle disabled or enabled. +TEST_P(D3D12GPUTimestampCalibrationTests, TimestampsCalibration) { + RunTest(); } -DAWN_INSTANTIATE_TEST(D3D12GPUTimestampCalibrationTests, - D3D12Backend({"disable_timestamp_query_conversion"})); +DAWN_INSTANTIATE_TEST_P( + D3D12GPUTimestampCalibrationTests, + // Test with the disable_timestamp_query_conversion toggle forced on and off. + {D3D12Backend({"disable_timestamp_query_conversion"}, {}), + D3D12Backend({}, {"disable_timestamp_query_conversion"})}, + {wgpu::FeatureName::TimestampQuery, wgpu::FeatureName::TimestampQueryInsidePasses}, + {EncoderType::NonPass, EncoderType::ComputePass, EncoderType::RenderPass}); } // namespace dawn::native::d3d12