KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_rhi_compute.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21#include <rhi/gpu_compute.h>
22#include <checkerboard_comp.h>
23#include <workgroup_comp.h>
24#include <reduction_comp.h>
25#include <multipass_comp.h>
26
27#include <algorithm>
28#include <array>
29#include <cmath>
30#include <vector>
31
32
33namespace
34{
35RHI_BIND_GROUP_LAYOUT_DESC StorageLayout( bool aReduction = false )
36{
38 layout.entries.push_back(
41
42 if( aReduction )
44
45 return layout;
46}
47
48
49struct RESOURCES
50{
51 std::shared_ptr<GPU_COMPUTE> gpu;
52 std::vector<RHI_BufferHandle> buffers;
53 std::vector<RHI_ComputePipelineHandle> pipelines;
56
57 ~RESOURCES()
58 {
59 gpu->DestroyBindGroup( group );
60 gpu->DestroyBindGroupLayout( layout );
61
62 for( auto pipeline : pipelines )
63 gpu->DestroyPipeline( pipeline );
64
65 for( auto buffer : buffers )
66 gpu->DestroyBuffer( buffer );
67 }
68
69 RHI_BufferHandle Buffer( size_t aBytes )
70 {
71 auto buffer = gpu->CreateStorageBuffer( aBytes );
72 BOOST_REQUIRE( buffer );
73 buffers.push_back( buffer );
74 return buffer;
75 }
76
77 RHI_ComputePipelineHandle Pipeline( const RHI_COMPUTE_PIPELINE_DESC& aDesc )
78 {
79 auto pipeline = gpu->CreatePipeline( aDesc );
80 BOOST_REQUIRE( pipeline );
81 pipelines.push_back( pipeline );
82 return pipeline;
83 }
84
85 void Group( const RHI_BIND_GROUP_LAYOUT_DESC& aLayout, const std::vector<RHI_BIND_GROUP_RESOURCE>& aEntries )
86 {
87 layout = gpu->CreateBindGroupLayout( aLayout );
88 BOOST_REQUIRE( layout );
89 group = gpu->CreateBindGroup( { layout, aEntries } );
90 BOOST_REQUIRE( group );
91 }
92};
93} // namespace
94
95
96BOOST_AUTO_TEST_SUITE( RhiCompute )
97
98BOOST_AUTO_TEST_CASE( DescriptorValidation )
99{
101 desc.bindGroupLayouts = { StorageLayout() };
102 BOOST_CHECK( RhiValidateComputePipelineDesc( desc ) );
103 desc.constants = { { 0, 1 } };
104 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
105 desc.constants = { { 1, 1 } };
106 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
107 desc.constants = { { 2, 0 }, { 2, 1 } };
108 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
109 desc.constants.clear();
110 desc.bindGroupLayouts[0].entries[0].binding = 0;
111 BOOST_CHECK( desc.bindGroupLayouts[0].Validate() );
112 BOOST_CHECK( !RhiValidateComputeBindGroupLayout( desc.bindGroupLayouts[0] ) );
113 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
114 desc.bindGroupLayouts = { StorageLayout() };
115 desc.workgroupX = 0;
116 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
117 desc.workgroupX = 8;
118 desc.workgroupY = 0;
119 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
120 desc.workgroupY = 8;
122 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
123 desc.pushConstantBytes = 0;
124 desc.bindGroupLayouts.push_back( StorageLayout() );
125 BOOST_CHECK( !RhiValidateComputePipelineDesc( desc ) );
126}
127
128BOOST_AUTO_TEST_CASE( RecordedBytesAreOwned )
129{
130 std::array<uint32_t, 2> data{ 37, 19 };
131 RHI_COMPUTE_JOB job;
132 job.Upload( RHI_BufferHandle{ 1 }, data.data(), sizeof( data ), 4 );
133 job.PushConstants( data.data(), sizeof( data ) );
134 data.fill( 0 );
135 const std::array<uint32_t, 2> expected{ 37, 19 };
136 const auto* bytes = reinterpret_cast<const uint8_t*>( expected.data() );
137 BOOST_CHECK( job.IsValid() );
138 BOOST_REQUIRE_EQUAL( job.Commands().size(), 2 );
139
140 for( const auto& command : job.Commands() )
141 BOOST_CHECK_EQUAL_COLLECTIONS( command.data.begin(), command.data.end(), bytes, bytes + sizeof( expected ) );
142
143 RHI_COMPUTE_JOB invalid;
144 invalid.Upload( RHI_BufferHandle{ 1 }, nullptr, 4 );
145 BOOST_CHECK( !invalid.IsValid() );
146}
147
148
149BOOST_AUTO_TEST_CASE( OrderedByteTransfers )
150{
151 auto gpu = GPU_COMPUTE::Get();
152
153 if( !gpu )
154 {
155 BOOST_TEST_MESSAGE( "Compute unavailable; skipping ordered transfers" );
156 return;
157 }
158
159 auto buffer = gpu->CreateStorageBuffer( 11 );
160 BOOST_REQUIRE( buffer );
161 std::array<uint8_t, 11> initial{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
162 std::array<uint8_t, 7> replacement{ 31, 32, 33, 34, 35, 36, 37 };
163 std::array<uint8_t, 11> first{};
164 std::array<uint8_t, 8> second{};
165 const std::array<uint8_t, 8> expected{ 2, 31, 32, 33, 34, 35, 36, 37 };
166 RHI_COMPUTE_JOB job;
167 job.Upload( buffer, initial.data(), initial.size() );
168 job.Readback( buffer, 0, first.size(), first.data() );
169 job.Upload( buffer, replacement.data(), replacement.size(), 3 );
170 replacement.fill( 0 );
171 job.Readback( buffer, 2, second.size(), second.data() );
172 BOOST_REQUIRE( gpu->Submit( job ) == RHI_COMPUTE_RESULT::OK );
173 gpu->DestroyBuffer( buffer );
174 BOOST_CHECK_EQUAL_COLLECTIONS( first.begin(), first.end(), initial.begin(), initial.end() );
175 BOOST_CHECK_EQUAL_COLLECTIONS( second.begin(), second.end(), expected.begin(), expected.end() );
176}
177
178
179BOOST_AUTO_TEST_CASE( CheckerboardSpecializations )
180{
181 auto gpu = GPU_COMPUTE::Get();
182
183 if( !gpu )
184 {
185 BOOST_TEST_MESSAGE( "Compute unavailable; skipping checkerboard" );
186 return;
187 }
188
189 RESOURCES resources{ gpu };
190 constexpr uint32_t width = 37, height = 19;
191 const std::array<uint32_t, 2> params{ width, height };
192 const auto layout = StorageLayout();
193 auto buffer = resources.Buffer( width * height * sizeof( uint32_t ) );
194 resources.Group( layout, { { 1, buffer } } );
195
196 for( auto workgroup : { std::array<uint32_t, 2>{ 8, 8 }, std::array<uint32_t, 2>{ 16, 4 } } )
197 {
198 for( uint32_t rb = 0; rb < 2; ++rb )
199 {
201 desc.shader = k_checkerboard_comp;
202 desc.bindGroupLayouts = { layout };
203 desc.constants = { { 2, rb } };
204 desc.workgroupX = workgroup[0];
205 desc.workgroupY = workgroup[1];
206 desc.pushConstantBytes = sizeof( params );
207 auto pipeline = resources.Pipeline( desc );
208 std::vector<uint32_t> cells( width * height, 0xfeedu );
209 RHI_COMPUTE_JOB job;
210 job.Upload( buffer, cells.data(), cells.size() * sizeof( cells[0] ) );
211 std::fill( cells.begin(), cells.end(), 0u );
212 job.Bind( pipeline );
213 job.BindGroup( 0, resources.group );
214 job.PushConstants( params.data(), sizeof( params ) );
215 job.Dispatch( ( width + workgroup[0] - 1 ) / workgroup[0], ( height + workgroup[1] - 1 ) / workgroup[1] );
216 job.Readback( buffer, 0, cells.size() * sizeof( cells[0] ), cells.data() );
217 BOOST_REQUIRE( gpu->Submit( job ) == RHI_COMPUTE_RESULT::OK );
218
219 for( uint32_t y = 0; y < height; ++y )
220 {
221 for( uint32_t x = 0; x < width; ++x )
222 BOOST_CHECK_EQUAL( cells[y * width + x], ( ( x + y ) & 1 ) == rb ? 17u + rb : 0xfeedu );
223 }
224 }
225 }
226}
227
228BOOST_AUTO_TEST_CASE( InjectedWorkgroupSize )
229{
230 auto gpu = GPU_COMPUTE::Get();
231
232 if( !gpu )
233 {
234 BOOST_TEST_MESSAGE( "Compute unavailable; skipping injected workgroup" );
235 return;
236 }
237
238 RESOURCES resources{ gpu };
239 auto buffer = resources.Buffer( 2 * sizeof( uint32_t ) );
240 const auto layout = StorageLayout();
241 resources.Group( layout, { { 1, buffer } } );
242
243 for( auto size : { std::array<uint32_t, 2>{ 8, 8 }, std::array<uint32_t, 2>{ 16, 4 } } )
244 {
246 desc.shader = k_workgroup_comp;
247 desc.bindGroupLayouts = { layout };
248 desc.workgroupX = size[0];
249 desc.workgroupY = size[1];
250 std::array<uint32_t, 2> result{};
251 RHI_COMPUTE_JOB job;
252 job.Upload( buffer, result.data(), sizeof( result ) );
253 job.Bind( resources.Pipeline( desc ) );
254 job.BindGroup( 0, resources.group );
255 job.Dispatch( 1 );
256 job.Readback( buffer, 0, sizeof( result ), result.data() );
257 BOOST_REQUIRE( gpu->Submit( job ) == RHI_COMPUTE_RESULT::OK );
258 BOOST_CHECK_EQUAL( result[0], size[0] );
259 BOOST_CHECK_EQUAL( result[1], size[1] );
260 }
261}
262
263BOOST_AUTO_TEST_CASE( SpecializedSharedReduction )
264{
265 auto gpu = GPU_COMPUTE::Get();
266
267 if( !gpu )
268 {
269 BOOST_TEST_MESSAGE( "Compute unavailable; skipping shared reduction" );
270 return;
271 }
272
273 RESOURCES resources{ gpu };
274 constexpr uint32_t groups = 16, localSize = 64;
275 std::vector<float> input( groups * localSize );
276
277 for( size_t i = 0; i < input.size(); ++i )
278 input[i] = static_cast<float>( static_cast<int>( ( i * 37 ) % 2003 ) - 1001 ) / 8.0f;
279
280 auto in = resources.Buffer( input.size() * sizeof( float ) );
281 auto out = resources.Buffer( groups * sizeof( float ) );
282 const auto layout = StorageLayout( true );
283 resources.Group( layout, { { 1, in }, { 2, out } } );
285 desc.shader = k_reduction_comp;
286 desc.bindGroupLayouts = { layout };
287 desc.workgroupX = localSize;
288 desc.workgroupY = 1;
289 std::array<float, groups> result;
290 result.fill( -1.0f );
291 RHI_COMPUTE_JOB job;
292 job.Upload( in, input.data(), input.size() * sizeof( float ) );
293 job.Upload( out, result.data(), sizeof( result ) );
294 job.Bind( resources.Pipeline( desc ) );
295 job.BindGroup( 0, resources.group );
296 job.Dispatch( groups );
297 job.Readback( out, 0, sizeof( result ), result.data() );
298 BOOST_REQUIRE( gpu->Submit( job ) == RHI_COMPUTE_RESULT::OK );
299
300 for( uint32_t group = 0; group < groups; ++group )
301 {
302 float expected = 0;
303
304 for( uint32_t local = 0; local < localSize; ++local )
305 expected = std::max( expected, std::abs( input[group * localSize + local] ) );
306
308 }
309}
310
311BOOST_AUTO_TEST_CASE( DependentPassesInOneJob )
312{
313 auto gpu = GPU_COMPUTE::Get();
314
315 if( !gpu )
316 {
317 BOOST_TEST_MESSAGE( "Compute unavailable; skipping dependent passes" );
318 return;
319 }
320
321 RESOURCES resources{ gpu };
322 constexpr uint32_t width = 37, height = 19, iterations = 5;
323 std::array<uint32_t, 2> params{ width, height };
324 auto buffer = resources.Buffer( width * height * sizeof( uint32_t ) );
325 const auto layout = StorageLayout();
326 resources.Group( layout, { { 1, buffer } } );
327 std::array<RHI_ComputePipelineHandle, 3> pipelines;
328
329 for( uint32_t pass = 0; pass < pipelines.size(); ++pass )
330 {
332 desc.shader = k_multipass_comp;
333 desc.bindGroupLayouts = { layout };
334 desc.constants = { { 2, pass } };
335 desc.pushConstantBytes = sizeof( params );
336 pipelines[pass] = resources.Pipeline( desc );
337 }
338
339 std::vector<uint32_t> expected( width * height ), result( width * height, 0xfeedu );
340
341 for( uint32_t y = 0; y < height; ++y )
342 {
343 for( uint32_t x = 0; x < width; ++x )
344 expected[y * width + x] = ( x + 3 * y ) % 11;
345 }
346
347 RHI_COMPUTE_JOB job;
348 job.Upload( buffer, result.data(), result.size() * sizeof( uint32_t ) );
349 job.BindGroup( 0, resources.group );
350 const auto pass = [&]( uint32_t aPass )
351 {
352 job.Bind( pipelines[aPass] );
353 job.PushConstants( params.data(), sizeof( params ) );
354 job.Dispatch( ( width + 7 ) / 8, ( height + 7 ) / 8 );
355 job.Barrier();
356 };
357 pass( 2 );
358
359 for( uint32_t iteration = 0; iteration < iterations; ++iteration )
360 {
361 for( uint32_t rb = 0; rb < 2; ++rb )
362 {
363 pass( rb );
364
365 for( uint32_t y = 0; y < height; ++y )
366 {
367 for( uint32_t x = 0; x < width; ++x )
368 {
369 if( ( ( x + y ) & 1 ) == rb )
370 {
371 auto i = y * width + x;
372 expected[i] += 1 + expected[x == 0 ? i + 1 : i - 1] % 3;
373 }
374 }
375 }
376 }
377 }
378
379 job.Readback( buffer, 0, result.size() * sizeof( uint32_t ), result.data() );
380 BOOST_REQUIRE( gpu->Submit( job ) == RHI_COMPUTE_RESULT::OK );
381 BOOST_CHECK_EQUAL_COLLECTIONS( result.begin(), result.end(), expected.begin(), expected.end() );
382}
383
static std::shared_ptr< GPU_COMPUTE > Get()
Return nullptr if compute is unavailable; replace a lost instance on the next call.
Plain owning command data, recordable on any thread without device access.
Definition rhi_compute.h:91
void Dispatch(uint32_t aGroupsX, uint32_t aGroupsY=1)
const std::vector< COMMAND > & Commands() const
void BindGroup(uint32_t aIndex, RHI_BindGroupHandle aGroup)
bool IsValid() const
void Readback(RHI_BufferHandle aBuffer, size_t aOffset, size_t aBytes, void *aDestination)
void Upload(RHI_BufferHandle aBuffer, const void *aData, size_t aBytes, size_t aOffset=0)
Copy upload bytes immediately.
void Bind(RHI_ComputePipelineHandle aPipeline)
void PushConstants(const void *aData, size_t aBytes)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
constexpr uint32_t RHI_MAX_PUSH_CONSTANT_BYTES
Largest push constant size SetPushConstants() accepts, in bytes.
Definition rhi_command.h:60
bool RhiValidateComputePipelineDesc(const RHI_COMPUTE_PIPELINE_DESC &aDesc)
Validate the portable contract without accessing a GPU or inspecting shader bytecode.
bool RhiValidateComputeBindGroupLayout(const RHI_BIND_GROUP_LAYOUT_DESC &aLayout)
Validate a compute bind group layout.
RHI_HANDLE< RHI_TAG_BUFFER > RHI_BufferHandle
Definition rhi_handle.h:83
RHI_HANDLE< RHI_TAG_BIND_GROUP > RHI_BindGroupHandle
Definition rhi_handle.h:90
RHI_HANDLE< RHI_TAG_COMPUTE_PIPELINE > RHI_ComputePipelineHandle
Definition rhi_handle.h:87
RHI_HANDLE< RHI_TAG_BIND_GROUP_LAYOUT > RHI_BindGroupLayoutHandle
Definition rhi_handle.h:89
std::vector< RHI_BINDING_ENTRY > entries
std::vector< RHI_SPECIALIZATION_CONSTANT > constants
Definition rhi_compute.h:47
std::vector< RHI_BIND_GROUP_LAYOUT_DESC > bindGroupLayouts
Definition rhi_compute.h:46
RHI_SHADER_BLOB shader
Definition rhi_compute.h:45
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_CHECK_EQUAL_COLLECTIONS(mixed.begin(), mixed.end(), expMixed.begin(), expMixed.end())
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(DescriptorValidation)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")