// Copyright 2020 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "tests/DawnTest.h" #include "dawn_native/Buffer.h" #include "dawn_native/CommandEncoder.h" #include "dawn_native/QueryHelper.h" #include "utils/WGPUHelpers.h" namespace { void EncodeConvertTimestampsToNanoseconds(wgpu::CommandEncoder encoder, wgpu::Buffer timestamps, wgpu::Buffer availability, wgpu::Buffer params) { ASSERT_TRUE(dawn_native::EncodeConvertTimestampsToNanoseconds( reinterpret_cast(encoder.Get()), reinterpret_cast(timestamps.Get()), reinterpret_cast(availability.Get()), reinterpret_cast(params.Get())).IsSuccess()); } class InternalShaderExpectation : public detail::Expectation { public: ~InternalShaderExpectation() override = default; InternalShaderExpectation(const uint64_t* values, const unsigned int count) { mExpected.assign(values, values + count); } // Expect the actual results are approximately equal to the expected values. testing::AssertionResult Check(const void* data, size_t size) override { DAWN_ASSERT(size == sizeof(uint64_t) * mExpected.size()); constexpr static float kErrorToleranceRatio = 0.002f; const uint64_t* actual = static_cast(data); for (size_t i = 0; i < mExpected.size(); ++i) { if (mExpected[i] == 0 && actual[i] != 0) { return testing::AssertionFailure() << "Expected data[" << i << "] to be 0, actual " << actual[i] << std::endl; } if (abs(static_cast(mExpected[i] - actual[i])) > mExpected[i] * kErrorToleranceRatio) { return testing::AssertionFailure() << "Expected data[" << i << "] to be " << mExpected[i] << ", actual " << actual[i] << ". Error rate is larger than " << kErrorToleranceRatio << std::endl; } } return testing::AssertionSuccess(); } private: std::vector mExpected; }; } // anonymous namespace class QueryInternalShaderTests : public DawnTest {}; // Test the accuracy of timestamp compute shader which uses unsigned 32-bit integers to simulate // unsigned 64-bit integers (timestamps) multiplied by float (period). // The arguments pass to timestamp internal pipeline: // - The timestamps buffer contains the original timestamps resolved from query set (created // manually here), and will be used to store the results processed by the compute shader. // Expect 0 for unavailable timestamps and nanoseconds for available timestamps in an expected // error tolerance ratio. // - The availability buffer passes the data of which slot in timestamps buffer is an initialized //  timestamp. // - The params buffer passes the timestamp count, the offset in timestamps buffer and the // timestamp period (here use GPU frequency (HZ) on Intel D3D12 to calculate the period in // ns for testing). TEST_P(QueryInternalShaderTests, TimestampComputeShader) { // TODO(crbug.com/dawn/741): Test output is wrong with D3D12 + WARP. DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP()); DAWN_TEST_UNSUPPORTED_IF(UsesWire()); constexpr uint32_t kTimestampCount = 10u; // A gpu frequency on Intel D3D12 (ticks/second) constexpr uint64_t kGPUFrequency = 12000048u; constexpr uint64_t kNsPerSecond = 1000000000u; // Timestamp period in nanoseconds constexpr float kPeriod = static_cast(kNsPerSecond) / kGPUFrequency; // Original timestamp values for testing std::vector timestamps = { 1, // garbage data which is not written at beginning 10079569507, // t0 10394415012, // t1 1, // garbage data which is not written between timestamps 11713454943, // t2 38912556941, // t3 (big value) 10080295766, // t4 (reset) 12159966783, // t5 (after reset) 12651224612, // t6 39872473956, // t7 }; // The buffer indicating which values are available timestamps std::vector availabilities = {0, 1, 1, 0, 1, 1, 1, 1, 1, 1}; wgpu::Buffer availabilityBuffer = utils::CreateBufferFromData(device, availabilities.data(), kTimestampCount * sizeof(uint32_t), wgpu::BufferUsage::Storage); // The resolve buffer storing original timestamps and the converted values wgpu::BufferDescriptor timestampsDesc; timestampsDesc.size = kTimestampCount * sizeof(uint64_t); timestampsDesc.usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; wgpu::Buffer timestampsBuffer = device.CreateBuffer(×tampsDesc); auto PrepareExpectedResults = [&](uint32_t first, uint32_t count, uint32_t offset) -> std::vector { ASSERT(offset % sizeof(uint64_t) == 0); std::vector expected; for (size_t i = 0; i < kTimestampCount; i++) { // The data out of the rang [first, first + count] remains as it is if (i < first || i >= first + count) { expected.push_back(timestamps[i]); continue; } if (availabilities[i] == 0) { // Not a available timestamp, write 0 expected.push_back(0u); } else { // Maybe the timestamp * period is larger than the maximum of uint64, so cast the // delta value to double (higher precision than float) expected.push_back( static_cast(static_cast(timestamps[i]) * kPeriod)); } } return expected; }; // Convert timestamps in timestamps buffer with offset 0 // Test for ResolveQuerySet(querySet, 0, kTimestampCount, timestampsBuffer, 0) { constexpr uint32_t kFirst = 0u; constexpr uint32_t kOffset = 0u; // Write orignal timestamps to timestamps buffer queue.WriteBuffer(timestampsBuffer, 0, timestamps.data(), kTimestampCount * sizeof(uint64_t)); // The params uniform buffer dawn_native::TimestampParams params = {kFirst, kTimestampCount, kOffset, kPeriod}; wgpu::Buffer paramsBuffer = utils::CreateBufferFromData(device, ¶ms, sizeof(params), wgpu::BufferUsage::Uniform); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); EncodeConvertTimestampsToNanoseconds(encoder, timestampsBuffer, availabilityBuffer, paramsBuffer); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); // Expected results: Timestamp * period std::vector expected = PrepareExpectedResults(0, kTimestampCount, kOffset); EXPECT_BUFFER(timestampsBuffer, 0, kTimestampCount * sizeof(uint64_t), new InternalShaderExpectation(expected.data(), kTimestampCount)); } // Convert timestamps in timestamps buffer with offset 8 // Test for ResolveQuerySet(querySet, 1, kTimestampCount - 1, timestampsBuffer, 8) { constexpr uint32_t kFirst = 1u; constexpr uint32_t kOffset = 8u; // Write orignal timestamps to timestamps buffer queue.WriteBuffer(timestampsBuffer, 0, timestamps.data(), kTimestampCount * sizeof(uint64_t)); // The params uniform buffer dawn_native::TimestampParams params = {kFirst, kTimestampCount - kFirst, kOffset, kPeriod}; wgpu::Buffer paramsBuffer = utils::CreateBufferFromData(device, ¶ms, sizeof(params), wgpu::BufferUsage::Uniform); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); EncodeConvertTimestampsToNanoseconds(encoder, timestampsBuffer, availabilityBuffer, paramsBuffer); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); // Expected results: Timestamp * period std::vector expected = PrepareExpectedResults(kFirst, kTimestampCount - kFirst, kOffset); EXPECT_BUFFER(timestampsBuffer, 0, kTimestampCount * sizeof(uint64_t), new InternalShaderExpectation(expected.data(), kTimestampCount)); } // Convert partial timestamps in timestamps buffer with offset 8 // Test for ResolveQuerySet(querySet, 1, 3, timestampsBuffer, 8) { constexpr uint32_t kFirst = 1u; constexpr uint32_t kCount = 3u; constexpr uint32_t kOffset = 8u; // Write orignal timestamps to timestamps buffer queue.WriteBuffer(timestampsBuffer, 0, timestamps.data(), kTimestampCount * sizeof(uint64_t)); // The params uniform buffer dawn_native::TimestampParams params = {kFirst, kCount, kOffset, kPeriod}; wgpu::Buffer paramsBuffer = utils::CreateBufferFromData(device, ¶ms, sizeof(params), wgpu::BufferUsage::Uniform); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); EncodeConvertTimestampsToNanoseconds(encoder, timestampsBuffer, availabilityBuffer, paramsBuffer); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); // Expected results: Timestamp * period std::vector expected = PrepareExpectedResults(kFirst, kCount, kOffset); EXPECT_BUFFER(timestampsBuffer, 0, kTimestampCount * sizeof(uint64_t), new InternalShaderExpectation(expected.data(), kTimestampCount)); } } DAWN_INSTANTIATE_TEST(QueryInternalShaderTests, D3D12Backend(), MetalBackend(), VulkanBackend());