KiCad PCB EDA Suite
Loading...
Searching...
No Matches
rhi_gal_pipelines.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
21#include <rhi/rhi_device.h>
22#include <rhi/rhi_bind_group.h>
23#include <rhi/rhi_pipeline.h>
24#include <rhi/rhi_vertex.h>
25#include <rhi/rhi_glyph_atlas.h>
26#include <rhi/rhi_instance.h>
27
28#include <algorithm>
29#include <cstdio>
30
31#include <rhi_shaders.h>
32
33
35 m_device( aDevice )
36{
37}
38
39
44
45
47 const RHI_SHADER_BLOB& aFrag )
48{
49 // Vulkan uses SPIR-V bytecode; Metal uses the cross-compiled MSL text
50 const bool useSource = m_device.GetBackend() != RHI_BACKEND::VULKAN && aVert.msl && aFrag.msl;
51
52 auto create = [&]( RHI_SHADER_TYPE aType, const RHI_SHADER_BLOB& aBlob )
53 {
54 RHI_SHADER_DESC desc;
55 desc.type = aType;
56 desc.bytecode = useSource ? reinterpret_cast<const uint8_t*>( aBlob.msl ) : aBlob.spirv;
57 desc.sizeBytes = useSource ? aBlob.mslSize : aBlob.spirvSize;
58 return m_device.CreateShader( desc );
59 };
60
61 return { create( RHI_SHADER_TYPE::VERTEX, aVert ), create( RHI_SHADER_TYPE::FRAGMENT, aFrag ) };
62}
63
64
66 bool aNegativeBlend,
67 bool aHasInstanceBinding,
68 bool aSingleCoverage,
69 bool aKnockoutMark,
70 bool aSquareAlpha,
71 bool aPremultiplied )
72{
73 if( !aShaders.vert || !aShaders.frag )
74 return {};
75
77 desc.vertexShader = aShaders.vert;
78 desc.fragmentShader = aShaders.frag;
79
80 desc.vertexBuffers.push_back( RhiStandardVertexLayout() );
81
82 if( aHasInstanceBinding )
83 {
84 desc.vertexBuffers.push_back( RhiInstanceLayout() );
85 }
86
88
89 RHI_BLEND_STATE blend;
90 blend.enabled = true;
91
92 if( aKnockoutMark )
93 {
94 // Colour stays as it is, only depth is written
101 }
102 else if( aNegativeBlend )
103 {
104 // Erases the destination under the source like CAIRO_OPERATOR_CLEAR for negative gerber polarity
111 }
112 else
113 {
120 }
121
122 desc.blendStates.push_back( blend );
123
124 // Depth test and write enabled for layer ordering
125 desc.depthStencil.depthTestEnable = true;
126 desc.depthStencil.depthWriteEnable = true;
128
129 // The first fragment of a layer writes the layer's depth, which a second one then fails
130 if( aSingleCoverage )
132
133 if( m_frameUBOLayout )
134 desc.bindGroupLayouts.push_back( m_frameUBOLayoutDesc );
135
136 // Only the track shaders draw glyphs of the bitmap font
137 const bool glyphs = &aShaders == &m_shaderPairs[0];
138
139 if( glyphs )
140 desc.bindGroupLayouts.push_back( m_glyphLayoutDesc );
141
143 desc.renderPassHasDepthStencil = true;
144
147
148 RHI_PipelineHandle pipeline = m_device.CreatePipeline( desc );
149
150 if( glyphs && pipeline )
151 m_glyphPipelines.push_back( pipeline );
152
153 return pipeline;
154}
155
156
158{
159 return std::find( m_glyphPipelines.begin(), m_glyphPipelines.end(), aPipeline ) != m_glyphPipelines.end();
160}
161
162
164{
165 for( const auto& [pipeline, variant] : m_overlayVariants )
166 {
167 if( pipeline == aPipeline )
168 return variant;
169 }
170
171 return aPipeline;
172}
173
174
176{
177 for( const auto& [pipeline, variant] : m_premultipliedVariants )
178 {
179 if( pipeline == aPipeline )
180 return variant;
181 }
182
183 return aPipeline;
184}
185
186
188 const ShaderPair& aShaders, bool aBlendEnabled,
189 const std::vector<RHI_BIND_GROUP_LAYOUT_DESC>& aBindGroupLayouts,
190 bool aPremultipliedAlpha, bool aRenderPassHasDepthStencil, bool aSquareAlpha )
191{
192 if( !aShaders.vert || !aShaders.frag )
193 return {};
194
196 desc.vertexShader = aShaders.vert;
197 desc.fragmentShader = aShaders.frag;
198
199 // Fullscreen quads use slot 0 only
200 desc.vertexBuffers.push_back( RhiStandardVertexLayout() );
201
203
204 RHI_BLEND_STATE blend;
205 blend.enabled = aBlendEnabled;
206
207 if( aBlendEnabled )
208 {
209 blend.srcColor = aPremultipliedAlpha ? RHI_BLEND_FACTOR::ONE
216 }
217
218 desc.blendStates.push_back( blend );
219
220 desc.depthStencil.depthTestEnable = false;
221 desc.depthStencil.depthWriteEnable = false;
222
224 desc.renderPassHasDepthStencil = aRenderPassHasDepthStencil;
225
226 for( const auto& bgl : aBindGroupLayouts )
227 desc.bindGroupLayouts.push_back( bgl );
228
231
232 return m_device.CreatePipeline( desc );
233}
234
235
237{
238 if( m_initialized )
239 return true;
240
241 RHI_BIND_GROUP_LAYOUT_DESC uboLayoutDesc;
243 uboLayoutDesc.entries.push_back(
245
246 m_frameUBOLayoutDesc = uboLayoutDesc;
247 m_frameUBOLayout = m_device.CreateBindGroupLayout( uboLayoutDesc );
248
249 if( !m_frameUBOLayout )
250 return false;
251
254 m_glyphLayoutDesc.entries.push_back(
256 m_glyphLayoutDesc.entries.push_back(
258
259 m_glyphLayout = m_device.CreateBindGroupLayout( m_glyphLayoutDesc );
260
261 if( !m_glyphLayout )
262 return false;
263
264 struct SHADER_DEF
265 {
266 const RHI_SHADER_BLOB& vert;
267 const RHI_SHADER_BLOB& frag;
268 };
269
270 const SHADER_DEF shaderDefs[] = {
271 { k_pcb_track_vert, k_pcb_track_frag },
272 { k_pcb_pad_vert, k_pcb_pad_frag },
273 { k_pcb_arc_vert, k_pcb_arc_frag },
274 { k_pcb_zone_vert, k_pcb_zone_frag },
275 { k_sch_wire_vert, k_sch_wire_frag },
276 // The grid and the cursor each cover the whole target with the same quad
277 { k_cursor_vert, k_grid_frag },
278 { k_cursor_vert, k_cursor_frag },
279 { k_compositor_vert, k_compositor_frag },
280 { k_bitmap_vert, k_bitmap_frag },
281 { k_compositor_vert, k_compositor_difference_frag },
282 };
283
284 constexpr size_t SHADER_COUNT = std::size( shaderDefs );
285
286 m_shaderPairs.resize( SHADER_COUNT );
287
288 for( size_t i = 0; i < SHADER_COUNT; i++ )
289 {
290 m_shaderPairs[i] = loadShaderPair( shaderDefs[i].vert, shaderDefs[i].frag );
291
292 if( !m_shaderPairs[i].vert || !m_shaderPairs[i].frag )
293 {
294 Destroy();
295 return false;
296 }
297 }
298
299 auto addOverlayVariant = [&]( RHI_PipelineHandle aPipeline, RHI_PipelineHandle aVariant )
300 {
301 if( aPipeline && aVariant )
302 m_overlayVariants.emplace_back( aPipeline, aVariant );
303 else if( aVariant )
304 m_device.DestroyPipeline( aVariant );
305 };
306
307 auto addPremultipliedVariant = [&]( RHI_PipelineHandle aPipeline, const ShaderPair& aShaders,
308 bool aSingleCoverage )
309 {
310 RHI_PipelineHandle variant = createInstancedPipeline( aShaders, false, true, aSingleCoverage, false, false,
311 true );
312
313 if( aPipeline && variant )
314 m_premultipliedVariants.emplace_back( aPipeline, variant );
315 else if( variant )
316 m_device.DestroyPipeline( variant );
317 };
318
319 // Overlay content blends its alpha by its alpha as well, see GetOverlayVariant
320 auto addOverlayVariants = [&]( RHI_PipelineHandle aBlend, RHI_PipelineHandle aOnce, const ShaderPair& aShaders,
321 bool aInstanced )
322 {
323 addOverlayVariant( aBlend, createInstancedPipeline( aShaders, false, aInstanced, false, false, true ) );
324 addOverlayVariant( aOnce, createInstancedPipeline( aShaders, false, aInstanced, true, false, true ) );
325 };
326
327 // The shaderDefs entry of each RHI_SHAPE
328 static constexpr int shapeShaders[KIGFX::RHI_SHAPES] = { 0, 2, 1, 3 };
329
330 for( int shape = 0; shape < KIGFX::RHI_SHAPES; ++shape )
331 {
332 using enum KIGFX::LAYER_DRAW_MODE;
333
334 const ShaderPair& shaders = m_shaderPairs[shapeShaders[shape]];
335 auto& pipelines = m_shapePipelines[shape];
336
337 // Polygons are pre-tessellated triangles with no instance data, so they must not declare slot 1
338 const bool instanced = shape != KIGFX::RHI_SHAPE_POLYGON;
339
340 pipelines[static_cast<int>( BLEND )] = createInstancedPipeline( shaders, false, instanced );
341 pipelines[static_cast<int>( NEGATIVE )] = createInstancedPipeline( shaders, true, instanced );
342 pipelines[static_cast<int>( ONCE )] = createInstancedPipeline( shaders, false, instanced, true );
343
344 if( instanced )
345 pipelines[static_cast<int>( MARK )] = createInstancedPipeline( shaders, false, true, false, true );
346
347 addOverlayVariants( pipelines[static_cast<int>( BLEND )], pipelines[static_cast<int>( ONCE )], shaders,
348 instanced );
349
350 if( instanced )
351 {
352 addPremultipliedVariant( pipelines[static_cast<int>( BLEND )], shaders, false );
353 addPremultipliedVariant( pipelines[static_cast<int>( ONCE )], shaders, true );
354 }
355 }
356
359 addOverlayVariants( m_wirePipeline, m_wireOncePipeline, m_shaderPairs[4], true );
360
361 // The grid and cursor read their uniforms from a UBO at set 0
362 RHI_BIND_GROUP_LAYOUT_DESC fsUboLayoutDesc;
364 fsUboLayoutDesc.entries.push_back(
366
367 m_fullscreenUBOLayout = m_device.CreateBindGroupLayout( fsUboLayoutDesc );
368
370 {
371 fprintf( stderr, "RHI: fullscreen UBO layout creation FAILED\n" );
372 Destroy();
373 return false;
374 }
375
376 m_gridPipeline = createFullscreenPipeline( m_shaderPairs[5], true, { fsUboLayoutDesc },
377 false, true );
378
379 if( !m_gridPipeline )
380 fprintf( stderr, "RHI: grid pipeline creation FAILED\n" );
381
382 m_cursorCompositePipeline = createFullscreenPipeline( m_shaderPairs[6], true, { fsUboLayoutDesc }, false, false );
383
384 // Post-process pipelines read their input texture at set 0
385 RHI_BIND_GROUP_LAYOUT_DESC texLayoutDesc;
387 texLayoutDesc.entries.push_back(
389
390 m_textureSamplerLayout = m_device.CreateBindGroupLayout( texLayoutDesc );
391
393 {
394 fprintf( stderr, "RHI: texture sampler layout creation FAILED\n" );
395 Destroy();
396 return false;
397 }
398
399 m_compositorPipeline = createFullscreenPipeline( m_shaderPairs[7], true, { texLayoutDesc },
400 true, false );
401
403 { texLayoutDesc }, true, true );
404
405 if( RHI_PipelineHandle variant = createFullscreenPipeline( m_shaderPairs[7], true, { texLayoutDesc }, true,
406 true, true ) )
407 {
409 m_overlayVariants.emplace_back( m_inlineCompositorPipeline, variant );
410 else
411 m_device.DestroyPipeline( variant );
412 }
413
414 RHI_BIND_GROUP_LAYOUT_DESC differenceLayoutDesc = texLayoutDesc;
415 differenceLayoutDesc.entries.push_back(
417
418 m_differenceLayout = m_device.CreateBindGroupLayout( differenceLayoutDesc );
419
420 // Without it diff layers are drawn as ordinary ones
422 {
424 createFullscreenPipeline( m_shaderPairs[9], false, { differenceLayoutDesc }, false, true );
425 }
426
428 fprintf( stderr, "RHI: difference pipeline creation FAILED\n" );
429
430 // The bitmap pipeline reads the FrameUBO at set 0 and its texture at set 1, and depth tests to stay
431 // in layer order with other geometry
432 {
433 const ShaderPair& bmpShaders = m_shaderPairs[8];
434
435 if( bmpShaders.vert && bmpShaders.frag )
436 {
438 desc.vertexShader = bmpShaders.vert;
439 desc.fragmentShader = bmpShaders.frag;
440
441 desc.vertexBuffers.push_back( RhiStandardVertexLayout() );
443
444 RHI_BLEND_STATE blend;
445 blend.enabled = true;
452 desc.blendStates.push_back( blend );
453
454 desc.depthStencil.depthTestEnable = true;
455 desc.depthStencil.depthWriteEnable = true;
457
458 desc.bindGroupLayouts.push_back( m_frameUBOLayoutDesc );
459 desc.bindGroupLayouts.push_back( texLayoutDesc );
460
462 desc.renderPassHasDepthStencil = true;
463
466
467 m_bitmapPipeline = m_device.CreatePipeline( desc );
468
469 if( !m_bitmapPipeline )
470 {
471 fprintf( stderr, "RHI: bitmap pipeline creation FAILED\n" );
472 Destroy();
473 return false;
474 }
475
476 desc.blendStates.front().srcAlpha = RHI_BLEND_FACTOR::SRC_ALPHA;
477 addOverlayVariant( m_bitmapPipeline, m_device.CreatePipeline( desc ) );
478 }
479 }
480
483
484 for( int shape = 0; shape < KIGFX::RHI_SHAPES; ++shape )
485 {
486 for( int mode = 0; mode < DRAW_MODES; ++mode )
487 {
488 const bool wanted = shape != KIGFX::RHI_SHAPE_POLYGON
489 || mode != static_cast<int>( KIGFX::LAYER_DRAW_MODE::MARK );
490
491 complete = complete && ( m_shapePipelines[shape][mode] || !wanted );
492 }
493 }
494
495 if( !complete )
496 {
497 Destroy();
498 return false;
499 }
500
501 m_initialized = true;
502 return true;
503}
504
505
507{
508 for( auto& pipelines : m_shapePipelines )
509 {
510 for( RHI_PipelineHandle& pipeline : pipelines )
511 m_device.Release( pipeline );
512 }
513
517 {
518 m_device.Release( *pipeline );
519 }
520
521 for( auto& [pipeline, variant] : m_overlayVariants )
522 m_device.Release( variant );
523
524 for( auto& [pipeline, variant] : m_premultipliedVariants )
525 m_device.Release( variant );
526
527 for( ShaderPair& pair : m_shaderPairs )
528 {
529 m_device.Release( pair.vert );
530 m_device.Release( pair.frag );
531 }
532
535 {
536 m_device.Release( *layout );
537 }
538
539 m_overlayVariants.clear();
541 m_shaderPairs.clear();
542 m_glyphPipelines.clear();
543 m_initialized = false;
544}
Creates, uses and destroys GPU resources through one graphics API backend.
Definition rhi_device.h:66
RHI_BindGroupLayoutHandle m_differenceLayout
bool Initialize()
Create all shader modules and pipeline objects.
ShaderPair loadShaderPair(const RHI_SHADER_BLOB &aVert, const RHI_SHADER_BLOB &aFrag)
RHI_BindGroupLayoutHandle m_textureSamplerLayout
RHI_BindGroupLayoutHandle m_fullscreenUBOLayout
RHI_PipelineHandle m_compositorPipeline
RHI_GAL_PIPELINES(RHI_DEVICE &aDevice)
std::vector< std::pair< RHI_PipelineHandle, RHI_PipelineHandle > > m_premultipliedVariants
RHI_BindGroupLayoutHandle m_frameUBOLayout
RHI_PipelineHandle m_gridPipeline
RHI_PipelineHandle m_bitmapPipeline
std::vector< ShaderPair > m_shaderPairs
Shader modules kept alive for the lifetime of the pipelines.
RHI_PipelineHandle GetPremultipliedVariant(RHI_PipelineHandle aPipeline) const
The variant of a pipeline for a cached layer that the legacy canvas draws on its overlay.
static constexpr int DRAW_MODES
void Destroy()
Release all GPU resources.
RHI_BIND_GROUP_LAYOUT_DESC m_glyphLayoutDesc
std::vector< RHI_PipelineHandle > m_glyphPipelines
RHI_BIND_GROUP_LAYOUT_DESC m_frameUBOLayoutDesc
RHI_PipelineHandle GetOverlayVariant(RHI_PipelineHandle aPipeline) const
The variant of a pipeline for content drawn into the overlay target.
RHI_BindGroupLayoutHandle m_glyphLayout
std::array< std::array< RHI_PipelineHandle, DRAW_MODES >, KIGFX::RHI_SHAPES > m_shapePipelines
RHI_PipelineHandle m_wirePipeline
RHI_PipelineHandle m_differencePipeline
bool UsesGlyphAtlas(RHI_PipelineHandle aPipeline) const
True for a pipeline that expects the glyph bind group at set 1.
RHI_PipelineHandle createInstancedPipeline(const ShaderPair &aShaders, bool aNegativeBlend=false, bool aHasInstanceBinding=true, bool aSingleCoverage=false, bool aKnockoutMark=false, bool aSquareAlpha=false, bool aPremultiplied=false)
Build a pipeline reading RHI_VERTEX from slot 0 and, with aHasInstanceBinding, RHI_INSTANCE from slot...
RHI_PipelineHandle m_inlineCompositorPipeline
RHI_PipelineHandle createFullscreenPipeline(const ShaderPair &aShaders, bool aBlendEnabled, const std::vector< RHI_BIND_GROUP_LAYOUT_DESC > &aBindGroupLayouts={}, bool aPremultipliedAlpha=false, bool aRenderPassHasDepthStencil=false, bool aSquareAlpha=false)
RHI_PipelineHandle m_wireOncePipeline
std::vector< std::pair< RHI_PipelineHandle, RHI_PipelineHandle > > m_overlayVariants
Each pipeline that has an overlay variant, paired with it.
RHI_PipelineHandle m_cursorCompositePipeline
@ RHI_SHAPE_POLYGON
@ MARK
write depth only, so the layer's later draws stay out of these pixels
constexpr int RHI_SHAPES
@ ALL
Graphics stages only, compute is opted into explicitly.
constexpr uint32_t RHI_UBO_BINDING
MSL is generated with –msl-decoration-binding, so a Metal buffer index is its binding number in every...
constexpr uint32_t RHI_GLYPH_TABLE_BINDING
Binding of the glyph table beside the atlas texture at 0, see RHI_UBO_BINDING.
RHI_HANDLE< RHI_TAG_PIPELINE > RHI_PipelineHandle
Definition rhi_handle.h:86
RHI_HANDLE< RHI_TAG_BIND_GROUP_LAYOUT > RHI_BindGroupLayoutHandle
Definition rhi_handle.h:89
RHI_VERTEX_BUFFER_LAYOUT_DESC RhiInstanceLayout()
Per-instance vertex layout of RHI_INSTANCE, starting at location 3 after the RHI_VERTEX attributes.
RHI_SHADER_TYPE
Definition rhi_shader.h:27
RHI_VERTEX_BUFFER_LAYOUT_DESC RhiStandardVertexLayout()
Shaders read position at location 0, color at 1 and texture coordinates at 2.
Definition rhi_vertex.h:62
std::vector< RHI_BINDING_ENTRY > entries
RHI_BIND_FREQUENCY frequency
RHI_BLEND_FACTOR dstAlpha
RHI_BLEND_FACTOR srcColor
RHI_BLEND_FACTOR dstColor
RHI_BLEND_FACTOR srcAlpha
RHI_BLEND_OP colorOp
RHI_BLEND_OP alphaOp
RHI_COMPARE_OP depthCompareOp
Everything here is fixed at creation except the states listed in dynamicStates.
std::vector< RHI_BIND_GROUP_LAYOUT_DESC > bindGroupLayouts
Element n is bind group n, as passed to RHI_DEVICE::BindGroup().
std::vector< RHI_DYNAMIC_STATE > dynamicStates
RHI_TEXTURE_FORMAT colorFormat
RHI_PRIMITIVE_TOPOLOGY topology
std::vector< RHI_VERTEX_BUFFER_LAYOUT_DESC > vertexBuffers
RHI_ShaderHandle vertexShader
RHI_ShaderHandle fragmentShader
std::vector< RHI_BLEND_STATE > blendStates
One per color attachment.
RHI_DEPTH_STENCIL_STATE depthStencil
One shader stage as GenerateShaderHeader.cmake embeds it.
Definition rhi_shader.h:46
const char * msl
Definition rhi_shader.h:49
The bytecode is SPIR-V on Vulkan and MSL source text on Metal.
Definition rhi_shader.h:35
RHI_SHADER_TYPE type
Definition rhi_shader.h:36
const uint8_t * bytecode
Definition rhi_shader.h:37