1 // Copyright 2020 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "tests/DawnTest.h"
16
17 #include "dawn_native/Buffer.h"
18 #include "dawn_native/CommandEncoder.h"
19 #include "dawn_native/QueryHelper.h"
20 #include "utils/WGPUHelpers.h"
21
22 namespace {
23
EncodeConvertTimestampsToNanoseconds(wgpu::CommandEncoder encoder,wgpu::Buffer timestamps,wgpu::Buffer availability,wgpu::Buffer params)24 void EncodeConvertTimestampsToNanoseconds(wgpu::CommandEncoder encoder,
25 wgpu::Buffer timestamps,
26 wgpu::Buffer availability,
27 wgpu::Buffer params) {
28 ASSERT_TRUE(dawn_native::EncodeConvertTimestampsToNanoseconds(
29 dawn_native::FromAPI(encoder.Get()), dawn_native::FromAPI(timestamps.Get()),
30 dawn_native::FromAPI(availability.Get()),
31 dawn_native::FromAPI(params.Get()))
32 .IsSuccess());
33 }
34
35 class InternalShaderExpectation : public detail::Expectation {
36 public:
37 ~InternalShaderExpectation() override = default;
38
InternalShaderExpectation(const uint64_t * values,const unsigned int count)39 InternalShaderExpectation(const uint64_t* values, const unsigned int count) {
40 mExpected.assign(values, values + count);
41 }
42
43 // Expect the actual results are approximately equal to the expected values.
Check(const void * data,size_t size)44 testing::AssertionResult Check(const void* data, size_t size) override {
45 DAWN_ASSERT(size == sizeof(uint64_t) * mExpected.size());
46 constexpr static float kErrorToleranceRatio = 0.002f;
47
48 const uint64_t* actual = static_cast<const uint64_t*>(data);
49 for (size_t i = 0; i < mExpected.size(); ++i) {
50 if (mExpected[i] == 0 && actual[i] != 0) {
51 return testing::AssertionFailure()
52 << "Expected data[" << i << "] to be 0, actual " << actual[i]
53 << std::endl;
54 }
55
56 if (abs(static_cast<int64_t>(mExpected[i] - actual[i])) >
57 mExpected[i] * kErrorToleranceRatio) {
58 return testing::AssertionFailure()
59 << "Expected data[" << i << "] to be " << mExpected[i] << ", actual "
60 << actual[i] << ". Error rate is larger than " << kErrorToleranceRatio
61 << std::endl;
62 }
63 }
64
65 return testing::AssertionSuccess();
66 }
67
68 private:
69 std::vector<uint64_t> mExpected;
70 };
71
72 } // anonymous namespace
73
74 constexpr static uint64_t kSentinelValue = ~uint64_t(0u);
75
76 // A gpu frequency on Intel D3D12 (ticks/second)
77 constexpr uint64_t kGPUFrequency = 12000048u;
78 constexpr uint64_t kNsPerSecond = 1000000000u;
79 // Timestamp period in nanoseconds
80 constexpr float kPeriod = static_cast<float>(kNsPerSecond) / kGPUFrequency;
81
82 class QueryInternalShaderTests : public DawnTest {
83 protected:
84 // Original timestamp values in query set for testing
85 const std::vector<uint64_t> querySetValues = {
86 kSentinelValue, // garbage data which is not written at beginning
87 10079569507, // t0
88 10394415012, // t1
89 kSentinelValue, // garbage data which is not written between timestamps
90 11713454943, // t2
91 38912556941, // t3 (big value)
92 10080295766, // t4 (reset)
93 12159966783, // t5 (after reset)
94 12651224612, // t6
95 39872473956, // t7
96 };
97
98 const uint32_t kQueryCount = querySetValues.size();
99
100 // Timestamps available state
101 const std::vector<uint32_t> availabilities = {0, 1, 1, 0, 1, 1, 1, 1, 1, 1};
102
GetExpectedResults(const std::vector<uint64_t> & origin,uint32_t start,uint32_t firstQuery,uint32_t queryCount)103 const std::vector<uint64_t> GetExpectedResults(const std::vector<uint64_t>& origin,
104 uint32_t start,
105 uint32_t firstQuery,
106 uint32_t queryCount) {
107 std::vector<uint64_t> expected(origin.begin(), origin.end());
108 for (size_t i = 0; i < queryCount; i++) {
109 if (availabilities[firstQuery + i] == 0) {
110 // Not a available timestamp, write 0
111 expected[start + i] = 0u;
112 } else {
113 // Maybe the timestamp * period is larger than the maximum of uint64, so cast the
114 // delta value to double (higher precision than float)
115 expected[start + i] =
116 static_cast<uint64_t>(static_cast<double>(origin[start + i]) * kPeriod);
117 }
118 }
119 return expected;
120 }
121
RunTest(uint32_t firstQuery,uint32_t queryCount,uint32_t destinationOffset)122 void RunTest(uint32_t firstQuery, uint32_t queryCount, uint32_t destinationOffset) {
123 ASSERT(destinationOffset % 256 == 0);
124
125 uint64_t size = queryCount * sizeof(uint64_t) + destinationOffset;
126
127 // The resolve buffer storing original timestamps and the converted values
128 wgpu::BufferDescriptor timestampsDesc;
129 timestampsDesc.size = size;
130 timestampsDesc.usage = wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc |
131 wgpu::BufferUsage::CopyDst;
132 wgpu::Buffer timestampsBuffer = device.CreateBuffer(×tampsDesc);
133
134 // Set sentinel values to check the slots before the destination offset should not be
135 // converted
136 std::vector<uint64_t> timestampValues(size / sizeof(uint64_t), 1u);
137 uint32_t start = destinationOffset / sizeof(uint64_t);
138 for (uint32_t i = 0; i < queryCount; i++) {
139 timestampValues[start + i] = querySetValues[firstQuery + 1];
140 }
141 // Write sentinel values and orignal timestamps to timestamps buffer
142 queue.WriteBuffer(timestampsBuffer, 0, timestampValues.data(), size);
143
144 // The buffer indicating which values are available timestamps
145 wgpu::Buffer availabilityBuffer =
146 utils::CreateBufferFromData(device, availabilities.data(),
147 kQueryCount * sizeof(uint32_t), wgpu::BufferUsage::Storage);
148
149 // The params uniform buffer
150 dawn_native::TimestampParams params = {firstQuery, queryCount, destinationOffset, kPeriod};
151 wgpu::Buffer paramsBuffer = utils::CreateBufferFromData(device, ¶ms, sizeof(params),
152 wgpu::BufferUsage::Uniform);
153
154 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
155 EncodeConvertTimestampsToNanoseconds(encoder, timestampsBuffer, availabilityBuffer,
156 paramsBuffer);
157 wgpu::CommandBuffer commands = encoder.Finish();
158 queue.Submit(1, &commands);
159
160 const std::vector<uint64_t> expected =
161 GetExpectedResults(timestampValues, start, firstQuery, queryCount);
162
163 EXPECT_BUFFER(timestampsBuffer, 0, size,
164 new InternalShaderExpectation(expected.data(), size / sizeof(uint64_t)));
165 }
166
167 private:
168 };
169
170 // Test the accuracy of timestamp compute shader which uses unsigned 32-bit integers to simulate
171 // unsigned 64-bit integers (timestamps) multiplied by float (period).
172 // The arguments pass to timestamp internal pipeline:
173 // - The timestamps buffer contains the original timestamps resolved from query set (created
174 // manually here), and will be used to store the results processed by the compute shader.
175 // Expect 0 for unavailable timestamps and nanoseconds for available timestamps in an expected
176 // error tolerance ratio.
177 // - The availability buffer passes the data of which slot in timestamps buffer is an initialized
178 // timestamp.
179 // - The params buffer passes the timestamp count, the offset in timestamps buffer and the
180 // timestamp period (here use GPU frequency (HZ) on Intel D3D12 to calculate the period in
181 // ns for testing).
TEST_P(QueryInternalShaderTests,TimestampComputeShader)182 TEST_P(QueryInternalShaderTests, TimestampComputeShader) {
183 // TODO(crbug.com/dawn/741): Test output is wrong with D3D12 + WARP.
184 DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP());
185
186 DAWN_TEST_UNSUPPORTED_IF(UsesWire());
187
188 // Convert timestamps in timestamps buffer with offset 0
189 // Test for ResolveQuerySet(querySet, 0, kQueryCount, timestampsBuffer, 0)
190 RunTest(0, kQueryCount, 0);
191
192 // Convert timestamps in timestamps buffer with offset 256
193 // Test for ResolveQuerySet(querySet, 1, kQueryCount - 1, timestampsBuffer, 256)
194 RunTest(1, kQueryCount - 1, 256);
195
196 // Convert partial timestamps in timestamps buffer with offset 256
197 // Test for ResolveQuerySet(querySet, 1, 4, timestampsBuffer, 256)
198 RunTest(1, 4, 256);
199 }
200
201 DAWN_INSTANTIATE_TEST(QueryInternalShaderTests, D3D12Backend(), MetalBackend(), VulkanBackend());
202