KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gal_modern.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 <gal/gal_modern.h>
22#include <rhi/rhi_device.h>
24#include <rhi/rhi_buffer.h>
27#include <rhi/rhi_projection.h>
28#include <rhi/rhi_cull_runs.h>
29#include <rhi/render_thread.h>
31#include <rhi/rhi_glyph_atlas.h>
32#include <bitmap_base.h>
33#include <gal/bitmap_texture.h>
34#include <bezier_curves.h>
35#include <font/glyph_types.h>
39#include <hash.h>
40#include <math/util.h>
41#include <mmh3_hash.h>
42
43#include <algorithm>
44#include <array>
45#include <cstring>
46#include <exception>
47#include <iterator>
48#include <tuple>
49#include <atomic>
50#include <limits>
51#include <memory>
52#include <mutex>
53#include <cmath>
54#include <utility>
55
56#include <core/utf8.h>
57#include <trace_helpers.h>
58#include <wx/log.h>
59
60#include "opengl/gl_resources.h"
61
62using namespace KIGFX;
63
64
65// Selection and highlight arithmetic can push COLOR4D components outside [0,1], and converting
66// those to uint8_t unclamped is undefined behavior.
67static inline uint8_t packColorChannel( double aValue )
68{
69 return static_cast<uint8_t>( std::clamp( aValue, 0.0, 1.0 ) * 255.0 );
70}
71
72
73static void hashBytes( MMH3_HASH& aHash, const void* aData, size_t aSize )
74{
75 aHash.addData( static_cast<const uint8_t*>( aData ), aSize );
76}
77
78
79static std::array<uint64_t, 2> hashDigest( MMH3_HASH& aHash )
80{
81 const HASH_128 digest = aHash.digest();
82
83 return { digest.Value64[0], digest.Value64[1] };
84}
85
86
87// Recount only the runs holding elements written since the last count or past aCounted. Every write
88// the GPU has to see marks the arena dirty, so the other runs are still correct.
89template <typename T, typename COUNT>
90static void updateRuns( std::vector<RHI_RUN_BOUNDS>& aRuns, const std::vector<T>& aElements,
91 const std::vector<std::pair<uint32_t, uint32_t>>& aDirty, size_t aCounted, bool aAll,
92 size_t aRunLength, COUNT aCount )
93{
94 const size_t size = aElements.size();
95 const size_t runs = ( size + aRunLength - 1 ) / aRunLength;
96
97 aRuns.resize( runs );
98
99 auto recount = [&]( size_t aRun )
100 {
101 const size_t first = aRun * aRunLength;
102
103 aRuns[aRun] = aCount( aElements.data() + first, std::min( aRunLength, size - first ) );
104 };
105
106 const size_t tail = aAll ? 0 : ( size == aCounted ? runs : std::min( aCounted, size ) / aRunLength );
107
108 for( size_t run = tail; run < runs; ++run )
109 recount( run );
110
111 for( const auto& [begin, spanEnd] : aDirty )
112 {
113 const size_t end = std::min<size_t>( spanEnd, size );
114
115 for( size_t run = begin / aRunLength; run < tail && run * aRunLength < end; ++run )
116 recount( run );
117 }
118}
119
120
121// Offset of a half-float depth from aBase, clamped to the range of a layer's depth slice
122static uint16_t relativeDepth( uint16_t aDepth, float aBase )
123{
124 const float limit = FRAME_DRAW_DESC::RELATIVE_DEPTH_RANGE - 1.0f;
125
126 return glm::packHalf1x16( std::clamp( glm::unpackHalf1x16( aDepth ) - aBase, -limit, limit ) );
127}
128
129
130template <typename POOL>
131static void hashPool( MMH3_HASH& aHash, const POOL& aPool, const std::vector<LAYER_RANGE>& aRanges )
132{
133 auto mixVector = [&]( const auto& aVector )
134 {
135 aHash.add( static_cast<int32_t>( aVector.size() ) );
136 hashBytes( aHash, aVector.data(), aVector.size() * sizeof( aVector[0] ) );
137 };
138
139 for( const std::vector<RHI_INSTANCE>& instances : aPool.instances )
140 mixVector( instances );
141
142 mixVector( aPool.polygonVertices );
143
144 for( const LAYER_RANGE& range : aRanges )
145 {
146 hashBytes( aHash, range.start.data(), sizeof( range.start ) );
147 hashBytes( aHash, range.count.data(), sizeof( range.count ) );
148 hashBytes( aHash, &range.alpha, sizeof( range.alpha ) );
149 hashBytes( aHash, &range.depth, sizeof( range.depth ) );
150 }
151}
152
153
154template <typename IT>
155static void drawPolylineSegments( GAL_MODERN& aGal, IT aBegin, IT aEnd )
156{
157 if( aBegin == aEnd )
158 return;
159
160 for( IT prev = aBegin, it = std::next( aBegin ); it != aEnd; ++prev, ++it )
161 aGal.DrawLine( *prev, *it );
162}
163
164
165// SHAPE_POLY_SET holds integer internal units
166template <typename IT>
167static SHAPE_LINE_CHAIN roundedOutline( IT aBegin, IT aEnd )
168{
169 SHAPE_LINE_CHAIN outline;
170
171 for( IT it = aBegin; it != aEnd; ++it )
172 outline.Append( KiROUND( *it ) );
173
174 return outline;
175}
176
177
179 GAL( aOptions ),
180 m_deviceProxy( aDeviceProxy ),
181 m_rhiInitialized( aDeviceProxy.IsInitialized() )
182{
183 // MATRIX3x3D constructs as zero, and VIEW caches groups before the first BeginDrawing
184 m_currentTransform.SetIdentity();
185
186 // The same default as the other canvases, which gerbview never overrides
188}
189
190
191std::shared_ptr<RENDER_THREAD> GAL_MODERN::AcquireRenderThread( bool aEnableValidation )
192{
193 static std::mutex s_mutex;
194 static std::weak_ptr<RENDER_THREAD> s_shared;
195
196 std::lock_guard<std::mutex> lock( s_mutex );
197
198 std::shared_ptr<RENDER_THREAD> thread = s_shared.lock();
199
200 if( thread && !thread->IsDeviceLost() )
201 return thread;
202
203 // The lost device goes before its replacement is made, or there would be two. Canvases still holding
204 // it find a stopped thread and rebuild onto the new one when they repaint.
205 if( thread )
206 thread->Shutdown();
207
208 thread = std::make_shared<RENDER_THREAD>(
209 [aEnableValidation]()
210 {
211 return RhiCreatePlatformDevice( aEnableValidation );
212 } );
213
214 if( !thread->Start() )
215 thread.reset();
216
217 s_shared = thread;
218 return thread;
219}
220
221
223{
224 static std::atomic<uint32_t> s_nextWindowId{ 1 };
225
226 return s_nextWindowId++;
227}
228
229
234
235
237{
238 // Destroyed now rather than retired, as nothing will draw them again
239 m_persistentTargets = false;
240
241 auto destroyPool = [&]( GEOMETRY_BUFFER_SET& bs )
242 {
243 for( RHI_BufferHandle buffer : bs.buffer )
244 {
245 if( buffer )
246 m_deviceProxy.DestroyBuffer( buffer );
247 }
248
249 releaseBitmapQuads( bs.bitmapQuads, true );
250 };
251
252 for( auto& bs : m_bufferSets )
253 destroyPool( bs );
254
255 for( auto& bs : m_overlaySets )
256 destroyPool( bs );
257
258 destroyPool( m_staticPool );
259 destroyPool( m_gridPool );
260
261 ReleaseRetiredBuffers( std::numeric_limits<uint64_t>::max() );
262
263 for( auto& [id, layer] : m_sceneLayers )
264 {
265 for( SCENE_ARENA<RHI_INSTANCE>& arena : layer.instances )
266 {
267 if( arena.buffer )
268 m_deviceProxy.DestroyBuffer( arena.buffer );
269 }
270
271 if( layer.polygons.buffer )
272 m_deviceProxy.DestroyBuffer( layer.polygons.buffer );
273 }
274
275 for( auto& [id, entry] : m_bitmapTextureCache )
276 {
277 if( entry.texture )
278 m_deviceProxy.DestroyTexture( entry.texture );
279 }
280
281 // A second call, from the destructor after the canvas tore down, then has nothing to destroy
283}
284
285
287{
288 m_isDrawing = true;
289 m_drawCursor = false;
290
291 m_currentTransform.SetIdentity();
293 m_transformStack.clear();
294
295 // Bitmap cache entries used from here on may back a submitted quad, so eviction only retires
296 // entries from earlier frames.
298
299 m_nonCachedRefilled = false;
300 m_overlayRefilled = false;
301
302 // Without persistent targets both dynamic pools are rebuilt every frame
304 {
307 }
308
309 // The static pool survives clean frames so Redraw() need not emit the cached layers again
310 if( m_staticDirty )
311 {
312 m_staticPool.ClearGeometry();
313 releaseBitmapQuads( m_staticPool.bitmapQuads, true );
314
315 // The rebuild uploads the whole pool, which covers any pending in-place patches
317 resetScene();
318
319 m_staticLayerRanges.clear();
320 }
321}
322
323
325{
326 const bool overlay = aTarget == TARGET_OVERLAY;
327
328 // The render thread may still be reading the previous set
329 if( overlay )
331 else
333
334 GEOMETRY_BUFFER_SET& pool = overlay ? obuf() : buf();
335 std::vector<LAYER_RANGE>& ranges = overlay ? m_overlayLayerRanges : m_dynamicLayerRanges;
336
337 pool.ClearGeometry();
338 releaseBitmapQuads( pool.bitmapQuads, true );
339 ranges.clear();
340
341 if( overlay )
342 {
343 m_overlayRefilled = true;
344 }
345 else
346 {
347 // The grid is drawn with the non-cached target
348 m_nonCachedRefilled = true;
349 m_drawGrid = false;
350 }
351
352 // A pool opened by the first draw into it has missed the range its layer depth opened
353 if( m_persistentTargets && m_isDrawing && m_currentTarget == aTarget )
354 beginLayerRange( static_cast<float>( m_layerDepth ) );
355}
356
357
359{
360 // Cleared first so a GPU-OOM throw from Flush() leaves no stale drawing state behind
361 m_isDrawing = false;
362 Flush();
363}
364
365
366void GAL_MODERN::ResizeScreen( int aWidth, int aHeight )
367{
368 m_screenWidth = aWidth;
369 m_screenHeight = aHeight;
370
371 SetScreenSize( VECTOR2I( aWidth, aHeight ) );
373}
374
375
377{
378 // Nobody keeps a descriptor across frames, so nothing can still be drawing from it
380 {
381 m_deviceProxy.DestroyBuffer( aBuffer );
382 return;
383 }
384
385 m_retiredBuffers.emplace_back( ++m_bufferGeneration, aBuffer );
386}
387
388
390{
392 {
393 m_deviceProxy.DestroyTexture( aTexture );
394 return;
395 }
396
397 m_retiredTextures.emplace_back( ++m_bufferGeneration, aTexture );
398}
399
400
401void GAL_MODERN::releaseBitmapQuads( std::vector<BITMAP_QUAD>& aQuads, bool aOwnedOnly )
402{
403 for( const BITMAP_QUAD& bq : aQuads )
404 {
405 if( aOwnedOnly && !bq.ownsResources )
406 continue;
407
408 if( bq.ownsTexture && bq.texture )
409 retireTexture( bq.texture );
410
411 if( bq.vertexBuffer )
412 retireBuffer( bq.vertexBuffer );
413 }
414
415 aQuads.clear();
416}
417
418
419void GAL_MODERN::ReleaseRetiredBuffers( uint64_t aConsumedGeneration )
420{
421 std::erase_if( m_retiredBuffers,
422 [&]( const std::pair<uint64_t, RHI_BufferHandle>& aRetired )
423 {
424 if( aRetired.first > aConsumedGeneration )
425 return false;
426
427 m_deviceProxy.DestroyBuffer( aRetired.second );
428 return true;
429 } );
430
431 std::erase_if( m_retiredTextures,
432 [&]( const std::pair<uint64_t, RHI_TextureHandle>& aRetired )
433 {
434 if( aRetired.first > aConsumedGeneration )
435 return false;
436
437 m_deviceProxy.DestroyTexture( aRetired.second );
438 return true;
439 } );
440}
441
442
443bool GAL_MODERN::ensureBuffer( RHI_BufferHandle& aBuffer, size_t& aCapacity,
444 size_t aNeeded, size_t aStride )
445{
446 if( aNeeded == 0 )
447 return true;
448
449 if( aCapacity < aNeeded )
450 {
451 size_t newCapacity = std::max( aNeeded, aCapacity * 2 );
452
453 // A byte size overflow is an allocation failure, so the caller escalates
454 if( aStride != 0 && newCapacity > std::numeric_limits<size_t>::max() / aStride )
455 return false;
456
457 RHI_BUFFER_DESC desc;
459 desc.sizeBytes = newCapacity * aStride;
460
461 RHI_BufferHandle newBuffer = m_deviceProxy.CreateBuffer( desc );
462
463 // Keep the old buffer so a later frame can retry, and let the caller escalate to the GPU-OOM
464 // fallback
465 if( !newBuffer )
466 return false;
467
468 if( aBuffer )
469 retireBuffer( aBuffer );
470
471 aBuffer = newBuffer;
472 aCapacity = newCapacity;
473 }
474
475 return true;
476}
477
478
480{
481 auto partitionRange = []( std::vector<RHI_INSTANCE>& aInstances, uint32_t aStart, uint32_t aEnd,
482 uint32_t& aNormalCount, uint32_t& aNegStart, uint32_t& aNegCount )
483 {
484 auto rangeBegin = aInstances.begin() + aStart;
485 auto rangeEnd = aInstances.begin() + aEnd;
486
487 auto it = std::stable_partition( rangeBegin, rangeEnd,
488 []( const RHI_INSTANCE& inst )
489 {
490 return !inst.HasFlag( RHI_INSTANCE_NEGATIVE );
491 } );
492
493 aNormalCount = static_cast<uint32_t>( std::distance( rangeBegin, it ) );
494 aNegStart = aStart + aNormalCount;
495 aNegCount = static_cast<uint32_t>( std::distance( it, rangeEnd ) );
496 };
497
498 auto processPool = [&]( GEOMETRY_BUFFER_SET& aPool, std::vector<LAYER_RANGE>& aRanges )
499 {
500 // A kept target is drawn the way the retained scene is, each range in its own slice of
501 // the depth range with its elements' depth relative to the range
502 const bool sliced = keepsRelativeDepth( aPool );
503 const RHI_SHAPE_COUNTS sizes = aPool.Sizes();
504
505 // Callers that never set a layer depth leave no ranges, which would drop every instance
506 if( aRanges.empty() && !aPool.Empty() )
507 {
508 LAYER_RANGE synth;
509 synth.target = m_currentTarget;
510 aRanges.push_back( synth );
511 }
512
513 aPool.polarityRuns.clear();
514
515 size_t mark = 0;
516 bool negative = false;
517
518 for( size_t i = 0; i < aRanges.size(); ++i )
519 {
520 LAYER_RANGE& lr = aRanges[i];
521 const bool last = i + 1 == aRanges.size();
522 RHI_SHAPE_COUNTS end = last ? sizes : aRanges[i + 1].start;
523 const uint32_t bmpEnd = last ? static_cast<uint32_t>( aPool.bitmapQuads.size() )
524 : aRanges[i + 1].bitmapStart;
525
526 // A range that changed polarity keeps its drawing order and is drawn in runs, each
527 // clear run erasing what came before it and each dark run drawing over what was cleared
528 const size_t firstMark = mark;
529
530 while( mark < aPool.polarityMarks.size() && aPool.polarityMarks[mark].range <= static_cast<int>( i ) )
531 mark++;
532
533 lr.runStart = static_cast<uint32_t>( aPool.polarityRuns.size() );
534 lr.runCount = 0;
535
536 if( negative || mark > firstMark )
537 {
538 POLARITY_RUN run;
539 run.negative = negative;
540 run.start = lr.start;
541
542 auto closeRun = [&]( const RHI_SHAPE_COUNTS& aAt )
543 {
544 for( int shape = 0; shape < RHI_SHAPES; ++shape )
545 run.count[shape] = std::min( aAt[shape], end[shape] ) - run.start[shape];
546
547 if( !run.Empty() )
548 aPool.polarityRuns.push_back( run );
549
550 for( int shape = 0; shape < RHI_SHAPES; ++shape )
551 run.start[shape] += run.count[shape];
552 };
553
554 for( size_t m = firstMark; m < mark; ++m )
555 {
556 const POLARITY_MARK& change = aPool.polarityMarks[m];
557
558 closeRun( change.at );
559 negative = change.negative;
560 run.negative = negative;
561 }
562
563 closeRun( end );
564 lr.runCount = static_cast<uint32_t>( aPool.polarityRuns.size() ) - lr.runStart;
565
566 // Order matters once something dark follows a knockout. Polygons need the runs
567 // too, a clear one is told from a dark one only by the run it is in.
568 bool ordered = false;
569 bool cleared = false;
570
571 for( uint32_t r = lr.runStart; r < lr.runStart + lr.runCount; ++r )
572 {
573 const POLARITY_RUN& made = aPool.polarityRuns[r];
574
575 if( made.negative )
576 ordered = ordered || made.count[RHI_SHAPE_POLYGON] > 0;
577 else
578 ordered = ordered || cleared;
579
580 cleared = cleared || made.negative;
581 }
582
583 if( !ordered )
584 {
585 aPool.polarityRuns.resize( lr.runStart );
586 lr.runCount = 0;
587 }
588 }
589
590 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
591 {
592 if( lr.runCount > 0 )
593 {
594 lr.count[shape] = end[shape] - lr.start[shape];
595 lr.negStart[shape] = end[shape];
596 lr.negCount[shape] = 0;
597 }
598 else
599 {
600 partitionRange( aPool.instances[shape], lr.start[shape], end[shape], lr.count[shape],
601 lr.negStart[shape], lr.negCount[shape] );
602 }
603 }
604
605 // A cached group can bring knockouts into a range that never set the negative mode itself
606 lr.hasNegatives = lr.hasNegatives || lr.runCount > 0
607 || std::ranges::any_of( lr.negCount, []( uint32_t aCount ) { return aCount > 0; } );
609 lr.bitmapCount = bmpEnd - lr.bitmapStart;
610
611 // Alpha shared by a whole range moves to the range, with its elements made opaque, so the
612 // composite applies it once. Mixed alpha stays per element.
613 uint8_t minAlpha = 255;
614 uint8_t maxAlpha = 0;
615 bool hasAny = false;
616
617 auto scan = [&]( const auto& aElements, uint32_t aStart, uint32_t aCount )
618 {
619 for( uint32_t j = aStart; j < aStart + aCount; ++j )
620 {
621 minAlpha = std::min( minAlpha, aElements[j].a );
622 maxAlpha = std::max( maxAlpha, aElements[j].a );
623 hasAny = true;
624 }
625 };
626
627 auto scanSpans = [&]( const RHI_SHAPE_COUNTS& aStart, const RHI_SHAPE_COUNTS& aCount )
628 {
629 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
630 scan( aPool.instances[shape], aStart[shape], aCount[shape] );
631
632 scan( aPool.polygonVertices, aStart[RHI_SHAPE_POLYGON], aCount[RHI_SHAPE_POLYGON] );
633 };
634
635 // A knockout covers fully whatever the layer's alpha, so only what is drawn counts
636 if( lr.runCount > 0 )
637 {
638 for( uint32_t r = lr.runStart; r < lr.runStart + lr.runCount; ++r )
639 {
640 if( !aPool.polarityRuns[r].negative )
641 scanSpans( aPool.polarityRuns[r].start, aPool.polarityRuns[r].count );
642 }
643 }
644 else
645 {
646 scanSpans( lr.start, lr.count );
647 }
648
649 if( sliced )
650 {
651 // A translucent pixel takes a layer once, however many of its elements overlap there
652 lr.alpha = 1.0f;
653 lr.singleCoverage = hasAny && minAlpha < 255;
654 lr.depthKnockout = lr.hasNegatives && lr.runCount == 0;
655
656 const bool hard = lr.singleCoverage || lr.depthKnockout;
657
658 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
659 {
660 for( uint32_t j = lr.start[shape]; j < end[shape]; ++j )
661 {
662 RHI_INSTANCE& inst = aPool.instances[shape][j];
663
664 inst.depth = relativeDepth( inst.depth, lr.depth );
665
666 if( hard )
668 }
669 }
670
671 for( uint32_t j = lr.start[RHI_SHAPE_POLYGON]; j < end[RHI_SHAPE_POLYGON]; ++j )
672 aPool.polygonVertices[j].u = relativeDepth( aPool.polygonVertices[j].u, lr.depth );
673 }
674 else if( hasAny && minAlpha == maxAlpha && minAlpha < 255 )
675 {
676 lr.alpha = static_cast<float>( minAlpha ) / 255.0f;
677
678 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
679 {
680 for( uint32_t j = lr.start[shape]; j < end[shape]; ++j )
681 aPool.instances[shape][j].a = 255;
682 }
683
684 for( uint32_t j = lr.start[RHI_SHAPE_POLYGON]; j < end[RHI_SHAPE_POLYGON]; ++j )
685 aPool.polygonVertices[j].a = 255;
686 }
687 else
688 {
689 lr.alpha = 1.0f;
690 }
691 }
692
693 aPool.lastCount = {};
694 aPool.lastNegCount = {};
695
696 for( const LAYER_RANGE& lr : aRanges )
697 {
698 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
699 {
700 aPool.lastCount[shape] += lr.count[shape];
701 aPool.lastNegCount[shape] += lr.negCount[shape];
702 }
703 }
704
706 };
707
708 // Content that rides the camera path through a zoom is culled against each frame's view
709 auto makeCullRuns = []( GEOMETRY_BUFFER_SET& aPool )
710 {
711 auto table = std::make_shared<RHI_RUN_TABLE>();
712
713 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
714 {
715 const std::vector<RHI_INSTANCE>& instances = aPool.instances[shape];
716
717 updateRuns( table->runs[shape], instances, {}, 0, true, RHI_CULL_RUN_LENGTH,
718 [shape]( const RHI_INSTANCE* aFirst, size_t aCount )
719 {
720 return RhiInstanceRun( aFirst, aCount, RHI_SHAPE( shape ) );
721 } );
722
723 // A pool's negatives sit in the same arrays as the rest
724 table->negRuns[shape] = table->runs[shape];
725 }
726
728 &RhiVertexRun );
729
730 aPool.cullRuns = std::move( table );
731 };
732
733 // A dynamic pool that was not refilled keeps the ranges, counts and buffers of its last frame
735 {
736 processPool( buf(), m_dynamicLayerRanges );
737 makeCullRuns( buf() );
738 }
739
741 {
742 processPool( obuf(), m_overlayLayerRanges );
743 makeCullRuns( obuf() );
744 }
745
746 // Likewise the static pool on clean frames
747 if( m_staticDirty )
748 processPool( m_staticPool, m_staticLayerRanges );
749
750 // The grid pool is drawn again on every DrawGrid, but takes a static redraw only when it changed
751 bool gridChanged = false;
752
754 {
755 processPool( m_gridPool, m_gridLayerRanges );
756 makeCullRuns( m_gridPool );
757
758 MMH3_HASH hash;
759
761
762 const std::array<uint64_t, 2> digest = hashDigest( hash );
763
764 gridChanged = digest != m_gridPoolHash;
765 m_gridPoolHash = digest;
766 m_gridPoolRefilled = false;
767 }
768
769 bool allocationFailed = !flushScene();
770
771 if( !m_renderThread )
772 {
774 m_staticDirty = false;
775 m_sceneChanged = false;
776 return;
777 }
778
779 // Adopting moves the elements in without a copy, which suits only pools refilled before they are
780 // read again. The static pool is copied as it is kept for patching.
781 auto uploadBatch = [&]( auto& aElements, RHI_BufferHandle& aBuffer, size_t& aCapacity, bool aAdopt )
782 {
783 if( aElements.empty() )
784 return;
785
786 using ElemType = typename std::decay_t<decltype( aElements )>::value_type;
787 size_t count = aElements.size();
788
789 if( !ensureBuffer( aBuffer, aCapacity, count, sizeof( ElemType ) ) || !aBuffer )
790 {
791 allocationFailed = true;
792 return;
793 }
794
795 RHI_GEOMETRY_UPLOAD upload;
796
797 if( aAdopt )
798 RhiUploadAdoptStaging( upload, std::move( aElements ) );
799 else
800 RhiUploadCopyStaging( upload, aElements.data(), count * sizeof( ElemType ) );
801
802 upload.targetBuffer = aBuffer;
803 upload.targetOffset = 0;
804 m_renderThread->SubmitUpload( std::move( upload ) );
805 };
806
807 auto uploadPool = [&]( GEOMETRY_BUFFER_SET& aPool, bool aAdopt )
808 {
809 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
810 uploadBatch( aPool.instances[shape], aPool.buffer[shape], aPool.capacity[shape], aAdopt );
811
812 uploadBatch( aPool.polygonVertices, aPool.buffer[RHI_SHAPE_POLYGON], aPool.capacity[RHI_SHAPE_POLYGON],
813 aAdopt );
814 };
815
817 uploadPool( buf(), true );
818
820 uploadPool( obuf(), true );
821
822 if( gridChanged )
823 uploadPool( m_gridPool, true );
824
825 if( m_staticDirty )
826 uploadPool( m_staticPool, false );
827
828 // A clean frame uploads only the spans that in-place patches touched
829 bool patchedStatic = false;
830
831 if( !m_staticDirty )
832 {
833 auto uploadPatch = [&]( const auto& aElements, RHI_BufferHandle aBuffer, PATCH_SPAN& aSpan )
834 {
835 if( aSpan.empty() )
836 return;
837
838 // A span that no longer fits the pool cannot be uploaded, so rebuild rather than drop it
839 if( !aBuffer || aSpan.end > aElements.size() )
840 {
841 aSpan.clear();
842 m_staticDirty = true;
843 return;
844 }
845
846 submitSpan( aElements, aBuffer, aSpan );
847 patchedStatic = true;
848 aSpan.clear();
849 };
850
851 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
852 uploadPatch( m_staticPool.instances[shape], m_staticPool.buffer[shape], m_staticPatches[shape] );
853
854 uploadPatch( m_staticPool.polygonVertices, m_staticPool.buffer[RHI_SHAPE_POLYGON],
856 }
857
858 // A patch needs the static target drawn again just as a rebuild does
859 m_staticRebuiltThisFlush = m_staticDirty || patchedStatic || m_sceneChanged || gridChanged;
860 m_staticDirty = false;
861 m_sceneChanged = false;
862
863 // GPU-OOM makes the draw panel leave this canvas. EndDrawing() runs from a destructor, where a
864 // second exception during unwinding would call std::terminate.
865 if( allocationFailed && std::uncaught_exceptions() == 0 )
866 throw GPU_OOM_ERROR( "RHI geometry buffer allocation failed (GPU out of memory)" );
867}
868
869
871{
872 for( GEOMETRY_BUFFER_SET* pool : { &buf(), &obuf(), &m_staticPool, &m_gridPool } )
873 {
874 pool->ClearGeometry();
875 releaseBitmapQuads( pool->bitmapQuads, true );
876 }
877
878 m_staticLayerRanges.clear();
879 m_dynamicLayerRanges.clear();
880 m_overlayLayerRanges.clear();
881 m_gridLayerRanges.clear();
882 m_gridPoolRefilled = true;
883
884 // The static pool is empty, so its group slots and pending patches are meaningless
886 resetScene();
887
888 m_staticDirty = true;
889
890 m_drawGrid = false;
891 m_drawCursor = false;
892}
893
894
896{
897 // A range belongs to one target, so a target change opens a range even at the same depth. The
898 // depth carries across the switch.
899 if( m_currentTarget != aTarget )
900 {
901 float depth = 0.0f;
902
903 if( !m_staticLayerRanges.empty() )
904 depth = m_staticLayerRanges.back().depth;
905
906 if( !m_dynamicLayerRanges.empty() )
907 depth = m_dynamicLayerRanges.back().depth;
908
910 depth = m_overlayLayerRanges.back().depth;
911
912 m_currentTarget = aTarget;
913
914 if( !m_staticLayerRanges.empty() || !m_dynamicLayerRanges.empty() || !m_overlayLayerRanges.empty() )
915 beginLayerRange( depth );
916 }
917}
918
919
920void GAL_MODERN::SetLayerDepth( double aLayerDepth )
921{
922 GAL::SetLayerDepth( aLayerDepth );
923
924 float depth = static_cast<float>( aLayerDepth );
925 const std::vector<LAYER_RANGE>& ranges = layerRangesForCurrentTarget();
926
927 if( ranges.empty() || ranges.back().depth != depth )
928 beginLayerRange( depth );
929}
930
931
932void GAL_MODERN::beginLayerRange( float aDepth )
933{
934 // VIEW sets target and depth before caching a group outside a frame. A range opened then holds no
935 // geometry and would pile up, as the pools are not rebuilt for every changed item.
936 if( m_isUpdating && !m_isDrawing )
937 return;
938
939 // A kept target has no use for a range until something draws into it, which opens one
941 return;
942
943 const GEOMETRY_BUFFER_SET& pool = std::as_const( *this ).poolForCurrentTarget();
944
945 LAYER_RANGE range;
946 range.depth = aDepth;
947 range.target = m_currentTarget;
948 range.start = pool.Sizes();
949 range.bitmapStart = static_cast<uint32_t>( pool.bitmapQuads.size() );
950 range.difference = m_diffLayerActive;
951
952 layerRangesForCurrentTarget().push_back( range );
953}
954
955
960
961
963{
965 return;
966
967 if( aTarget == TARGET_OVERLAY && !m_overlayRefilled )
969 else if( aTarget == TARGET_NONCACHED && !m_nonCachedRefilled )
971}
972
973
975{
976 // Only the non-cached and overlay ranges carry this. A retained scene layer is not differenced,
977 // which is why gerbview moves its diff layers off the cached target.
978 m_diffLayerActive = true;
979
980 // A target not yet opened gets the flag with the range its first draw opens
982 return;
983
984 std::vector<LAYER_RANGE>& ranges = layerRangesForCurrentTarget();
985 const GEOMETRY_BUFFER_SET& pool = std::as_const( *this ).poolForCurrentTarget();
986
987 // The range SetLayerDepth opened for this layer holds nothing yet
988 const bool fresh = !ranges.empty() && ranges.back().start == pool.Sizes()
989 && ranges.back().bitmapStart == pool.bitmapQuads.size();
990
991 if( fresh )
992 ranges.back().difference = true;
993 else
994 beginLayerRange( static_cast<float>( m_layerDepth ) );
995}
996
997
999{
1000 m_diffLayerActive = false;
1001
1002 if( m_isDrawing && !currentTargetOpen() )
1003 return;
1004
1005 // Whatever is drawn next at the same depth is not part of the diff layer
1006 const std::vector<LAYER_RANGE>& ranges = layerRangesForCurrentTarget();
1007
1008 if( !ranges.empty() && ranges.back().difference )
1009 beginLayerRange( static_cast<float>( m_layerDepth ) );
1010}
1011
1012
1014{
1015 m_negativeMode = true;
1016 m_negativesLayer = true;
1017
1018 std::vector<LAYER_RANGE>& ranges = layerRangesForCurrentTarget();
1019
1020 if( !ranges.empty() )
1021 ranges.back().hasNegatives = true;
1022}
1023
1024
1026{
1027 m_negativeMode = false;
1028 m_negativesLayer = false;
1029}
1030
1031
1032void GAL_MODERN::notePolarity( bool aNegative )
1033{
1034 // A group being recorded keeps its polarity, and the pool learns it when the group is drawn
1035 if( m_currentGroupId >= 0 )
1036 {
1037 m_groupNegative = m_groupNegative || aNegative;
1038 return;
1039 }
1040
1042
1043 if( pool.drawsNegative == aNegative )
1044 return;
1045
1046 pool.drawsNegative = aNegative;
1047 pool.polarityMarks.push_back(
1048 { aNegative, static_cast<int>( layerRangesForCurrentTarget().size() ) - 1, pool.Sizes() } );
1049}
1050
1051
1053{
1054 m_negativeMode = aSetting;
1055
1056 std::vector<LAYER_RANGE>& ranges = layerRangesForCurrentTarget();
1057
1058 if( aSetting && !ranges.empty() )
1059 ranges.back().hasNegatives = true;
1060}
1061
1062
1064{
1066
1067 // The grid pass also draws the axes, which the other canvases show without the grid
1068 if( !gridShown() && !m_axesEnabled )
1069 return;
1070
1071 m_drawGrid = true;
1072}
1073
1074
1076{
1077 const VECTOR2D pixelScale = m_screenWorldMatrix.GetScale();
1078
1079 return std::min( std::abs( pixelScale.x ), std::abs( pixelScale.y ) );
1080}
1081
1082
1084{
1085 m_gridPool.ClearGeometry();
1086 m_gridLayerRanges.clear();
1087 m_gridPoolRefilled = true;
1088
1089 if( m_gridSources.empty() )
1090 return;
1091
1092 const RENDER_TARGET target = m_currentTarget;
1093 const double depth = m_layerDepth;
1094
1096 m_drawingGridSources = true;
1097
1098 {
1100
1101 // Each bounded grid clears its area of the grids under it, so painting from last to first in
1102 // precedence leaves the first on top. A grid being edited goes over the others without hiding them.
1103 for( auto it = m_gridSources.rbegin(); it != m_gridSources.rend(); ++it )
1104 {
1105 if( !it->unbounded && !it->highlighted )
1106 drawGridSource( *it );
1107 }
1108
1109 for( auto it = m_gridSources.rbegin(); it != m_gridSources.rend(); ++it )
1110 {
1111 if( !it->unbounded && it->highlighted )
1112 drawGridSource( *it );
1113 }
1114 }
1115
1116 m_drawingGridSources = false;
1117 m_currentTarget = target;
1118 GAL::SetLayerDepth( depth );
1119}
1120
1121
1123{
1124 const COLOR4D color = gridSourceColor( aSrc );
1125 const GRID_LATTICE lattice = gridLattice( aSrc );
1126 COLOR4D wash = m_clearColor;
1127
1128 if( aSrc.highlighted )
1129 wash.a = GRID_DIM_ALPHA;
1130
1131 const double pixel = screenPixelSize();
1132 const double minorWidth = gridMinorLineWidth( pixel, m_dpiScale );
1133 const double majorWidth = 2.0 * minorWidth;
1134
1135 // The static target draws a range kind by kind, so each step takes its own range to paint over the
1136 // last. Their slice puts them all at the far end of the depth range.
1137 auto nextStep = [&]()
1138 {
1139 GAL::SetLayerDepth( 0.0 );
1140 beginLayerRange( 0.0f );
1141 };
1142
1143 nextStep();
1144 SetIsFill( true );
1145 SetIsStroke( false );
1146 SetFillColor( wash );
1147 drawGridCoverageShape( aSrc );
1148
1149 // Only what a pan within one cell could show, so a huge grid costs no more than the view and the
1150 // geometry stays the same while the view moves within a cell.
1151 const BOX2D view = gridScreenBBox( aSrc );
1152 const double cell = std::max( view.GetWidth(), view.GetHeight() ) / 2.0;
1153 const double left = ( std::floor( view.GetLeft() / cell ) - 1.0 ) * cell;
1154 const double top = ( std::floor( view.GetTop() / cell ) - 1.0 ) * cell;
1155 const double right = ( std::ceil( view.GetRight() / cell ) + 1.0 ) * cell;
1156 const double bottom = ( std::ceil( view.GetBottom() / cell ) + 1.0 ) * cell;
1157 const BOX2D reach( VECTOR2D( left, top ), VECTOR2D( right - left, bottom - top ) );
1158
1159 auto dotAt = [&]( const VECTOR2D& aPos, bool aMajorX, bool aMajorY )
1160 {
1161 if( !reach.Contains( aPos ) )
1162 return;
1163
1164 const VECTOR2D half( ( aMajorX ? majorWidth : minorWidth ) / 2.0, ( aMajorY ? majorWidth : minorWidth ) / 2.0 );
1165 DrawRectangle( aPos - half, aPos + half );
1166 };
1167
1168 auto crossAt = [&]( const VECTOR2D& aPos, bool aMajor, double aArmAngle )
1169 {
1170 if( !reach.Contains( aPos ) )
1171 return;
1172
1173 const double width = aMajor ? majorWidth : minorWidth;
1174 const VECTOR2D arm1( std::cos( aArmAngle ) * 2.0 * width, std::sin( aArmAngle ) * 2.0 * width );
1175 const VECTOR2D arm2( -arm1.y, arm1.x );
1176
1177 SetLineWidth( width );
1178 DrawLine( aPos - arm1, aPos + arm1 );
1179 DrawLine( aPos - arm2, aPos + arm2 );
1180 };
1181
1182 nextStep();
1183 SetIsFill( aSrc.style == GRID_STYLE::DOTS );
1185 SetFillColor( color );
1186 SetStrokeColor( color );
1187
1188 Save();
1189 Translate( aSrc.origin );
1190 Rotate( -aSrc.orientation );
1191
1192 if( aSrc.kind == GRID_SOURCE::KIND::POLAR && aSrc.extent.x > 0.0 && aSrc.extent.y > 0.0 )
1193 {
1194 const double rMax = aSrc.extent.x;
1195 const double phiMax = aSrc.extent.y;
1196 const double dr = lattice.pitch.x;
1197 const double dPhi = lattice.pitch.y;
1198
1199 if( aSrc.style == GRID_STYLE::LINES )
1200 {
1201 int rIdx = 1;
1202
1203 for( double r = dr; r <= rMax + 1e-6; r += dr, ++rIdx )
1204 {
1205 SetLineWidth( lattice.IsMajor( rIdx ) ? majorWidth : minorWidth );
1206 DrawArc( VECTOR2D( 0, 0 ), r, ANGLE_0, EDA_ANGLE( phiMax, RADIANS_T ) );
1207 }
1208
1209 int pIdx = 0;
1210
1211 for( double phi = 0; phi <= phiMax + 1e-6; phi += dPhi, ++pIdx )
1212 {
1213 SetLineWidth( lattice.IsMajor( pIdx ) ? majorWidth : minorWidth );
1214 DrawLine( VECTOR2D( 0, 0 ), VECTOR2D( rMax * std::cos( phi ), rMax * std::sin( phi ) ) );
1215 }
1216 }
1217 else
1218 {
1219 int rIdx = 0;
1220
1221 for( double r = 0; r <= rMax + 1e-6; r += dr, ++rIdx )
1222 {
1223 int pIdx = 0;
1224
1225 for( double phi = 0; phi <= phiMax + 1e-6; phi += dPhi, ++pIdx )
1226 {
1227 const VECTOR2D pos( r * std::cos( phi ), r * std::sin( phi ) );
1228
1229 if( aSrc.style == GRID_STYLE::DOTS )
1230 dotAt( pos, lattice.IsMajor( rIdx ), lattice.IsMajor( pIdx ) );
1231 else
1232 crossAt( pos, lattice.IsMajor( rIdx ) && lattice.IsMajor( pIdx ), phi );
1233 }
1234 }
1235 }
1236 }
1237 else if( aSrc.kind == GRID_SOURCE::KIND::CARTESIAN )
1238 {
1239 const double dx = lattice.pitch.x;
1240 const double dy = lattice.pitch.y;
1241
1242 const double xMin = std::max( reach.GetLeft(), -aSrc.extent.x );
1243 const double xMax = std::min( reach.GetRight(), aSrc.extent.x );
1244 const double yMin = std::max( reach.GetTop(), -aSrc.extent.y );
1245 const double yMax = std::min( reach.GetBottom(), aSrc.extent.y );
1246
1247 const int ixMin = static_cast<int>( std::ceil( xMin / dx ) );
1248 const int ixMax = static_cast<int>( std::floor( xMax / dx ) );
1249 const int iyMin = static_cast<int>( std::ceil( yMin / dy ) );
1250 const int iyMax = static_cast<int>( std::floor( yMax / dy ) );
1251
1252 if( aSrc.style == GRID_STYLE::LINES )
1253 {
1254 for( int ix = ixMin; ix <= ixMax; ++ix )
1255 {
1256 SetLineWidth( lattice.IsMajor( ix ) ? majorWidth : minorWidth );
1257 DrawLine( VECTOR2D( ix * dx, yMin ), VECTOR2D( ix * dx, yMax ) );
1258 }
1259
1260 for( int iy = iyMin; iy <= iyMax; ++iy )
1261 {
1262 SetLineWidth( lattice.IsMajor( iy ) ? majorWidth : minorWidth );
1263 DrawLine( VECTOR2D( xMin, iy * dy ), VECTOR2D( xMax, iy * dy ) );
1264 }
1265 }
1266 else
1267 {
1268 for( int ix = ixMin; ix <= ixMax; ++ix )
1269 {
1270 for( int iy = iyMin; iy <= iyMax; ++iy )
1271 {
1272 if( aSrc.style == GRID_STYLE::DOTS )
1273 dotAt( VECTOR2D( ix * dx, iy * dy ), lattice.IsMajor( ix ), lattice.IsMajor( iy ) );
1274 else
1275 crossAt( VECTOR2D( ix * dx, iy * dy ), lattice.IsMajor( ix ) && lattice.IsMajor( iy ), 0.0 );
1276 }
1277 }
1278 }
1279 }
1280
1281 Restore();
1282
1283 // Outlined, so that a grid too sparse to show still reads as an area
1284 nextStep();
1285 SetIsFill( false );
1286 SetIsStroke( true );
1288 SetLineWidth( pixel / m_dpiScale );
1289 drawGridCoverageShape( aSrc );
1290}
1291
1292
1293void GAL_MODERN::DrawCursor( const VECTOR2D& aCursorPosition )
1294{
1295 m_cursorPosition = aCursorPosition;
1296 m_drawCursor = true;
1297}
1298
1299
1300void GAL_MODERN::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1301{
1302 addCapsuleInstance( aStartPoint, aEndPoint, m_lineWidth, m_strokeColor );
1303}
1304
1305
1306void GAL_MODERN::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
1307 double aWidth )
1308{
1309 if( m_isFillEnabled || aWidth == 1.0 )
1310 {
1311 addCapsuleInstance( aStartPoint, aEndPoint, aWidth, m_fillColor );
1312 return;
1313 }
1314
1315 // The OpenGL canvas compares in float, and draws a segment of no length as a circle
1316 if( static_cast<float>( aStartPoint.x ) == static_cast<float>( aEndPoint.x )
1317 && static_cast<float>( aStartPoint.y ) == static_cast<float>( aEndPoint.y ) )
1318 {
1319 DrawCircle( aStartPoint, aWidth / 2.0 );
1320 return;
1321 }
1322
1323 // Outlined as the other canvases do it, two hairline sides and a semicircle at each end
1324 const double halfWidth = aWidth / 2.0;
1325 const VECTOR2D delta = aEndPoint - aStartPoint;
1326 const double angle = std::atan2( delta.y, delta.x );
1327 const VECTOR2D side = VECTOR2D( -delta.y, delta.x ).Resize( halfWidth );
1328
1329 addCapsuleInstance( aStartPoint + side, aEndPoint + side, 1.0, m_strokeColor );
1330 addCapsuleInstance( aStartPoint - side, aEndPoint - side, 1.0, m_strokeColor );
1331 addArcInstance( aStartPoint, halfWidth, angle + M_PI / 2.0, M_PI, 1.0, m_strokeColor );
1332 addArcInstance( aEndPoint, halfWidth, angle - M_PI / 2.0, M_PI, 1.0, m_strokeColor );
1333}
1334
1335
1336void GAL_MODERN::DrawTrack( const VECTOR2D& aStart, const VECTOR2D& aEnd,
1337 double aWidth, const COLOR4D& aColor )
1338{
1339 addCapsuleInstance( aStart, aEnd, aWidth, aColor );
1340}
1341
1342
1343void GAL_MODERN::DrawPad( const VECTOR2D& aCenter, double aHalfWidth, double aHalfHeight,
1344 double aCornerRadius, float aShapeType, double aRotation,
1345 const COLOR4D& aColor )
1346{
1347 addPadInstance( aCenter, aHalfWidth, aHalfHeight, aCornerRadius, aShapeType, aRotation,
1348 aColor );
1349}
1350
1351
1352void GAL_MODERN::DrawSegmentChain( const std::vector<VECTOR2D>& aPointList, double aWidth )
1353{
1354 for( size_t i = 1; i < aPointList.size(); ++i )
1355 {
1356 DrawSegment( aPointList[i - 1], aPointList[i], aWidth );
1357 }
1358}
1359
1360
1361void GAL_MODERN::DrawSegmentChain( const SHAPE_LINE_CHAIN& aLineChain, double aWidth )
1362{
1363 // A closed chain of one point counts one segment, from the point to itself
1364 if( aLineChain.PointCount() < 2 )
1365 return;
1366
1367 for( int i = 0; i < aLineChain.SegmentCount(); ++i )
1368 {
1369 const SEG seg = aLineChain.CSegment( i );
1370 DrawSegment( VECTOR2D( seg.A ), VECTOR2D( seg.B ), aWidth );
1371 }
1372}
1373
1374
1375void GAL_MODERN::addCapsuleInstance( const VECTOR2D& aStart, const VECTOR2D& aEnd,
1376 double aWidth, const COLOR4D& aColor )
1377{
1378 VECTOR2D start = xform( aStart );
1379 VECTOR2D end = xform( aEnd );
1380 double scale = xformScale();
1381
1382 VECTOR2D delta = end - start;
1383 double length = delta.EuclideanNorm();
1384 double halfWidth = ( aWidth * scale ) / 2.0;
1385 double halfLength = length / 2.0;
1386 double rotation = std::atan2( delta.y, delta.x );
1387
1388 // Screen-space elements keep their pixel size across zoom, as the vertex shader rebuilds world
1389 // sizes from the live projection
1390 if( m_screenSpaceMode && m_worldScale > 0 )
1391 {
1392 halfWidth *= m_worldScale;
1393 halfLength *= m_worldScale;
1394 }
1395
1396 VECTOR2D midpoint( ( start.x + end.x ) / 2.0, ( start.y + end.y ) / 2.0 );
1397
1398 RHI_INSTANCE inst = makeInstance( aColor );
1399 inst.SetPosition( static_cast<float>( midpoint.x ), static_cast<float>( midpoint.y ) );
1400 inst.SetRotation( static_cast<float>( rotation ) );
1401 inst.SetParam( 0, static_cast<float>( halfWidth ) );
1402 inst.SetParam( 1, static_cast<float>( halfLength ) );
1403
1404 if( m_screenSpaceMode )
1406
1407 if( m_bitmapTextHeight > 0 )
1408 {
1410 inst.SetParam( 2, static_cast<float>( m_bitmapTextHeight * scale ) );
1411 }
1412 else if( m_strokeLengthPad > 0.0f )
1413 {
1415 inst.SetParam( 2, m_strokeLengthPad );
1416 }
1417
1418 if( m_strokeLod > 0.0 )
1419 {
1421 inst.SetParam( 3, static_cast<float>( m_strokeLod ) );
1422 }
1423
1425}
1426
1427
1429{
1430 RHI_INSTANCE inst;
1431 inst.SetColor( packColorChannel( aColor.r ), packColorChannel( aColor.g ), packColorChannel( aColor.b ),
1432 packColorChannel( aColor.a ) );
1433 inst.SetDepth( static_cast<float>( m_layerDepth ) );
1434
1435 // A knockout erases by coverage, whatever colour its item is painted in
1436 if( m_negativeMode )
1437 {
1438 inst.a = 255;
1440 }
1441
1444
1447
1448 return inst;
1449}
1450
1451
1452void GAL_MODERN::DrawArc( const VECTOR2D& aCenterPoint, double aRadius,
1453 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle )
1454{
1455 if( aRadius <= 0 )
1456 return;
1457
1458 if( m_isFillEnabled )
1459 {
1460 // The arc shader's rounded end caps would bulge a sector into a lens, so fill it as a polygon
1461 double startAngle = aStartAngle.AsRadians();
1462 double endAngle = startAngle + aAngle.AsRadians();
1463 normalize( startAngle, endAngle );
1464
1465 const double sweep = endAngle - startAngle;
1466
1467 const int segCount = std::max( 1, static_cast<int>( std::ceil( sweep / calcAngleStep( aRadius ) ) ) );
1468
1469 std::vector<VECTOR2D> sectorPts;
1470 sectorPts.reserve( static_cast<size_t>( segCount ) + 2 );
1471 sectorPts.push_back( aCenterPoint );
1472
1473 for( int ii = 0; ii <= segCount; ++ii )
1474 {
1475 const double a = startAngle + sweep * ii / segCount;
1476 sectorPts.emplace_back( aCenterPoint.x + std::cos( a ) * aRadius,
1477 aCenterPoint.y + std::sin( a ) * aRadius );
1478 }
1479
1480 // Not DrawPolygon(), which would also stroke the radial edges
1481 fillOutline( roundedOutline( sectorPts.begin(), sectorPts.end() ), m_fillColor );
1482 }
1483
1484 if( m_isStrokeEnabled )
1485 {
1486 addArcInstance( aCenterPoint, aRadius, aStartAngle.AsRadians(), aAngle.AsRadians(),
1488 }
1489}
1490
1491
1492void GAL_MODERN::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
1493 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
1494 double aWidth, double /*aMaxError*/ )
1495{
1496 if( aRadius <= 0 )
1497 {
1498 if( aWidth > 0 )
1499 DrawCircle( aCenterPoint, aWidth / 2.0 );
1500
1501 return;
1502 }
1503
1504 if( m_isStrokeEnabled )
1505 {
1506 double startAngle = aStartAngle.AsRadians();
1507 double endAngle = startAngle + aAngle.AsRadians();
1508 normalize( startAngle, endAngle );
1509
1510 const double halfWidth = aWidth / 2.0;
1511 const double sweep = endAngle - startAngle;
1512 const VECTOR2D startPoint( aCenterPoint.x + std::cos( startAngle ) * aRadius,
1513 aCenterPoint.y + std::sin( startAngle ) * aRadius );
1514 const VECTOR2D endPoint( aCenterPoint.x + std::cos( endAngle ) * aRadius,
1515 aCenterPoint.y + std::sin( endAngle ) * aRadius );
1516
1517 addArcInstance( aCenterPoint, aRadius + halfWidth, startAngle, sweep, m_lineWidth, m_strokeColor );
1518
1519 if( aRadius > halfWidth )
1520 addArcInstance( aCenterPoint, aRadius - halfWidth, startAngle, sweep, m_lineWidth, m_strokeColor );
1521
1522 addArcInstance( startPoint, halfWidth, startAngle + M_PI, M_PI, m_lineWidth, m_strokeColor );
1523 addArcInstance( endPoint, halfWidth, endAngle, M_PI, m_lineWidth, m_strokeColor );
1524 }
1525
1526 if( m_isFillEnabled )
1527 {
1528 addArcInstance( aCenterPoint, aRadius, aStartAngle.AsRadians(), aAngle.AsRadians(), aWidth,
1529 m_fillColor );
1530 }
1531}
1532
1533
1534RHI_INSTANCE& GAL_MODERN::addArcInstance( const VECTOR2D& aCenter, double aRadius, double aStartAngle,
1535 double aSweepAngle, double aWidth, const COLOR4D& aColor )
1536{
1537 const VECTOR2D center = xform( aCenter );
1538 const double scale = xformScale();
1539
1540 RHI_INSTANCE inst = makeInstance( aColor );
1541 inst.SetPosition( static_cast<float>( center.x ), static_cast<float>( center.y ) );
1542 inst.SetParam( 0, static_cast<float>( aRadius * scale ) );
1543 inst.SetParam( 1, static_cast<float>( aWidth * scale / 2.0 ) );
1544 inst.SetParam( 2, static_cast<float>( aStartAngle + xformAngle() ) );
1545 inst.SetParam( 3, static_cast<float>( aSweepAngle ) );
1546
1547 emitInstance( RHI_SHAPE_ARC, inst );
1549}
1550
1551
1552void GAL_MODERN::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
1553{
1554 if( aRadius <= 0 )
1555 return;
1556
1557 if( m_isFillEnabled )
1558 {
1559 addPadInstance( aCenterPoint, aRadius, aRadius, 0.0, 0.0f, 0.0, m_fillColor )
1561 }
1562
1563 if( m_isStrokeEnabled )
1564 {
1565 addArcInstance( aCenterPoint, aRadius, 0.0, 2.0 * M_PI, m_lineWidth, m_strokeColor )
1567 }
1568}
1569
1570
1571void GAL_MODERN::DrawEllipse( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1572 const EDA_ANGLE& aRotation )
1573{
1574 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1575 return;
1576
1577 drawEllipseShape( SHAPE_ELLIPSE( KiROUND( aCenterPoint ), KiROUND( aMajorRadius ), KiROUND( aMinorRadius ),
1578 aRotation ) );
1579}
1580
1581
1582void GAL_MODERN::DrawEllipseArc( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
1583 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle,
1584 const EDA_ANGLE& aEndAngle )
1585{
1586 if( aMajorRadius <= 0 || aMinorRadius <= 0 )
1587 return;
1588
1589 drawEllipseShape( SHAPE_ELLIPSE( KiROUND( aCenterPoint ), KiROUND( aMajorRadius ), KiROUND( aMinorRadius ),
1590 aRotation, aStartAngle, aEndAngle ) );
1591}
1592
1593
1595{
1596 // As finely as GAL::DrawEllipse divides it for the other canvases
1597 const int maxError = std::max( 1, KiROUND( aEllipse.GetMajorRadius() / 500.0 ) );
1598 const SHAPE_LINE_CHAIN curve = aEllipse.ConvertToPolyline( maxError );
1599
1600 if( m_isFillEnabled )
1601 {
1602 SHAPE_LINE_CHAIN outline = curve;
1603
1604 if( aEllipse.IsArc() )
1605 outline.Insert( 0, aEllipse.GetCenter() );
1606
1607 fillOutline( outline, m_fillColor );
1608 }
1609
1610 if( m_isStrokeEnabled )
1611 strokeLineChain( curve );
1612}
1613
1614
1615void GAL_MODERN::DrawHoleWall( const VECTOR2D& aCenterPoint, double aHoleRadius,
1616 double aWallWidth )
1617{
1618 if( !m_isFillEnabled || aHoleRadius + aWallWidth <= 0 )
1619 return;
1620
1621 // The hole covers a wall thinner than a pixel unless the ring is widened outward from it
1622 RHI_INSTANCE& ring = addArcInstance( aCenterPoint, aHoleRadius + aWallWidth / 2.0, 0.0, 2.0 * M_PI, aWallWidth,
1623 m_fillColor );
1624
1627}
1628
1629
1630void GAL_MODERN::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1631{
1632 double hw = std::abs( aEndPoint.x - aStartPoint.x ) / 2.0;
1633 double hh = std::abs( aEndPoint.y - aStartPoint.y ) / 2.0;
1634 VECTOR2D center( ( aStartPoint.x + aEndPoint.x ) / 2.0,
1635 ( aStartPoint.y + aEndPoint.y ) / 2.0 );
1636
1637 if( m_isFillEnabled )
1638 {
1639 addPadInstance( center, hw, hh, 0.0, 1.0f, 0.0, m_fillColor );
1640 }
1641
1642 if( m_isStrokeEnabled )
1643 {
1644 VECTOR2D a( aStartPoint.x, aStartPoint.y );
1645 VECTOR2D b( aEndPoint.x, aStartPoint.y );
1646 VECTOR2D c( aEndPoint.x, aEndPoint.y );
1647 VECTOR2D d( aStartPoint.x, aEndPoint.y );
1648
1653 }
1654}
1655
1656
1657void GAL_MODERN::DrawPolygon( const std::deque<VECTOR2D>& aPointList )
1658{
1659 if( aPointList.size() < 2 )
1660 return;
1661
1662 if( m_isFillEnabled )
1663 fillOutline( roundedOutline( aPointList.begin(), aPointList.end() ), m_fillColor );
1664
1665 // Match Cairo and the OpenGL canvas, which do not close the stroke. Callers wanting a closed
1666 // outline repeat the first point at the end.
1667 if( m_isStrokeEnabled )
1668 drawPolylineSegments( *this, aPointList.begin(), aPointList.end() );
1669}
1670
1671
1672void GAL_MODERN::DrawPolygon( const VECTOR2D aPointList[], int aListSize )
1673{
1674 if( aListSize < 2 )
1675 return;
1676
1677 if( m_isFillEnabled )
1678 fillOutline( roundedOutline( aPointList, aPointList + aListSize ), m_fillColor );
1679
1680 if( m_isStrokeEnabled )
1681 drawPolylineSegments( *this, aPointList, aPointList + aListSize );
1682}
1683
1684
1685void GAL_MODERN::DrawPolygon( const SHAPE_POLY_SET& aPolySet, bool /*aStrokeTriangulation*/ )
1686{
1687 if( m_isFillEnabled )
1688 {
1689 if( aPolySet.IsTriangulationUpToDate() )
1690 {
1692 }
1693 else
1694 {
1695 SHAPE_POLY_SET triangulated( aPolySet );
1696 triangulated.CacheTriangulation( false );
1697 emitTriangulatedPolygon( triangulated, m_fillColor );
1698 }
1699 }
1700
1701 if( m_isStrokeEnabled )
1702 {
1703 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1704 strokeLineChain( aPolySet.COutline( j ) );
1705
1706 for( int j = 0; j < aPolySet.OutlineCount(); ++j )
1707 {
1708 for( int h = 0; h < aPolySet.HoleCount( j ); ++h )
1709 strokeLineChain( aPolySet.CHole( j, h ) );
1710 }
1711 }
1712}
1713
1714
1716{
1717 if( aPolygon.PointCount() < 2 )
1718 return;
1719
1720 if( m_isFillEnabled )
1721 fillOutline( aPolygon, m_fillColor );
1722
1723 if( m_isStrokeEnabled )
1724 strokeLineChain( aPolygon );
1725}
1726
1727
1728void GAL_MODERN::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
1729 const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint,
1730 double aFilterValue )
1731{
1732 std::vector<VECTOR2D> controlPts = { aStartPoint, aControlPointA, aControlPointB, aEndPoint };
1733 BEZIER_POLY converter( controlPts );
1734
1735 std::vector<VECTOR2D> output;
1736 converter.GetPoly( output, aFilterValue );
1737
1738 if( output.size() < 2 )
1739 return;
1740
1741 for( size_t i = 1; i < output.size(); ++i )
1742 {
1743 DrawLine( output[i - 1], output[i] );
1744 }
1745}
1746
1747
1748void GAL_MODERN::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
1749{
1750 // Mirroring and rotation go to the texture coordinates as on the OpenGL canvas, so the image as
1751 // loaded is uploaded once per KIID however it is turned
1752 const wxImage* image = aBitmap.GetOriginalImageData();
1753
1754 if( !image || !image->IsOk() )
1755 return;
1756
1757 float alpha = static_cast<float>( std::clamp( alphaBlend, 0.0, 1.0 ) );
1758
1759 const VECTOR2I sizePixels = aBitmap.GetSizePixels();
1760
1761 if( image->GetWidth() <= 0 || image->GetHeight() <= 0 || sizePixels.x <= 0 || sizePixels.y <= 0 )
1762 return;
1763
1764 const KIID imageId = aBitmap.GetImageID();
1765 RHI_TextureHandle texture;
1766 std::array<uint64_t, 2> bitmapContentHash{};
1767
1768 auto cacheIt = m_bitmapTextureCache.find( imageId );
1769
1770 if( cacheIt != m_bitmapTextureCache.end() )
1771 {
1772 texture = cacheIt->second.texture;
1773 bitmapContentHash = cacheIt->second.contentHash;
1774 cacheIt->second.lastUse = ++m_bitmapUseCounter;
1775 }
1776 else
1777 {
1778 // The quad keeps the image's size, so a texture scaled down to fit only loses detail
1779 const wxImage fitted = FitImageToTexture( *image, static_cast<int>( m_deviceProxy.MaxTextureSize() ) );
1780
1781 if( !fitted.IsOk() )
1782 return;
1783
1784 RHI_TEXTURE_DESC texDesc;
1785 texDesc.width = static_cast<uint32_t>( fitted.GetWidth() );
1786 texDesc.height = static_cast<uint32_t>( fitted.GetHeight() );
1788 texDesc.filter = RHI_FILTER::BILINEAR;
1789
1790 texture = m_deviceProxy.CreateTexture( texDesc );
1791
1792 if( !texture )
1793 return;
1794
1795 std::vector<uint8_t> rgba = ImageToRgba( fitted );
1796
1797 MMH3_HASH contentHash;
1798 contentHash.add( static_cast<int32_t>( texDesc.width ) );
1799 contentHash.add( static_cast<int32_t>( texDesc.height ) );
1800 hashBytes( contentHash, rgba.data(), rgba.size() );
1801 bitmapContentHash = hashDigest( contentHash );
1802
1803 m_deviceProxy.UploadTexture( texture, rgba.data(), rgba.size() );
1804
1805 BITMAP_CACHE_ENTRY& entry = m_bitmapTextureCache[imageId];
1806 entry.texture = texture;
1807 entry.bytes = rgba.size();
1808 entry.lastUse = ++m_bitmapUseCounter;
1809 entry.contentHash = bitmapContentHash;
1810
1812 }
1813
1814 double scale = 1.0 / ( aBitmap.GetPPI() * m_worldUnitLength );
1815 double w = static_cast<double>( sizePixels.x ) * scale;
1816 double h = static_cast<double>( sizePixels.y ) * scale;
1817
1818 float hw = static_cast<float>( w / 2.0 );
1819 float hh = static_cast<float>( h / 2.0 );
1820
1821 float texStartX = aBitmap.IsMirroredX() ? 1.0f : 0.0f;
1822 float texEndX = aBitmap.IsMirroredX() ? 0.0f : 1.0f;
1823 float texStartY = aBitmap.IsMirroredY() ? 1.0f : 0.0f;
1824 float texEndY = aBitmap.IsMirroredY() ? 0.0f : 1.0f;
1825
1826 const double texCos = aBitmap.Rotation().Cos();
1827 const double texSin = aBitmap.Rotation().Sin();
1828
1829 VECTOR2D corners[4] = {
1830 { -hw, -hh },
1831 { hw, -hh },
1832 { hw, hh },
1833 { -hw, hh }
1834 };
1835
1836 for( auto& corner : corners )
1837 corner = xform( corner );
1838
1839 uint8_t alphaU8 = packColorChannel( alpha );
1840
1841 // The OpenGL canvas turns the texture matrix about the texture centre
1842 auto setVert = [&]( RHI_VERTEX& v, const VECTOR2D& pos, float tu, float tv )
1843 {
1844 const double du = tu - 0.5;
1845 const double dv = tv - 0.5;
1846
1847 v.SetPosition( static_cast<float>( pos.x ), static_cast<float>( pos.y ) );
1848 v.SetUV( static_cast<float>( 0.5 + du * texCos - dv * texSin ),
1849 static_cast<float>( 0.5 + du * texSin + dv * texCos ) );
1850 v.SetColor( 255, 255, 255, alphaU8 );
1851 };
1852
1853 BITMAP_QUAD quad;
1854 quad.alpha = alpha;
1855 quad.contentHash = bitmapContentHash;
1856 quad.texture = texture;
1857 quad.ownsTexture = false; // owned by m_bitmapTextureCache
1858
1859 setVert( quad.vertices[0], corners[0], texStartX, texStartY );
1860 setVert( quad.vertices[1], corners[1], texEndX, texStartY );
1861 setVert( quad.vertices[2], corners[2], texEndX, texEndY );
1862 setVert( quad.vertices[3], corners[0], texStartX, texStartY );
1863 setVert( quad.vertices[4], corners[2], texEndX, texEndY );
1864 setVert( quad.vertices[5], corners[3], texStartX, texEndY );
1865
1866 RHI_BUFFER_DESC bufDesc;
1867 bufDesc.type = RHI_BUFFER_TYPE::VERTEX;
1868 bufDesc.sizeBytes = sizeof( RHI_VERTEX ) * 6;
1869
1870 quad.vertexBuffer = m_deviceProxy.CreateBuffer( bufDesc );
1871
1872 if( quad.vertexBuffer )
1873 m_deviceProxy.UpdateBuffer( quad.vertexBuffer, quad.vertices, sizeof( RHI_VERTEX ) * 6 );
1874
1875 // The current target's pool, so a group being recorded captures the quad
1876 poolForCurrentTarget().bitmapQuads.push_back( quad );
1877}
1878
1879
1880void GAL_MODERN::DrawPolyline( const std::deque<VECTOR2D>& aPointList )
1881{
1882 drawPolylineSegments( *this, aPointList.begin(), aPointList.end() );
1883}
1884
1885
1886void GAL_MODERN::DrawPolyline( const std::vector<VECTOR2D>& aPointList )
1887{
1888 drawPolylineSegments( *this, aPointList.begin(), aPointList.end() );
1889}
1890
1891
1892void GAL_MODERN::DrawPolyline( const VECTOR2D aPointList[], int aListSize )
1893{
1894 if( aListSize < 2 )
1895 return;
1896
1897 drawPolylineSegments( *this, aPointList, aPointList + aListSize );
1898}
1899
1900
1902{
1903 strokeLineChain( aLineChain );
1904}
1905
1906
1907void GAL_MODERN::DrawPolylines( const std::vector<std::vector<VECTOR2D>>& aPointLists )
1908{
1909 for( const auto& points : aPointLists )
1910 {
1911 DrawPolyline( points );
1912 }
1913}
1914
1915
1916namespace
1917{
1918// Glyphs the shader table holds, which is every glyph of the font unless the font outgrows it
1919uint32_t tableGlyphCount()
1920{
1921 return std::min( BUILTIN_FONT::GlyphCount(), RHI_GLYPH_TABLE_SIZE );
1922}
1923
1924
1925// The atlas the OpenGL canvas draws bitmap text from, as the render thread reads it
1926std::shared_ptr<const RHI_GLYPH_ATLAS_DATA> buildGlyphAtlas()
1927{
1928 static const std::shared_ptr<const RHI_GLYPH_ATLAS_DATA> built = []()
1929 {
1930 using namespace BUILTIN_FONT;
1931
1932 auto atlas = std::make_shared<RHI_GLYPH_ATLAS_DATA>();
1933 atlas->width = font_image.width;
1934 atlas->height = font_image.height;
1935 atlas->rgba = RgbToRgba( font_image.pixels, nullptr, size_t( atlas->width ) * atlas->height );
1936
1937 atlas->table.assign( ( RHI_GLYPH_TABLE_SIZE + 1 ) * 4, 0.0f );
1938
1939 const FONT_GLYPH_TYPE* first = LookupGlyph( '!' );
1940
1941 for( uint32_t ii = 0; ii < tableGlyphCount() && first; ++ii )
1942 {
1943 const GLYPH_BOX box = ComputeGlyphBox( first[ii] );
1944
1945 atlas->table[ii * 4 + 0] = box.atlasX / atlas->width;
1946 atlas->table[ii * 4 + 1] = box.atlasY / atlas->height;
1947 atlas->table[ii * 4 + 2] = box.w / atlas->width;
1948 atlas->table[ii * 4 + 3] = box.h / atlas->height;
1949 }
1950
1951 atlas->table[RHI_GLYPH_TABLE_SIZE * 4 + 0] = static_cast<float>( atlas->width );
1952 atlas->table[RHI_GLYPH_TABLE_SIZE * 4 + 1] = static_cast<float>( atlas->height );
1953
1954 return atlas;
1955 }();
1956
1957 return built;
1958}
1959} // namespace
1960
1961
1962bool GAL_MODERN::drawBitmapGlyphs( const wxString& aText, const VECTOR2I& aPosition, const EDA_ANGLE& aAngle )
1963{
1964 using namespace BUILTIN_FONT;
1965
1966 // Cases the glyph quads leave to strokes
1968 || m_globalFlipX || m_globalFlipY || m_currentTransform.Determinant() <= 0.0 || aText.Contains( wxT( "^{" ) )
1969 || aText.Contains( wxT( "_{" ) ) || aText.Contains( wxT( "~{" ) ) || aText.Contains( wxT( "\n" ) ) )
1970 {
1971 return false;
1972 }
1973
1974 const UTF8 text( aText );
1975 VECTOR2D textSize;
1976 float commonOffset;
1977 std::tie( textSize, commonOffset ) = ComputeBitmapTextSize( text );
1978
1979 if( textSize.y <= 0.0 )
1980 return false;
1981
1982 if( m_renderThread )
1983 m_renderThread->SetGlyphAtlas( buildGlyphAtlas() );
1984
1985 const FONT_GLYPH_TYPE* first = LookupGlyph( '!' );
1986
1987 // The shader table indexes glyphs by their place in the font
1988 const FONT_GLYPH_TYPE* const limit = first + tableGlyphCount();
1989
1990 const double scale = 1.4 * GetGlyphSize().y / textSize.y;
1991 const double angle = aAngle.AsRadians();
1992 const double xfScale = xformScale();
1993 const double xfAngle = xformAngle();
1994
1995 // The origin of the first glyph, in the font's units from the text position
1996 double cursorX = 0.0;
1997 double cursorY = -commonOffset;
1998
1999 switch( GetHorizontalJustify() )
2000 {
2001 case GR_TEXT_H_ALIGN_CENTER: cursorX -= textSize.x / 2.0; break;
2002 case GR_TEXT_H_ALIGN_RIGHT: cursorX -= textSize.x; break;
2003 default: break;
2004 }
2005
2006 switch( GetVerticalJustify() )
2007 {
2008 case GR_TEXT_V_ALIGN_CENTER: cursorY -= textSize.y / 2.0; break;
2009 case GR_TEXT_V_ALIGN_BOTTOM: cursorY -= textSize.y; break;
2010 default: break;
2011 }
2012
2013 const COLOR4D& color = m_strokeColor;
2014 const double cosA = std::cos( angle );
2015 const double sinA = std::sin( angle );
2016
2017 std::vector<RHI_INSTANCE> glyphs;
2018 glyphs.reserve( size_t( aText.length() ) );
2019
2020 for( UTF8::uni_iter chIt = text.ubegin(), end = text.uend(); chIt < end; ++chIt )
2021 {
2022 if( *chIt == ' ' )
2023 {
2024 cursorX += SpaceAdvance();
2025 continue;
2026 }
2027
2028 const FONT_GLYPH_TYPE* glyph = LookupGlyph( *chIt );
2029
2030 if( !glyph )
2031 glyph = LookupGlyph( '?' );
2032
2033 if( !glyph )
2034 continue;
2035
2036 if( glyph < first || glyph >= limit )
2037 return false;
2038
2039 const GLYPH_BOX box = ComputeGlyphBox( *glyph );
2040 const double width = box.w;
2041 const double height = box.h;
2042 const double centerX = cursorX + box.x + width / 2.0;
2043 const double centerY = cursorY + box.y + height / 2.0;
2044
2045 // The text turns about its position the way the OpenGL canvas turns it, against the angle
2046 VECTOR2D local( scale * centerX, scale * centerY );
2047 VECTOR2D turned( local.x * cosA + local.y * sinA, -local.x * sinA + local.y * cosA );
2048 VECTOR2D center = xform( VECTOR2D( aPosition.x + turned.x, aPosition.y + turned.y ) );
2049
2050 RHI_INSTANCE inst;
2051 inst.SetPosition( static_cast<float>( center.x ), static_cast<float>( center.y ) );
2052 inst.SetRotation( static_cast<float>( xfAngle - angle ) );
2053 inst.SetParam( 0, static_cast<float>( scale * width / 2.0 * xfScale ) );
2054 inst.SetParam( 1, static_cast<float>( scale * height / 2.0 * xfScale ) );
2055 inst.SetParam( 2, static_cast<float>( glyph - first ) );
2056 inst.SetColor( packColorChannel( color.r ), packColorChannel( color.g ), packColorChannel( color.b ),
2057 packColorChannel( color.a ) );
2058 inst.SetDepth( static_cast<float>( m_layerDepth ) );
2060
2063
2064 if( m_strokeLod > 0.0 )
2065 {
2067 inst.SetParam( 3, static_cast<float>( m_strokeLod ) );
2068 }
2069
2070 glyphs.push_back( inst );
2071 cursorX += glyph->advance;
2072 }
2073
2074 notePolarity( false );
2075
2076 std::vector<RHI_INSTANCE>& tracks = poolForCurrentTarget().instances[RHI_SHAPE_TRACK];
2077 tracks.insert( tracks.end(), glyphs.begin(), glyphs.end() );
2078
2079 return true;
2080}
2081
2082
2083void GAL_MODERN::BitmapText( const wxString& aText, const VECTOR2I& aPosition, const EDA_ANGLE& aAngle )
2084{
2085 if( drawBitmapGlyphs( aText, aPosition, aAngle ) )
2086 return;
2087
2088 // The OpenGL canvas draws this from a font texture, which fades away as the text shrinks.
2089 // Strokes held at one pixel would turn every small net name into a smudge instead.
2091 GAL::BitmapText( aText, aPosition, aAngle );
2092 m_bitmapTextHeight = 0.0;
2093}
2094
2095
2096void GAL_MODERN::DrawGlyph( const KIFONT::GLYPH& aGlyph, int /*aNth*/, int /*aTotal*/ )
2097{
2098 if( aGlyph.IsStroke() )
2099 {
2100 const auto& strokeGlyph = static_cast<const KIFONT::STROKE_GLYPH&>( aGlyph );
2101 DrawPolylines( strokeGlyph );
2102 }
2103 else if( aGlyph.IsOutline() )
2104 {
2105 const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
2106 const RHI_VERTEX fill = fillVertex( m_fillColor );
2107
2108 outlineGlyph.Triangulate(
2109 [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2, const VECTOR2I& aPt3 )
2110 {
2111 emitTriangle( aPt1, aPt2, aPt3, fill );
2112 } );
2113 }
2114}
2115
2116
2117RHI_INSTANCE& GAL_MODERN::addPadInstance( const VECTOR2D& aCenter, double aHalfWidth, double aHalfHeight,
2118 double aCornerRadius, float aShapeType, double aRotation,
2119 const COLOR4D& aColor )
2120{
2121 const VECTOR2D center = xform( aCenter );
2122 const double scale = xformScale();
2123
2124 RHI_INSTANCE inst = makeInstance( aColor );
2125 inst.SetPosition( static_cast<float>( center.x ), static_cast<float>( center.y ) );
2126 inst.SetRotation( static_cast<float>( aRotation + xformAngle() ) );
2127 inst.SetParam( 0, static_cast<float>( aHalfWidth * scale ) );
2128 inst.SetParam( 1, static_cast<float>( aHalfHeight * scale ) );
2129 inst.SetParam( 2, static_cast<float>( aCornerRadius * scale ) );
2130 inst.SetParam( 3, aShapeType );
2131
2132 emitInstance( RHI_SHAPE_PAD, inst );
2134}
2135
2136
2138{
2139 // A closed chain takes a closing segment, which SegmentCount() includes even for one point
2140 if( aChain.PointCount() < 2 )
2141 return;
2142
2143 for( int i = 0; i < aChain.SegmentCount(); ++i )
2144 {
2145 const SEG seg = aChain.CSegment( i );
2146 DrawLine( VECTOR2D( seg.A ), VECTOR2D( seg.B ) );
2147 }
2148}
2149
2150
2151void GAL_MODERN::fillOutline( SHAPE_LINE_CHAIN aOutline, const COLOR4D& aColor )
2152{
2153 if( aOutline.PointCount() < 3 )
2154 return;
2155
2156 // Line endings arrive as open chains, which SHAPE_POLY_SET rejects
2157 aOutline.SetClosed( true );
2158
2159 SHAPE_POLY_SET polySet( aOutline );
2160 polySet.CacheTriangulation( false );
2161 emitTriangulatedPolygon( polySet, aColor );
2162}
2163
2164
2165void GAL_MODERN::emitTriangulatedPolygon( const SHAPE_POLY_SET& aPolySet, const COLOR4D& aColor )
2166{
2167 const RHI_VERTEX fill = fillVertex( aColor );
2168
2169 for( int j = 0; j < static_cast<int>( aPolySet.TriangulatedPolyCount() ); ++j )
2170 {
2171 const auto* triPoly = aPolySet.TriangulatedPolygon( j );
2172
2173 for( int i = 0; i < static_cast<int>( triPoly->GetTriangleCount() ); ++i )
2174 {
2175 VECTOR2I a, b, c;
2176 triPoly->GetTriangle( i, a, b, c );
2177 emitTriangle( a, b, c, fill );
2178 }
2179 }
2180}
2181
2182
2184{
2185 RHI_VERTEX vertex;
2186 vertex.SetColor( packColorChannel( aColor.r ), packColorChannel( aColor.g ), packColorChannel( aColor.b ),
2187 m_negativeMode ? 255 : packColorChannel( aColor.a ) );
2188 vertex.SetUV( static_cast<float>( m_layerDepth ), 0.0f );
2189
2191
2192 return vertex;
2193}
2194
2195
2196void GAL_MODERN::emitTriangle( const VECTOR2D& aA, const VECTOR2D& aB, const VECTOR2D& aC, const RHI_VERTEX& aFill )
2197{
2198 std::vector<RHI_VERTEX>& vertices = poolForCurrentTarget().polygonVertices;
2199
2200 for( const VECTOR2D& corner : { aA, aB, aC } )
2201 {
2202 const VECTOR2D placed = xform( corner );
2203
2204 vertices.push_back( aFill );
2205 vertices.back().SetPosition( static_cast<float>( placed.x ), static_cast<float>( placed.y ) );
2206 }
2207}
2208
2209
2211{
2212 // Groups do not nest. A nested BeginGroup() draws into the open group and only counts depth, as a
2213 // second swap would corrupt the scratch state.
2214 if( m_groupDepth > 0 )
2215 {
2216 m_groupDepth++;
2217 return m_currentGroupId;
2218 }
2219
2220 int groupId = m_groupCounter++;
2221
2222 // swap() keeps the data() pointers, so staging data the render thread holds stays valid
2224
2225 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2226 m_groupScratchInstances[shape].swap( pool.instances[shape] );
2227
2230
2231 m_currentGroupId = groupId;
2232 m_groupNegative = false;
2233 m_groupDepth = 1;
2234
2235 return groupId;
2236}
2237
2238
2240{
2241 if( m_groupDepth == 0 )
2242 return;
2243
2244 if( m_groupDepth > 1 )
2245 {
2246 m_groupDepth--;
2247 return;
2248 }
2249
2251
2253 group.instances = std::move( pool.instances );
2254 group.polygonVertices = std::move( pool.polygonVertices );
2255 group.bitmapQuads = std::move( pool.bitmapQuads );
2256
2257 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2258 m_groupScratchInstances[shape].swap( pool.instances[shape] );
2259
2262
2263 group.baseDepth = static_cast<float>( m_layerDepth );
2264 group.negative = m_groupNegative;
2265 countBitmapRefs( group, 1 );
2266
2267 m_groups[m_currentGroupId] = std::move( group );
2268 m_currentGroupId = -1;
2269 m_groupDepth = 0;
2270}
2271
2272
2273std::array<uint64_t, 2> GAL_MODERN::computeGroupFingerprint( const GAL_GROUP& aGroup )
2274{
2275 MMH3_HASH hash;
2276
2277 auto mix = [&]( const void* aData, size_t aSize )
2278 {
2279 hashBytes( hash, aData, aSize );
2280 };
2281
2282 auto mixVector = [&]( const auto& aVector )
2283 {
2284 const uint64_t count = aVector.size();
2285 mix( &count, sizeof( count ) );
2286 mix( aVector.data(), aVector.size() * sizeof( aVector[0] ) );
2287 };
2288
2289 for( const std::vector<RHI_INSTANCE>& instances : aGroup.instances )
2290 mixVector( instances );
2291
2292 mixVector( aGroup.polygonVertices );
2293 mix( &aGroup.baseDepth, sizeof( aGroup.baseDepth ) );
2294 mix( &aGroup.negative, sizeof( aGroup.negative ) );
2295 mix( &aGroup.undimmed, sizeof( aGroup.undimmed ) );
2296
2297 const uint64_t quadCount = aGroup.bitmapQuads.size();
2298 mix( &quadCount, sizeof( quadCount ) );
2299
2300 for( const BITMAP_QUAD& quad : aGroup.bitmapQuads )
2301 {
2302 mix( quad.vertices, sizeof( quad.vertices ) );
2303 mix( &quad.alpha, sizeof( quad.alpha ) );
2304 mix( quad.contentHash.data(), sizeof( quad.contentHash ) );
2305 }
2306
2307 return hashDigest( hash );
2308}
2309
2310
2311std::array<uint64_t, 2> GAL_MODERN::GroupContentFingerprint( int aGroupNumber ) const
2312{
2313 auto it = m_groups.find( aGroupNumber );
2314
2315 if( it == m_groups.end() )
2316 return {};
2317
2318 const GAL_GROUP& group = it->second;
2319
2320 if( !group.contentFingerprintValid )
2321 {
2322 group.contentFingerprint = computeGroupFingerprint( group );
2323 group.contentFingerprintValid = true;
2324 }
2325
2326 return group.contentFingerprint;
2327}
2328
2329
2330void GAL_MODERN::DrawGroup( int aGroupNumber )
2331{
2332 auto it = m_groups.find( aGroupNumber );
2333
2334 if( it == m_groups.end() )
2335 {
2336 wxLogTrace( traceGalGroups, wxS( "DrawGroup(%d) NOT FOUND (total groups=%zu)" ),
2337 aGroupNumber, m_groups.size() );
2338 return;
2339 }
2340
2341 const GAL_GROUP& group = it->second;
2343
2344 notePolarity( group.negative );
2345
2346 const RHI_SHAPE_COUNTS before = pool.Sizes();
2347
2348 // Record where the group lands in the static pool so a later color or depth change can be patched
2349 // in place. Inside BeginGroup() the static vectors are the scratch.
2350 if( &pool == &m_staticPool && m_groupDepth == 0 )
2351 {
2352 STATIC_GROUP_SLOT slot;
2353 slot.start = before;
2354
2355 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2356 slot.count[shape] = static_cast<uint32_t>( group.instances[shape].size() );
2357
2358 slot.count[RHI_SHAPE_POLYGON] = static_cast<uint32_t>( group.polygonVertices.size() );
2359 m_staticGroupSlots[aGroupNumber].push_back( slot );
2360 }
2361
2362 // A placed group is not told when its layer moves, the scene keeps depth per layer. Drawn
2363 // anywhere else it has to be carried to the depth its layer has now.
2364 const float shift = group.sceneLayer >= 0 ? static_cast<float>( m_layerDepth ) - group.baseDepth : 0.0f;
2365 const bool overlay = m_groupDepth == 0 && m_currentTarget == TARGET_OVERLAY;
2366
2367 auto rebase = [shift]( uint16_t& aDepth )
2368 {
2369 aDepth = glm::packHalf1x16( glm::unpackHalf1x16( aDepth ) + shift );
2370 };
2371
2372 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2373 {
2374 std::vector<RHI_INSTANCE>& instances = pool.instances[shape];
2375
2376 instances.insert( instances.end(), group.instances[shape].begin(), group.instances[shape].end() );
2377
2378 if( !overlay && shift == 0.0f )
2379 continue;
2380
2381 for( size_t ii = before[shape]; ii < instances.size(); ++ii )
2382 {
2383 if( overlay )
2384 instances[ii].SetFlag( RHI_INSTANCE_OVERLAY_ALPHA );
2385
2386 if( shift != 0.0f )
2387 rebase( instances[ii].depth );
2388 }
2389 }
2390
2391 pool.polygonVertices.insert( pool.polygonVertices.end(), group.polygonVertices.begin(),
2392 group.polygonVertices.end() );
2393
2394 if( shift != 0.0f )
2395 {
2396 for( size_t ii = before[RHI_SHAPE_POLYGON]; ii < pool.polygonVertices.size(); ++ii )
2397 rebase( pool.polygonVertices[ii].u );
2398 }
2399
2400 if( !group.bitmapQuads.empty() )
2401 {
2402 // A group's bitmap quads always go to a dynamic pool
2404 const bool open = !m_persistentTargets
2407
2408 // A kept pool still lists the quads of the frame that filled it
2409 for( const auto& bq : group.bitmapQuads )
2410 {
2411 if( !open )
2412 break;
2413
2414 BITMAP_QUAD copy = bq;
2415 copy.ownsResources = false;
2416 bitmapPool.bitmapQuads.push_back( copy );
2417 }
2418 }
2419}
2420
2421
2423{
2424 // Zero scale collapses the instance quad to a point, so it produces no fragments
2425 RHI_INSTANCE inst;
2426 inst.scaleX = 0;
2427 inst.scaleY = 0;
2428 inst.SetColor( 0, 0, 0, 0 );
2429 return inst;
2430}
2431
2432
2434{
2435 RHI_VERTEX vert;
2436 vert.SetColor( 0, 0, 0, 0 );
2437 return vert;
2438}
2439
2440
2441static void fillDead( RHI_INSTANCE* aFirst, uint32_t aCount )
2442{
2443 std::fill_n( aFirst, aCount, deadInstance() );
2444}
2445
2446
2447static void fillDead( RHI_VERTEX* aFirst, uint32_t aCount )
2448{
2449 std::fill_n( aFirst, aCount, deadVertex() );
2450}
2451
2452
2453template <typename ARENA>
2454static void markSceneDirty( ARENA& aArena, uint32_t aStart, uint32_t aCount )
2455{
2456 // Every span is its own staging buffer on the render thread, so keep the list short
2457 static constexpr size_t MAX_SPANS = 8;
2458
2459 if( aCount == 0 || aArena.uploadAll )
2460 return;
2461
2462 for( auto& span : aArena.dirty )
2463 {
2464 // A slot is freed, reused and written in one update, which is still one span
2465 if( aStart >= span.begin && aStart <= span.end )
2466 {
2467 span.end = std::max( span.end, aStart + aCount );
2468 return;
2469 }
2470 }
2471
2472 if( aArena.dirty.size() >= MAX_SPANS )
2473 {
2474 for( size_t ii = 1; ii < aArena.dirty.size(); ++ii )
2475 aArena.dirty[0].add( aArena.dirty[ii].begin, aArena.dirty[ii].end - aArena.dirty[ii].begin );
2476
2477 aArena.dirty.resize( 1 );
2478 }
2479
2480 aArena.dirty.emplace_back();
2481 aArena.dirty.back().add( aStart, aCount );
2482}
2483
2484
2485template <typename T>
2486uint32_t GAL_MODERN::allocSceneSlot( SCENE_ARENA<T>& aArena, uint32_t aCount )
2487{
2488 if( aCount == 0 )
2489 return 0;
2490
2491 uint32_t start = 0;
2492 auto it = aArena.freeSlots.find( aCount );
2493
2494 if( it != aArena.freeSlots.end() && !it->second.empty() )
2495 {
2496 start = it->second.back();
2497 it->second.pop_back();
2498 aArena.dead -= aCount;
2499 }
2500 else
2501 {
2502 start = static_cast<uint32_t>( aArena.elements.size() );
2503 aArena.elements.resize( start + aCount );
2504 }
2505
2506 markSceneDirty( aArena, start, aCount );
2507 return start;
2508}
2509
2510
2511template <typename T>
2512void GAL_MODERN::freeSceneSlot( SCENE_ARENA<T>& aArena, uint32_t aStart, uint32_t aCount )
2513{
2514 if( aCount == 0 )
2515 return;
2516
2517 // The last slot just shortens the draw
2518 if( static_cast<size_t>( aStart ) + aCount == aArena.elements.size() )
2519 {
2520 aArena.elements.resize( aStart );
2521 return;
2522 }
2523
2524 fillDead( &aArena.elements[aStart], aCount );
2525 markSceneDirty( aArena, aStart, aCount );
2526 aArena.dead += aCount;
2527 aArena.freeSlots[aCount].push_back( aStart );
2528}
2529
2530
2531template <typename T>
2532void GAL_MODERN::compactSceneArena( SCENE_LAYER& aLayer, SCENE_ARENA<T>& aArena, int aArenaId )
2533{
2534 std::vector<GAL_GROUP*> owners;
2535 owners.reserve( aLayer.residents.size() );
2536
2537 for( int groupId : aLayer.residents )
2538 {
2539 GAL_GROUP& group = m_groups.at( groupId );
2540
2541 if( group.slotCount[aArenaId] > 0 )
2542 owners.push_back( &group );
2543 }
2544
2545 // Keeping slot order keeps the draw order inside the layer
2546 std::sort( owners.begin(), owners.end(),
2547 [aArenaId]( const GAL_GROUP* aLhs, const GAL_GROUP* aRhs )
2548 {
2549 return aLhs->slotStart[aArenaId] < aRhs->slotStart[aArenaId];
2550 } );
2551
2552 std::vector<T> packed;
2553 packed.reserve( aArena.elements.size() - aArena.dead );
2554
2555 for( GAL_GROUP* group : owners )
2556 {
2557 auto first = aArena.elements.begin() + group->slotStart[aArenaId];
2558
2559 group->slotStart[aArenaId] = static_cast<uint32_t>( packed.size() );
2560 packed.insert( packed.end(), first, first + group->slotCount[aArenaId] );
2561 }
2562
2563 aArena.elements.swap( packed );
2564 aArena.freeSlots.clear();
2565 aArena.dirty.clear();
2566 aArena.dead = 0;
2567 aArena.uploadAll = true;
2568}
2569
2570
2571void GAL_MODERN::countGroupAlpha( const GAL_GROUP& aGroup, SCENE_LAYER& aLayer, int aDelta )
2572{
2573 // A knockout covers fully whatever alpha the layer is drawn with, so it has no say in it
2574 auto count = [&]( const std::vector<RHI_INSTANCE>& aInstances )
2575 {
2576 for( const RHI_INSTANCE& inst : aInstances )
2577 {
2578 if( !inst.HasFlag( RHI_INSTANCE_NEGATIVE ) )
2579 aLayer.alphaCount[inst.a] += aDelta;
2580 }
2581 };
2582
2583 for( const std::vector<RHI_INSTANCE>& instances : aGroup.instances )
2584 count( instances );
2585
2586 if( !aGroup.negative )
2587 {
2588 for( const RHI_VERTEX& vert : aGroup.polygonVertices )
2589 aLayer.alphaCount[vert.a] += aDelta;
2590 }
2591}
2592
2593
2594void GAL_MODERN::countGroupArenas( const GAL_GROUP& aGroup, uint32_t* aCounts )
2595{
2596 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2597 {
2598 const std::vector<RHI_INSTANCE>& instances = aGroup.instances[shape];
2599 const auto negatives = std::ranges::count_if( instances,
2600 []( const RHI_INSTANCE& aInstance )
2601 {
2602 return aInstance.HasFlag(
2604 } );
2605
2606 aCounts[arenaOf( shape, false )] = static_cast<uint32_t>( instances.size() - negatives );
2607 aCounts[arenaOf( shape, true )] = static_cast<uint32_t>( negatives );
2608 }
2609}
2610
2611
2613{
2614 // In its own depth slice a layer keeps its alpha instead of being composited with it. Its
2615 // edges are hard, a partly covered one would claim the pixel from an element behind it.
2616 const bool composited = sceneLayerComposited( aLayer );
2617 const bool opaque = aLayer.bakedAlpha >= 0 && composited;
2618 const bool hard = !composited && ( aLayer.translucent || aLayer.hasNegatives );
2619
2620 // The scene draws each layer into its own slice of the depth range, so an element keeps
2621 // only how far it sits from its layer
2622 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
2623 {
2624 const int normalArena = arenaOf( shape, false );
2625 const int negativeArena = arenaOf( shape, true );
2626 SCENE_ARENA<RHI_INSTANCE>& normals = aLayer.instances[normalArena];
2627 SCENE_ARENA<RHI_INSTANCE>& negatives = aLayer.instances[negativeArena];
2628 uint32_t normal = aGroup.slotStart[normalArena];
2629 uint32_t negative = aGroup.slotStart[negativeArena];
2630
2631 for( const RHI_INSTANCE& source : aGroup.instances[shape] )
2632 {
2633 RHI_INSTANCE& dest = source.HasFlag( RHI_INSTANCE_NEGATIVE ) ? negatives.elements[negative++]
2634 : normals.elements[normal++];
2635 dest = source;
2636 dest.depth = relativeDepth( source.depth, aGroup.baseDepth );
2637
2638 if( opaque )
2639 dest.a = 255;
2640
2641 if( hard )
2643
2644 if( aLayer.overlayAlpha )
2646 }
2647
2648 markSceneDirty( normals, aGroup.slotStart[normalArena], aGroup.slotCount[normalArena] );
2649 markSceneDirty( negatives, aGroup.slotStart[negativeArena], aGroup.slotCount[negativeArena] );
2650 }
2651
2652 uint32_t vertex = aGroup.slotStart[ARENA_POLYGON];
2653
2654 for( const RHI_VERTEX& source : aGroup.polygonVertices )
2655 {
2656 RHI_VERTEX& dest = aLayer.polygons.elements[vertex++];
2657 dest = source;
2658 dest.u = relativeDepth( source.u, aGroup.baseDepth );
2659
2660 if( opaque )
2661 dest.a = 255;
2662 }
2663
2665}
2666
2667
2668static size_t retainedPrefixHash( const RETAINED_GROUP_ORDER& aOrder )
2669{
2670 return hash_val( aOrder.priority, aOrder.bbox[0], aOrder.bbox[1], aOrder.bbox[2], aOrder.bbox[3],
2671 aOrder.lodScale );
2672}
2673
2674
2675void GAL_MODERN::placeGroupInScene( int aGroupId, GAL_GROUP& aGroup, SCENE_LAYER& aLayer )
2676{
2677 countGroupArenas( aGroup, aGroup.slotCount );
2678
2679 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
2680 aGroup.slotStart[arena] = allocSceneSlot( aLayer.instances[arena], aGroup.slotCount[arena] );
2681
2682 aGroup.slotCount[ARENA_POLYGON] = static_cast<uint32_t>( aGroup.polygonVertices.size() );
2684
2685 aGroup.resident = true;
2686 aGroup.residentIndex = static_cast<uint32_t>( aLayer.residents.size() );
2687 aGroup.placeSeq = ++m_placeSeq;
2688 aLayer.residents.push_back( aGroupId );
2689
2690 if( aLayer.retainedPrefixIndex )
2691 aLayer.retainedPrefixIndex->emplace( retainedPrefixHash( aGroup.order ), aGroupId );
2692 aLayer.runsStale = true;
2693
2694 if( aGroup.negative )
2695 aLayer.knockoutGroups++;
2696
2697 if( !aGroup.bitmapQuads.empty() )
2698 aLayer.bitmapResidents.push_back( aGroupId );
2699
2700 countGroupAlpha( aGroup, aLayer, 1 );
2701 aLayer.alphaStale = true;
2702
2703 writeGroupToScene( aGroup, aLayer );
2704 m_sceneChanged = true;
2705}
2706
2707
2708void GAL_MODERN::removeGroupFromScene( int aGroupId, GAL_GROUP& aGroup )
2709{
2710 SCENE_LAYER& layer = m_sceneLayers.at( aGroup.sceneLayer );
2711
2712 if( layer.retainedPrefixIndex )
2713 {
2714 auto [first, last] = layer.retainedPrefixIndex->equal_range( retainedPrefixHash( aGroup.order ) );
2715
2716 for( auto it = first; it != last; ++it )
2717 {
2718 if( it->second == aGroupId )
2719 {
2720 layer.retainedPrefixIndex->erase( it );
2721 break;
2722 }
2723 }
2724 }
2725
2726 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
2727 freeSceneSlot( layer.instances[arena], aGroup.slotStart[arena], aGroup.slotCount[arena] );
2728
2730
2731 const int last = layer.residents.back();
2732
2733 layer.residents[aGroup.residentIndex] = last;
2734 m_groups.at( last ).residentIndex = aGroup.residentIndex;
2735 layer.residents.pop_back();
2736
2737 if( !aGroup.bitmapQuads.empty() )
2738 std::erase( layer.bitmapResidents, aGroupId );
2739
2740 countGroupAlpha( aGroup, layer, -1 );
2741 layer.alphaStale = true;
2742 layer.runsStale = true;
2743
2744 if( aGroup.negative )
2745 layer.knockoutGroups--;
2746
2747 aGroup.resident = false;
2748 m_sceneChanged = true;
2749}
2750
2751
2752bool GAL_MODERN::retainedTieOrderChanged( int aGroupId, GAL_GROUP& aGroup, SCENE_LAYER& aLayer )
2753{
2754 if( !aLayer.retainedPrefixIndex )
2755 {
2756 aLayer.retainedPrefixIndex.emplace();
2757 aLayer.retainedPrefixIndex->reserve( aLayer.residents.size() );
2758
2759 for( int id : aLayer.residents )
2760 aLayer.retainedPrefixIndex->emplace( retainedPrefixHash( m_groups.at( id ).order ), id );
2761 }
2762
2763 uint32_t candidateCounts[ARENA_COUNT] = {};
2764 bool candidateCounted = false;
2765
2766 auto countCandidate = [&]()
2767 {
2768 if( candidateCounted )
2769 return;
2770
2771 countGroupArenas( aGroup, candidateCounts );
2772 candidateCounts[ARENA_POLYGON] = static_cast<uint32_t>( aGroup.polygonVertices.size() );
2773 candidateCounted = true;
2774 };
2775
2776 auto candidateStart = [&]( int aArena )
2777 {
2778 const uint32_t count = candidateCounts[aArena];
2779
2780 if( aArena == ARENA_POLYGON )
2781 {
2782 auto it = aLayer.polygons.freeSlots.find( count );
2783 return it != aLayer.polygons.freeSlots.end() && !it->second.empty()
2784 ? it->second.back()
2785 : static_cast<uint32_t>( aLayer.polygons.elements.size() );
2786 }
2787
2788 const SCENE_ARENA<RHI_INSTANCE>& arena = aLayer.instances[aArena];
2789 auto it = arena.freeSlots.find( count );
2790 return it != arena.freeSlots.end() && !it->second.empty()
2791 ? it->second.back()
2792 : static_cast<uint32_t>( arena.elements.size() );
2793 };
2794
2795 auto [first, last] = aLayer.retainedPrefixIndex->equal_range( retainedPrefixHash( aGroup.order ) );
2796
2797 for( auto it = first; it != last; ++it )
2798 {
2799 const int peerId = it->second;
2800
2801 if( peerId == aGroupId )
2802 continue;
2803
2804 GAL_GROUP& peer = m_groups.at( peerId );
2805
2806 if( !aGroup.order.SamePrefix( peer.order ) )
2807 continue;
2808
2809 aGroup.order.contentTie = true;
2810 peer.order.contentTie = true;
2811 aGroup.order.contentHash = GroupContentFingerprint( aGroupId );
2812 peer.order.contentHash = GroupContentFingerprint( peerId );
2813
2814 if( !aGroup.resident )
2815 {
2816 if( !( aGroup.order < peer.order ) && !( peer.order < aGroup.order ) )
2817 continue;
2818
2819 countCandidate();
2820
2821 for( int arena = 0; arena <= ARENA_POLYGON; ++arena )
2822 {
2823 if( candidateCounts[arena] == 0 || peer.slotCount[arena] == 0 )
2824 continue;
2825
2826 if( ( candidateStart( arena ) < peer.slotStart[arena] ) != ( aGroup.order < peer.order ) )
2827 return true;
2828 }
2829
2830 continue;
2831 }
2832
2833 if( !( aGroup.order < peer.order ) && !( peer.order < aGroup.order ) )
2834 continue;
2835
2836 for( int arena = 0; arena <= ARENA_POLYGON; ++arena )
2837 {
2838 if( aGroup.slotCount[arena] == 0 || peer.slotCount[arena] == 0 )
2839 continue;
2840
2841 if( ( aGroup.slotStart[arena] < peer.slotStart[arena] ) != ( aGroup.order < peer.order ) )
2842 return true;
2843 }
2844 }
2845
2846 return false;
2847}
2848
2849
2851{
2852 auto reset = []( auto& aArena )
2853 {
2854 aArena.elements.clear();
2855 aArena.freeSlots.clear();
2856 aArena.dirty.clear();
2857 aArena.dead = 0;
2858 aArena.uploadAll = true;
2859 };
2860
2861 for( auto& [id, layer] : m_sceneLayers )
2862 {
2863 for( SCENE_ARENA<RHI_INSTANCE>& arena : layer.instances )
2864 reset( arena );
2865
2866 reset( layer.polygons );
2867
2868 layer.residents.clear();
2869 layer.bitmapResidents.clear();
2870 layer.retainedPrefixIndex.reset();
2871 std::fill( std::begin( layer.alphaCount ), std::end( layer.alphaCount ), 0 );
2872 layer.bakedAlpha = -1;
2873 layer.alphaStale = false;
2874 layer.translucent = false;
2875 layer.runs.clear();
2876 layer.runsStale = false;
2877 layer.knockoutGroups = 0;
2878 }
2879
2880 for( auto& [id, group] : m_groups )
2881 group.resident = false;
2882
2883 m_openSceneLayer = nullptr;
2884 m_openSceneLayerId = -1;
2885 m_sceneChanged = true;
2886}
2887
2888
2889void GAL_MODERN::SetSceneLayerState( int aLayer, int aOrder, bool aVisible, bool aHasNegatives )
2890{
2891 SCENE_LAYER& layer = m_sceneLayers[aLayer];
2892 const float depth = static_cast<float>( aOrder );
2893
2894 if( layer.inOrder && layer.depth == depth && layer.visible == aVisible
2895 && layer.hasNegatives == aHasNegatives )
2896 {
2897 return;
2898 }
2899
2900 if( !layer.inOrder )
2901 m_sceneOrder.push_back( aLayer );
2902
2903 if( !layer.inOrder || layer.depth != depth )
2904 m_sceneOrderStale = true;
2905
2906 // A hidden layer is not uploaded, so one that comes back has all of it to send
2907 if( aVisible && !layer.visible )
2908 {
2909 for( SCENE_ARENA<RHI_INSTANCE>& arena : layer.instances )
2910 arena.uploadAll = true;
2911
2912 layer.polygons.uploadAll = true;
2913 }
2914
2915 const bool rewrite = layer.inOrder && layer.hasNegatives != aHasNegatives;
2916
2917 if( layer.hasNegatives != aHasNegatives )
2918 layer.runsStale = true;
2919
2920 layer.inOrder = true;
2921 layer.depth = depth;
2922 layer.visible = aVisible;
2923 layer.hasNegatives = aHasNegatives;
2924 m_sceneChanged = true;
2925
2926 // Whether the layer has knockouts decides how its elements' edges are drawn
2927 if( rewrite && !m_staticDirty )
2928 {
2929 for( int groupId : layer.residents )
2930 writeGroupToScene( m_groups.at( groupId ), layer );
2931 }
2932}
2933
2934
2935void GAL_MODERN::SetSceneLayerDim( int aLayer, const COLOR4D& aToward, double aKeep )
2936{
2937 // Without a fade the colour it would fade towards does not matter, so all such dims are equal
2938 RHI_LAYER_DIM dim;
2939
2940 if( aKeep < 1.0 )
2941 {
2942 dim.toward[0] = static_cast<float>( aToward.r );
2943 dim.toward[1] = static_cast<float>( aToward.g );
2944 dim.toward[2] = static_cast<float>( aToward.b );
2945 dim.keep = static_cast<float>( std::max( aKeep, 0.0 ) );
2946 }
2947
2948 auto it = m_sceneLayers.find( aLayer );
2949
2950 if( it == m_sceneLayers.end() && dim == RHI_LAYER_DIM() )
2951 return;
2952
2953 SCENE_LAYER& layer = it != m_sceneLayers.end() ? it->second : m_sceneLayers[aLayer];
2954
2955 if( layer.dim == dim )
2956 return;
2957
2958 layer.dim = dim;
2959 m_sceneChanged = true;
2960}
2961
2962
2964{
2965 SCENE_LAYER& layer = m_sceneLayers[aLayer];
2966
2967 if( layer.overlayAlpha )
2968 return;
2969
2970 layer.overlayAlpha = true;
2971
2972 for( int groupId : layer.residents )
2973 writeGroupToScene( m_groups.at( groupId ), layer );
2974
2975 m_sceneChanged = true;
2976}
2977
2978
2980{
2981 // Far layers first, as VIEW orders them
2982 std::stable_sort( m_sceneOrder.begin(), m_sceneOrder.end(),
2983 [this]( int aLhs, int aRhs )
2984 {
2985 return m_sceneLayers.at( aLhs ).depth > m_sceneLayers.at( aRhs ).depth;
2986 } );
2987
2988 m_sceneOrderStale = false;
2989}
2990
2991
2993{
2994 if( !m_isDrawing || !m_staticDirty )
2995 return;
2996
2997 // A caller that does not describe its layers gets them in the order it opens them
2998 if( !m_sceneLayers.contains( aLayer ) || !m_sceneLayers.at( aLayer ).inOrder )
2999 SetSceneLayerState( aLayer, static_cast<int>( m_layerDepth ), true, m_negativesLayer );
3000
3001 m_openSceneLayer = &m_sceneLayers.at( aLayer );
3002 m_openSceneLayerId = aLayer;
3003}
3004
3005
3006void GAL_MODERN::trackGroupLod( int aGroupId, GAL_GROUP& aGroup, bool aTrack )
3007{
3008 // The list is ordered by threshold, so a group whose threshold changed moves
3009 if( aGroup.lodEntry && ( !aTrack || ( *aGroup.lodEntry )->first != aGroup.lodScale ) )
3010 {
3011 m_lodGroups.erase( *aGroup.lodEntry );
3012 aGroup.lodEntry.reset();
3013 }
3014
3015 if( aTrack && !aGroup.lodEntry )
3016 aGroup.lodEntry = m_lodGroups.emplace( aGroup.lodScale, aGroupId );
3017}
3018
3019
3020void GAL_MODERN::SetSceneScale( double aScale )
3021{
3022 if( aScale == m_sceneScale )
3023 return;
3024
3025 const double previous = m_sceneScale;
3026 m_sceneScale = aScale;
3027
3028 // A pending rebuild places everything against the new scale anyway
3029 if( m_staticDirty )
3030 return;
3031
3032 // Every other group already matches the scale, so only those whose threshold lies between the
3033 // two scales change. A zoom step crosses few of the many thresholds a board has.
3034 auto first = m_lodGroups.lower_bound( std::min( previous, aScale ) );
3035 auto last = m_lodGroups.lower_bound( std::max( previous, aScale ) );
3036 std::vector<int> crossed;
3037
3038 for( auto entry = first; entry != last; ++entry )
3039 crossed.push_back( entry->second );
3040
3041 std::sort( crossed.begin(), crossed.end(), [&]( int a, int b )
3042 {
3043 const GAL_GROUP& lhs = m_groups.at( a );
3044 const GAL_GROUP& rhs = m_groups.at( b );
3045
3046 if( lhs.lodScale != rhs.lodScale )
3047 return lhs.lodScale < rhs.lodScale;
3048
3049 if( lhs.sceneLayer != rhs.sceneLayer )
3050 return lhs.sceneLayer < rhs.sceneLayer;
3051
3052 return lhs.order < rhs.order;
3053 } );
3054
3055 // Free lists reuse same-size slots from the end, so hide in reverse placement order.
3056 if( aScale < previous )
3057 std::reverse( crossed.begin(), crossed.end() );
3058
3059 for( int groupId : crossed )
3060 {
3061 GAL_GROUP& group = m_groups.at( groupId );
3062 const bool wanted = group.lodScale < m_sceneScale;
3063
3064 if( wanted == group.resident )
3065 continue;
3066
3067 if( !wanted )
3068 {
3069 removeGroupFromScene( groupId, group );
3070 continue;
3071 }
3072
3073 auto layerIt = m_sceneLayers.find( group.sceneLayer );
3074
3075 if( layerIt == m_sceneLayers.end() || !layerIt->second.inOrder )
3076 {
3077 m_staticDirty = true;
3078 return;
3079 }
3080
3081 placeGroupInScene( groupId, group, layerIt->second );
3082 }
3083}
3084
3085
3087{
3089 return false;
3090
3091 // A failed upload leaves the scene for the next drawn frame to rebuild and report
3092 if( !flushScene() )
3093 m_staticDirty = true;
3094
3095 m_sceneChanged = false;
3096 return true;
3097}
3098
3099
3100void GAL_MODERN::SetGroupPlacement( int aGroup, int aLayer, double aLodScale, bool aShown,
3101 const RETAINED_GROUP_ORDER& aOrder )
3102{
3103 auto it = m_groups.find( aGroup );
3104
3105 if( it == m_groups.end() )
3106 return;
3107
3108 GAL_GROUP& group = it->second;
3109 const bool wanted = aShown && aLodScale < m_sceneScale;
3110 RETAINED_GROUP_ORDER order = aOrder;
3111
3112 if( group.order.contentTie && !order.contentTie && group.order.SamePrefix( order ) )
3113 {
3114 order.contentTie = true;
3115 order.contentHash = GroupContentFingerprint( aGroup );
3116 }
3117
3118 if( group.resident && ( !wanted || group.sceneLayer != aLayer ) )
3119 removeGroupFromScene( aGroup, group );
3120
3121 if( group.resident && !group.order.SamePrefix( order ) )
3122 {
3123 m_sceneLayers.at( group.sceneLayer ).retainedPrefixIndex.reset();
3124 m_staticDirty = true;
3125 }
3126
3127 group.sceneLayer = aLayer;
3128 group.lodScale = aLodScale;
3129 group.shown = aShown;
3130 group.order = std::move( order );
3131
3132 trackGroupLod( aGroup, group, aShown && aLodScale > 0.0 && aLodScale < std::numeric_limits<double>::max() );
3133
3134 if( !wanted || group.resident )
3135 return;
3136
3137 if( m_staticDirty )
3138 {
3139 // Outside the rebuild itself the group waits for it
3140 if( m_openSceneLayer && m_openSceneLayerId == aLayer )
3142
3143 return;
3144 }
3145
3146 auto layerIt = m_sceneLayers.find( aLayer );
3147
3148 // Only a rebuild knows where a layer the scene has not drawn yet sits in the draw order
3149 if( layerIt == m_sceneLayers.end() || !layerIt->second.inOrder )
3150 {
3151 m_staticDirty = true;
3152 return;
3153 }
3154
3155 if( retainedTieOrderChanged( aGroup, group, layerIt->second ) )
3156 {
3157 m_staticDirty = true;
3158 return;
3159 }
3160
3161 placeGroupInScene( aGroup, group, layerIt->second );
3162}
3163
3164
3165template <typename T>
3166void GAL_MODERN::submitSpan( const std::vector<T>& aElements, RHI_BufferHandle aBuffer, const PATCH_SPAN& aSpan )
3167{
3168 const size_t bytes = static_cast<size_t>( aSpan.end - aSpan.begin ) * sizeof( T );
3169
3170 RHI_GEOMETRY_UPLOAD upload;
3171 RhiUploadCopyStaging( upload, &aElements[aSpan.begin], bytes );
3172 upload.targetBuffer = aBuffer;
3173 upload.targetOffset = static_cast<size_t>( aSpan.begin ) * sizeof( T );
3174 m_renderThread->SubmitUpload( std::move( upload ) );
3175}
3176
3177
3178template <typename T>
3180{
3181 const size_t count = aArena.elements.size();
3182
3183 // A stale upload flag on an empty arena would mark its layer edited on every flush
3184 if( count == 0 )
3185 {
3186 aArena.dirty.clear();
3187 aArena.uploadAll = false;
3188 return true;
3189 }
3190
3191 if( aArena.capacity < count )
3192 {
3193 // Headroom lets a layer take new items without reallocating and re-uploading itself
3194 const size_t wanted = std::max( count + count / 4 + 64, aArena.capacity * 2 );
3195
3196 if( m_renderThread && !ensureBuffer( aArena.buffer, aArena.capacity, wanted, sizeof( T ) ) )
3197 return false;
3198
3199 if( !m_renderThread )
3200 aArena.capacity = wanted;
3201
3202 aArena.uploadAll = true;
3203 }
3204
3205 if( aArena.uploadAll )
3206 {
3207 aArena.dirty.clear();
3208 aArena.dirty.emplace_back();
3209 aArena.dirty.back().add( 0, static_cast<uint32_t>( count ) );
3210 }
3211
3212 for( PATCH_SPAN span : aArena.dirty )
3213 {
3214 // A slot freed off the end of the arena after it was written leaves a span past the end
3215 span.end = std::min<uint32_t>( span.end, static_cast<uint32_t>( count ) );
3216
3217 if( span.empty() )
3218 continue;
3219
3220 if( m_renderThread && aArena.buffer )
3221 submitSpan( aArena.elements, aArena.buffer, span );
3222 }
3223
3224 aArena.dirty.clear();
3225 aArena.uploadAll = false;
3226 return true;
3227}
3228
3229
3230std::shared_ptr<const RHI_RUN_TABLE> GAL_MODERN::updateSceneCullRuns( SCENE_LAYER& aLayer )
3231{
3232 auto changed = []( const auto& aArena, size_t aCounted )
3233 {
3234 return !aArena.dirty.empty() || aArena.elements.size() != aCounted
3235 || ( aArena.uploadAll && !aArena.elements.empty() );
3236 };
3237
3238 bool any = !aLayer.cullRuns || changed( aLayer.polygons, aLayer.cullCounted[ARENA_POLYGON] );
3239
3240 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
3241 any = any || changed( aLayer.instances[arena], aLayer.cullCounted[arena] );
3242
3243 if( !any )
3244 return nullptr;
3245
3246 auto table =
3247 aLayer.cullRuns ? std::make_shared<RHI_RUN_TABLE>( *aLayer.cullRuns ) : std::make_shared<RHI_RUN_TABLE>();
3248
3249 auto spans = []( const auto& aArena )
3250 {
3251 std::vector<std::pair<uint32_t, uint32_t>> out;
3252
3253 for( const auto& span : aArena.dirty )
3254 out.emplace_back( span.begin, span.end );
3255
3256 return out;
3257 };
3258
3259 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
3260 {
3261 const SCENE_ARENA<RHI_INSTANCE>& instances = aLayer.instances[arena];
3262 const RHI_SHAPE shape = RHI_SHAPE( arena / 2 );
3263 std::vector<RHI_RUN_BOUNDS>& runs = arena % 2 ? table->negRuns[shape] : table->runs[shape];
3264
3265 updateRuns( runs, instances.elements, spans( instances ), aLayer.cullCounted[arena],
3266 !aLayer.cullRuns || instances.uploadAll, RHI_CULL_RUN_LENGTH,
3267 [shape]( const RHI_INSTANCE* aFirst, size_t aCount )
3268 {
3269 return RhiInstanceRun( aFirst, aCount, shape );
3270 } );
3271
3272 aLayer.cullCounted[arena] = instances.elements.size();
3273 }
3274
3275 updateRuns( table->runs[RHI_SHAPE_POLYGON], aLayer.polygons.elements, spans( aLayer.polygons ),
3276 aLayer.cullCounted[ARENA_POLYGON],
3278
3279 aLayer.cullCounted[ARENA_POLYGON] = aLayer.polygons.elements.size();
3280 return table;
3281}
3282
3283
3285{
3286 // Past this many dead elements an arena that is mostly holes is repacked
3287 static constexpr uint32_t COMPACT_MIN_DEAD = 256;
3288
3289 bool ok = true;
3290
3291 m_openSceneLayer = nullptr;
3292 m_openSceneLayerId = -1;
3293
3294 auto wantsCompaction = []( const auto& aArena )
3295 {
3296 return aArena.dead > COMPACT_MIN_DEAD && static_cast<size_t>( aArena.dead ) * 2 > aArena.elements.size();
3297 };
3298
3299 if( m_sceneOrderStale )
3301
3302 for( int layerId : m_sceneOrder )
3303 {
3304 SCENE_LAYER& layer = m_sceneLayers.at( layerId );
3305
3306 // Edits to a hidden layer stay in its arenas until it is shown
3307 if( !layer.visible )
3308 continue;
3309
3310 if( layer.alphaStale )
3311 {
3312 int minAlpha = 256;
3313 int maxAlpha = -1;
3314
3315 for( int alpha = 0; alpha < 256; ++alpha )
3316 {
3317 if( layer.alphaCount[alpha] > 0 )
3318 {
3319 minAlpha = std::min( minAlpha, alpha );
3320 maxAlpha = std::max( maxAlpha, alpha );
3321 }
3322 }
3323
3324 // Same rule as the full rebuild, so one translucent alpha shared by the whole layer
3325 // is applied once at composite
3326 const int hoisted = ( maxAlpha >= 0 && minAlpha == maxAlpha && minAlpha < 255 ) ? minAlpha : -1;
3327
3328 const bool translucent = minAlpha < 255;
3329
3330 if( ( hoisted >= 0 ) != ( layer.bakedAlpha >= 0 ) || translucent != layer.translucent )
3331 {
3332 layer.bakedAlpha = hoisted;
3333 layer.translucent = translucent;
3334
3335 for( int groupId : layer.residents )
3336 writeGroupToScene( m_groups.at( groupId ), layer );
3337 }
3338
3339 layer.bakedAlpha = hoisted;
3340 layer.translucent = translucent;
3341 layer.alphaStale = false;
3342 }
3343
3344 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
3345 {
3346 if( wantsCompaction( layer.instances[arena] ) )
3347 {
3348 compactSceneArena( layer, layer.instances[arena], arena );
3349 layer.runsStale = true;
3350 }
3351 }
3352
3353 if( wantsCompaction( layer.polygons ) )
3354 {
3355 compactSceneArena( layer, layer.polygons, ARENA_POLYGON );
3356 layer.runsStale = true;
3357 }
3358
3359 if( layer.runsStale )
3360 {
3361 const bool composited = sceneLayerComposited( layer );
3362
3363 buildSceneRuns( layer );
3364
3365 // Going through the temp target or not decides how the elements carry their alpha
3366 if( sceneLayerComposited( layer ) != composited )
3367 {
3368 for( int groupId : layer.residents )
3369 writeGroupToScene( m_groups.at( groupId ), layer );
3370 }
3371 }
3372
3373 if( std::shared_ptr<const RHI_RUN_TABLE> runs = updateSceneCullRuns( layer ) )
3374 layer.cullRuns = std::move( runs );
3375
3376 for( int arena = 0; arena < ARENA_INSTANCE_COUNT; ++arena )
3377 ok = uploadSceneArena( layer.instances[arena] ) && ok;
3378
3379 ok = uploadSceneArena( layer.polygons ) && ok;
3380 }
3381
3382 return ok;
3383}
3384
3385
3387{
3388 if( m_sceneDrawOrdered == aOrdered )
3389 return;
3390
3391 m_sceneDrawOrdered = aOrdered;
3392
3393 for( auto& [id, layer] : m_sceneLayers )
3394 layer.runsStale = layer.runsStale || layer.knockoutGroups > 0;
3395
3396 m_sceneChanged = true;
3397}
3398
3399
3401{
3402 aLayer.runs.clear();
3403 aLayer.runsStale = false;
3404
3405 // The groups say whether the layer has knockouts. The view's flag for it is not set on
3406 // every path that loads a Gerber file.
3407 if( aLayer.knockoutGroups == 0 || !m_sceneDrawOrdered )
3408 return;
3409
3410 std::vector<const GAL_GROUP*> placed;
3411 placed.reserve( aLayer.residents.size() );
3412
3413 for( int groupId : aLayer.residents )
3414 placed.push_back( &m_groups.at( groupId ) );
3415
3416 std::sort( placed.begin(), placed.end(),
3417 []( const GAL_GROUP* aLhs, const GAL_GROUP* aRhs )
3418 {
3419 return aLhs->placeSeq < aRhs->placeSeq;
3420 } );
3421
3422 // Order matters once something dark follows a knockout. Polygons need the runs too, a
3423 // clear one is told from a dark one only by the run it is in.
3424 bool ordered = false;
3425 bool cleared = false;
3426
3427 auto joins = [&]( const POLARITY_RUN& aLast, const POLARITY_RUN& aNext )
3428 {
3429 for( int shape = 0; shape < RHI_SHAPES; ++shape )
3430 {
3431 if( aNext.count[shape] != 0 && aLast.count[shape] != 0
3432 && aLast.start[shape] + aLast.count[shape] != aNext.start[shape] )
3433 {
3434 return false;
3435 }
3436 }
3437
3438 return aLast.negative == aNext.negative;
3439 };
3440
3441 auto add = [&]( const POLARITY_RUN& aNext )
3442 {
3443 if( aNext.Empty() )
3444 return;
3445
3446 if( aNext.negative )
3447 ordered = ordered || aNext.count[RHI_SHAPE_POLYGON] > 0;
3448 else
3449 ordered = ordered || cleared;
3450
3451 cleared = cleared || aNext.negative;
3452
3453 if( aLayer.runs.empty() || !joins( aLayer.runs.back(), aNext ) )
3454 {
3455 aLayer.runs.push_back( aNext );
3456 return;
3457 }
3458
3459 POLARITY_RUN& last = aLayer.runs.back();
3460
3461 for( int shape = 0; shape < RHI_SHAPES; ++shape )
3462 {
3463 if( last.count[shape] == 0 )
3464 last.start[shape] = aNext.start[shape];
3465
3466 last.count[shape] += aNext.count[shape];
3467 }
3468 };
3469
3470 for( const GAL_GROUP* group : placed )
3471 {
3472 // Normal elements sit in one set of arenas and knockouts in another. A group's polygons
3473 // carry no flag of their own and take the polarity the group was recorded in.
3474 POLARITY_RUN dark;
3476 clear.negative = true;
3477
3478 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
3479 {
3480 dark.start[shape] = group->slotStart[arenaOf( shape, false )];
3481 dark.count[shape] = group->slotCount[arenaOf( shape, false )];
3482 clear.start[shape] = group->slotStart[arenaOf( shape, true )];
3483 clear.count[shape] = group->slotCount[arenaOf( shape, true )];
3484 }
3485
3486 POLARITY_RUN& polygons = group->negative ? clear : dark;
3487 polygons.start[RHI_SHAPE_POLYGON] = group->slotStart[ARENA_POLYGON];
3488 polygons.count[RHI_SHAPE_POLYGON] = group->slotCount[ARENA_POLYGON];
3489
3490 add( dark );
3491 add( clear );
3492 }
3493
3494 if( !ordered )
3495 aLayer.runs.clear();
3496}
3497
3498
3499bool GAL_MODERN::assignSlicesByOrder( std::vector<LAYER_RANGE>& aRanges, size_t aFirst ) const
3500{
3501 float lowest = std::numeric_limits<float>::max();
3502 float highest = std::numeric_limits<float>::lowest();
3503
3504 for( const auto& [id, layer] : m_sceneLayers )
3505 {
3506 if( layer.inOrder )
3507 {
3508 lowest = std::min( lowest, layer.depth );
3509 highest = std::max( highest, layer.depth );
3510 }
3511 }
3512
3513 if( lowest > highest )
3514 return false;
3515
3516 // A layer takes one unit of order around its own, so the tenth steps a group advances by
3517 // sort between their layer and the next. A unit of margin keeps those steps inside.
3518 const float base = lowest - 1.0f;
3519 const float span = highest - lowest + 2.0f;
3520
3521 for( size_t ii = aFirst; ii < aRanges.size(); ++ii )
3522 {
3523 LAYER_RANGE& range = aRanges[ii];
3524
3525 range.depthNear = std::clamp( ( range.depth - 0.5f - base ) / span, 0.0f, 1.0f );
3526 range.depthFar = std::clamp( ( range.depth + 0.5f - base ) / span, 0.0f, 1.0f );
3527 }
3528
3529 return true;
3530}
3531
3532
3534{
3535 auto count = []( const auto& aArena )
3536 {
3537 return static_cast<uint32_t>( aArena.elements.size() );
3538 };
3539
3540 const size_t firstRange = aDesc.layerRanges.size();
3541
3542 for( int layerId : m_sceneOrder )
3543 {
3544 const SCENE_LAYER& layer = m_sceneLayers.at( layerId );
3545
3546 if( !layer.visible )
3547 continue;
3548
3549 LAYER_RANGE range;
3550 range.depth = layer.depth;
3551 const bool composited = sceneLayerComposited( layer );
3552
3553 range.alpha = ( layer.bakedAlpha >= 0 && composited )
3554 ? static_cast<float>( layer.bakedAlpha ) / 255.0f
3555 : 1.0f;
3556 range.hasNegatives = layer.hasNegatives || !layer.runs.empty();
3557 range.target = TARGET_CACHED;
3558
3559 range.singleCoverage = !composited && layer.translucent;
3560 range.depthKnockout = !composited && layer.hasNegatives;
3561 range.dim = layer.dim;
3562 range.overlayAlpha = layer.overlayAlpha;
3563
3564 range.runStart = static_cast<uint32_t>( aDesc.polarityRuns.size() );
3565 range.runCount = static_cast<uint32_t>( layer.runs.size() );
3566 aDesc.polarityRuns.insert( aDesc.polarityRuns.end(), layer.runs.begin(), layer.runs.end() );
3567
3568 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
3569 {
3570 const SCENE_ARENA<RHI_INSTANCE>& normals = layer.instances[arenaOf( shape, false )];
3571 const SCENE_ARENA<RHI_INSTANCE>& negatives = layer.instances[arenaOf( shape, true )];
3572
3573 range.count[shape] = count( normals );
3574 range.negCount[shape] = count( negatives );
3575 range.buffer[shape] = normals.buffer;
3576 range.negBuffer[shape] = negatives.buffer;
3577 }
3578
3579 range.count[RHI_SHAPE_POLYGON] = count( layer.polygons );
3580 range.buffer[RHI_SHAPE_POLYGON] = layer.polygons.buffer;
3581 range.cullRuns = layer.cullRuns;
3582
3583 // A placed group's bitmaps are drawn with its layer. The quads stay owned by the group,
3584 // and the descriptor is fetched again whenever the scene changes.
3585 range.bitmapStart = static_cast<uint32_t>( aDesc.bitmapDraws.size() );
3586
3587 for( int groupId : layer.bitmapResidents )
3588 {
3589 for( const BITMAP_QUAD& bq : m_groups.at( groupId ).bitmapQuads )
3590 {
3591 if( bq.texture && bq.vertexBuffer )
3592 aDesc.bitmapDraws.push_back( { bq.vertexBuffer, bq.texture, 6 } );
3593 }
3594 }
3595
3596 range.bitmapCount = static_cast<uint32_t>( aDesc.bitmapDraws.size() ) - range.bitmapStart;
3597
3598 if( !range.HasGeometry() )
3599 continue;
3600
3601 for( int shape = 0; shape < RHI_SHAPES; ++shape )
3602 {
3603 aDesc.count[shape] += range.count[shape];
3604 aDesc.negCount[shape] += range.negCount[shape];
3605 }
3606
3607 aDesc.layerRanges.push_back( range );
3608 }
3609
3610 const size_t slices = aDesc.layerRanges.size() - firstRange;
3611
3612 if( slices == 0 )
3613 return;
3614
3615 aDesc.relativeDepth = true;
3616
3617 if( assignSlicesByOrder( aDesc.layerRanges, firstRange ) )
3618 return;
3619
3620 // Later layers are nearer, so each takes the slice in front of the one before it
3621 for( size_t ii = 0; ii < slices; ++ii )
3622 {
3623 LAYER_RANGE& range = aDesc.layerRanges[firstRange + ii];
3624
3625 range.depthFar = 1.0f - static_cast<float>( ii ) / static_cast<float>( slices );
3626 range.depthNear = 1.0f - static_cast<float>( ii + 1 ) / static_cast<float>( slices );
3627 }
3628}
3629
3630
3631template <typename GROUP, typename INSTANCE_FN, typename VERTEX_FN>
3632static void forEachElement( GROUP& aGroup, INSTANCE_FN aInstance, VERTEX_FN aVertex )
3633{
3634 for( std::vector<RHI_INSTANCE>& instances : aGroup.instances )
3635 std::ranges::for_each( instances, aInstance );
3636
3637 std::ranges::for_each( aGroup.polygonVertices, aVertex );
3638}
3639
3640
3641template <typename INSTANCE_FN, typename VERTEX_FN>
3642bool GAL_MODERN::patchStaticGroup( int aGroup, INSTANCE_FN aInstance, VERTEX_FN aVertex )
3643{
3644 auto slotIt = m_staticGroupSlots.find( aGroup );
3645
3646 if( slotIt == m_staticGroupSlots.end() )
3647 return false;
3648
3649 const RHI_SHAPE_COUNTS sizes = m_staticPool.Sizes();
3650
3651 for( const STATIC_GROUP_SLOT& slot : slotIt->second )
3652 {
3653 // A slot from an earlier build is stale once the pool shrank, so drop the group's slots and
3654 // let the caller rebuild rather than write out of bounds
3655 for( int shape = 0; shape < RHI_SHAPES; ++shape )
3656 {
3657 if( static_cast<size_t>( slot.start[shape] ) + slot.count[shape] > sizes[shape] )
3658 {
3659 m_staticGroupSlots.erase( slotIt );
3660 return false;
3661 }
3662 }
3663
3664 for( int shape = 0; shape < RHI_INSTANCED_SHAPES; ++shape )
3665 {
3666 auto first = m_staticPool.instances[shape].begin() + slot.start[shape];
3667 std::for_each( first, first + slot.count[shape], aInstance );
3668 }
3669
3670 auto first = m_staticPool.polygonVertices.begin() + slot.start[RHI_SHAPE_POLYGON];
3671 std::for_each( first, first + slot.count[RHI_SHAPE_POLYGON], aVertex );
3672
3673 for( int shape = 0; shape < RHI_SHAPES; ++shape )
3674 m_staticPatches[shape].add( slot.start[shape], slot.count[shape] );
3675 }
3676
3677 return true;
3678}
3679
3680
3681void GAL_MODERN::rewriteResidentGroup( int aGroupNumber, GAL_GROUP& aGroup )
3682{
3683 // A pending rebuild re-places every group, so patching the old scene would be wasted
3684 if( !aGroup.resident || m_staticDirty )
3685 return;
3686
3687 SCENE_LAYER& layer = m_sceneLayers.at( aGroup.sceneLayer );
3688
3689 if( aGroup.order.contentTie && retainedTieOrderChanged( aGroupNumber, aGroup, layer ) )
3690 {
3691 m_staticDirty = true;
3692 return;
3693 }
3694
3695 writeGroupToScene( aGroup, layer );
3696 m_sceneChanged = true;
3697}
3698
3699
3700void GAL_MODERN::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
3701{
3702 auto it = m_groups.find( aGroupNumber );
3703
3704 if( it == m_groups.end() )
3705 return;
3706
3707 GAL_GROUP& group = it->second;
3708
3709 const uint8_t cr = packColorChannel( aNewColor.r );
3710 const uint8_t cg = packColorChannel( aNewColor.g );
3711 const uint8_t cb = packColorChannel( aNewColor.b );
3712 const uint8_t ca = packColorChannel( aNewColor.a );
3713
3714 // A recolour of every layer asks for the colour most groups already have. The groups that
3715 // come here are painted in one colour, so their first element speaks for the rest.
3716 auto unchanged = [&]( const auto& aElements )
3717 {
3718 return !aElements.empty() && aElements.front().r == cr && aElements.front().g == cg
3719 && aElements.front().b == cb && aElements.front().a == ca;
3720 };
3721
3722 if( std::ranges::any_of( group.instances, unchanged ) || unchanged( group.polygonVertices ) )
3723 return;
3724
3725 SCENE_LAYER* sceneLayer = ( group.resident && !m_staticDirty ) ? &m_sceneLayers.at( group.sceneLayer )
3726 : nullptr;
3727
3728 if( sceneLayer )
3729 countGroupAlpha( group, *sceneLayer, -1 );
3730
3732 group,
3733 [&]( RHI_INSTANCE& aInstance )
3734 {
3735 aInstance.SetColor( cr, cg, cb, aInstance.HasFlag( RHI_INSTANCE_NEGATIVE ) ? 255 : ca );
3736 },
3737 [&]( RHI_VERTEX& aVertex )
3738 {
3739 aVertex.SetColor( cr, cg, cb, group.negative ? 255 : ca );
3740 } );
3741
3742 group.contentFingerprintValid = false;
3743
3744 if( group.order.contentTie )
3745 group.order.contentHash = GroupContentFingerprint( aGroupNumber );
3746
3747 if( sceneLayer )
3748 {
3749 countGroupAlpha( group, *sceneLayer, 1 );
3750 sceneLayer->alphaStale = true;
3751 }
3752
3753 rewriteResidentGroup( aGroupNumber, group );
3754
3755 // A group VIEW has placed lives in the retained scene, or is being kept out of it
3756 if( group.sceneLayer >= 0 )
3757 return;
3758
3759 // Patch the static pool in place, or rebuild when the group has no slot there yet
3760 auto recolor = [&]( auto& aElement )
3761 {
3762 aElement.SetColor( cr, cg, cb, ca );
3763 };
3764
3765 if( !patchStaticGroup( aGroupNumber, recolor, recolor ) )
3766 m_staticDirty = true;
3767}
3768
3769
3770void GAL_MODERN::ChangeGroupDepth( int aGroupNumber, int aDepth )
3771{
3772 auto it = m_groups.find( aGroupNumber );
3773
3774 if( it == m_groups.end() )
3775 return;
3776
3777 GAL_GROUP& group = it->second;
3778 const float depth = static_cast<float>( aDepth );
3779
3780 // The zone shader reads the layer depth from u
3781 auto setInstanceDepth = [depth]( RHI_INSTANCE& aInstance )
3782 {
3783 aInstance.SetDepth( depth );
3784 };
3785
3786 auto setVertexDepth = [depth]( RHI_VERTEX& aVertex )
3787 {
3788 aVertex.u = glm::packHalf1x16( depth );
3789 };
3790
3791 forEachElement( group, setInstanceDepth, setVertexDepth );
3792
3793 group.baseDepth = depth;
3794 group.contentFingerprintValid = false;
3795
3796 if( group.order.contentTie )
3797 group.order.contentHash = GroupContentFingerprint( aGroupNumber );
3798
3799 rewriteResidentGroup( aGroupNumber, group );
3800
3801 if( group.sceneLayer >= 0 )
3802 return;
3803
3804 if( !patchStaticGroup( aGroupNumber, setInstanceDepth, setVertexDepth ) )
3805 m_staticDirty = true;
3806}
3807
3808
3809void GAL_MODERN::ChangeGroupUndimmed( int aGroupNumber, bool aUndimmed )
3810{
3811 auto it = m_groups.find( aGroupNumber );
3812
3813 if( it == m_groups.end() )
3814 return;
3815
3816 GAL_GROUP& group = it->second;
3817
3818 if( group.undimmed == aUndimmed )
3819 return;
3820
3821 group.undimmed = aUndimmed;
3822
3823 // The zone shader reads the exemption from v
3825 group,
3826 [&]( RHI_INSTANCE& aInstance )
3827 {
3828 if( aUndimmed )
3829 aInstance.SetFlag( RHI_INSTANCE_UNDIMMED );
3830 else
3831 aInstance.ClearFlag( RHI_INSTANCE_UNDIMMED );
3832 },
3833 [&]( RHI_VERTEX& aVertex )
3834 {
3835 aVertex.v = glm::packHalf1x16( aUndimmed ? 1.0f : 0.0f );
3836 } );
3837
3838 group.contentFingerprintValid = false;
3839
3840 if( group.order.contentTie )
3841 group.order.contentHash = GroupContentFingerprint( aGroupNumber );
3842
3843 rewriteResidentGroup( aGroupNumber, group );
3844}
3845
3846
3847void GAL_MODERN::DeleteGroup( int aGroupNumber )
3848{
3849 auto it = m_groups.find( aGroupNumber );
3850
3851 if( it == m_groups.end() )
3852 return;
3853
3854 if( it->second.resident )
3855 removeGroupFromScene( aGroupNumber, it->second );
3856
3857 trackGroupLod( aGroupNumber, it->second, false );
3858 countBitmapRefs( it->second, -1 );
3859 releaseBitmapQuads( it->second.bitmapQuads, false );
3860
3861 m_groups.erase( it );
3862}
3863
3864
3865void GAL_MODERN::countBitmapRefs( const GAL_GROUP& aGroup, int aDelta )
3866{
3867 for( const BITMAP_QUAD& bq : aGroup.bitmapQuads )
3868 {
3869 if( bq.ownsTexture || !bq.texture )
3870 continue;
3871
3872 for( auto& [id, entry] : m_bitmapTextureCache )
3873 {
3874 if( entry.texture == bq.texture )
3875 {
3876 entry.groupRefs += aDelta;
3877 break;
3878 }
3879 }
3880 }
3881}
3882
3883
3885{
3886 auto cachedBytes = [&]()
3887 {
3888 size_t bytes = 0;
3889
3890 for( const auto& [id, entry] : m_bitmapTextureCache )
3891 bytes += entry.bytes;
3892
3893 return bytes;
3894 };
3895
3896 while( m_bitmapTextureCache.size() > BITMAP_CACHE_MAX_ENTRIES || cachedBytes() > BITMAP_CACHE_MAX_BYTES )
3897 {
3898 // An entry used this frame may back an emitted quad, so only older ones are evicted
3899 auto lru = m_bitmapTextureCache.end();
3900
3901 for( auto it = m_bitmapTextureCache.begin(); it != m_bitmapTextureCache.end(); ++it )
3902 {
3903 if( it->second.lastUse >= m_bitmapFrameStartSeq )
3904 continue;
3905
3906 if( it->second.groupRefs > 0 )
3907 continue;
3908
3909 if( lru == m_bitmapTextureCache.end() || it->second.lastUse < lru->second.lastUse )
3910 lru = it;
3911 }
3912
3913 if( lru == m_bitmapTextureCache.end() )
3914 break;
3915
3916 if( lru->second.texture )
3917 retireTexture( lru->second.texture );
3918
3919 m_bitmapTextureCache.erase( lru );
3920 }
3921}
3922
3923
3925{
3926 for( auto& [id, group] : m_groups )
3927 releaseBitmapQuads( group.bitmapQuads, false );
3928
3929 resetScene();
3930
3931 // Keep m_groupCounter running. VIEW::Clear leaves stale group ids on its items, and a restarted
3932 // counter hands those ids to new groups that the next item's recache then deletes
3933 m_lodGroups.clear();
3934 m_groups.clear();
3935
3936 // The bitmap textures belong to the cache being cleared. Retiring them keeps in-flight frames valid.
3937 for( auto& [id, entry] : m_bitmapTextureCache )
3938 {
3939 if( entry.texture )
3940 retireTexture( entry.texture );
3941 }
3942
3943 m_bitmapTextureCache.clear();
3944
3945 // The slots describe groups that no longer exist
3947 m_staticDirty = true;
3948}
3949
3950
3952{
3954}
3955
3956
3958{
3959 if( !m_transformStack.empty() )
3960 {
3962 m_transformStack.pop_back();
3963 }
3964 else
3965 {
3966 m_currentTransform.SetIdentity();
3967 }
3968
3970}
3971
3972
3973void GAL_MODERN::Transform( const MATRIX3x3D& aTransformation )
3974{
3975 m_currentTransform = m_currentTransform * aTransformation;
3977}
3978
3979
3980void GAL_MODERN::Rotate( double aAngle )
3981{
3982 MATRIX3x3D rot;
3983 rot.SetIdentity();
3984 rot.SetRotation( aAngle );
3985 Transform( rot );
3986}
3987
3988
3989void GAL_MODERN::Translate( const VECTOR2D& aTranslation )
3990{
3991 MATRIX3x3D tr;
3992 tr.SetIdentity();
3993 tr.SetTranslation( aTranslation );
3994 Transform( tr );
3995}
3996
3997
3998void GAL_MODERN::Scale( const VECTOR2D& aScale )
3999{
4000 MATRIX3x3D sc;
4001 sc.SetIdentity();
4002 sc.SetScale( aScale );
4003 Transform( sc );
4004}
4005
4006
4008{
4009 // The render thread presents on the vertical blank and takes only the newest frame, so the
4010 // panel need not hold a repaint back to the refresh rate the way an unsynced canvas does
4011 return 1;
4012}
4013
4014
4015static void toFloat4( float* aDest, const COLOR4D& aColor )
4016{
4017 aDest[0] = static_cast<float>( aColor.r );
4018 aDest[1] = static_cast<float>( aColor.g );
4019 aDest[2] = static_cast<float>( aColor.b );
4020 aDest[3] = static_cast<float>( aColor.a );
4021}
4022
4023
4024template <typename QUADS>
4025static void appendBitmapDraws( FRAME_DRAW_DESC& aDesc, const QUADS& aQuads )
4026{
4027 for( const auto& bq : aQuads )
4028 {
4029 if( bq.texture && bq.vertexBuffer )
4030 aDesc.bitmapDraws.push_back( { bq.vertexBuffer, bq.texture, 6 } );
4031 }
4032}
4033
4034
4035// For a pool other than the one the descriptor's buffers come from, so each range points at its own
4036// pool's buffers and runs
4037template <typename POOL>
4038static void appendPoolRanges( FRAME_DRAW_DESC& aDesc, const POOL& aPool, const std::vector<LAYER_RANGE>& aRanges,
4039 std::vector<LAYER_RANGE>& aDest, size_t aAt, uint32_t aBitmapBase )
4040{
4041 const uint32_t runBase = static_cast<uint32_t>( aDesc.polarityRuns.size() );
4042
4043 aDesc.polarityRuns.insert( aDesc.polarityRuns.end(), aPool.polarityRuns.begin(), aPool.polarityRuns.end() );
4044
4045 std::vector<LAYER_RANGE> ranges;
4046
4047 for( LAYER_RANGE range : aRanges )
4048 {
4049 range.runStart += runBase;
4050 range.buffer = aPool.buffer;
4051 range.negBuffer = aPool.buffer;
4052 range.bitmapStart += aBitmapBase;
4053 range.cullRuns = aPool.cullRuns;
4054 ranges.push_back( std::move( range ) );
4055 }
4056
4057 aDest.insert( aDest.begin() + aAt, ranges.begin(), ranges.end() );
4058
4059 for( int shape = 0; shape < RHI_SHAPES; ++shape )
4060 {
4061 aDesc.count[shape] += aPool.lastCount[shape];
4062 aDesc.negCount[shape] += aPool.lastNegCount[shape];
4063 }
4064}
4065
4066
4067static void buildDescForPool( FRAME_DRAW_DESC& aDesc, const auto& aPool, const std::vector<LAYER_RANGE>& aRanges )
4068{
4069 aDesc.buffer = aPool.buffer;
4070 aDesc.count = aPool.lastCount;
4071 aDesc.negCount = aPool.lastNegCount;
4072
4073 appendBitmapDraws( aDesc, aPool.bitmapQuads );
4074
4075 aDesc.layerRanges = aRanges;
4076 aDesc.polarityRuns = aPool.polarityRuns;
4077}
4078
4079
4081{
4082 FRAME_DRAW_DESC desc;
4084
4085 // Grid item grids go first, over the grid and under everything else, at the far end of the depth
4086 // range. Their ranges index their own pool's buffers.
4088
4089 for( size_t ii = 0; ii < m_gridLayerRanges.size(); ++ii )
4090 {
4091 desc.layerRanges[ii].bitmapStart = 0;
4092 desc.layerRanges[ii].bitmapCount = 0;
4093 desc.layerRanges[ii].depthNear = 1.0f;
4094 desc.layerRanges[ii].depthFar = 1.0f;
4095 }
4096
4097 appendSceneRanges( desc );
4098
4099 // With kept targets the grid goes under the board, as the first thing in the static
4100 // target. That target is redrawn whenever the camera or the grid settings change.
4102 desc.gridFirst = true;
4104
4106 PopulateCameraUBOs( desc );
4107 return desc;
4108}
4109
4110
4112{
4113 FRAME_DRAW_DESC desc;
4115
4116 for( LAYER_RANGE& range : desc.layerRanges )
4117 range.cullRuns = buf().cullRuns;
4118
4120
4121 // The overlay target sits above the non-cached one, as it does when the other backends
4122 // composite their targets. Its ranges index their own pool's buffers.
4123 const GEOMETRY_BUFFER_SET& overlay = obuf();
4124 const uint32_t bitmapBase = static_cast<uint32_t>( desc.bitmapDraws.size() );
4125
4126 appendBitmapDraws( desc, overlay.bitmapQuads );
4127 appendPoolRanges( desc, overlay, m_overlayLayerRanges, desc.layerRanges, desc.layerRanges.size(), bitmapBase );
4128
4129 // Draw order is far to near, so each range takes the slice in front of the one before it.
4130 // The overlay's ranges come last and so in front of the non-cached ones.
4131 if( keepsRelativeDepth( buf() ) && assignSlicesByOrder( desc.layerRanges, 0 ) )
4132 {
4133 desc.relativeDepth = true;
4134 desc.testAgainstStatic = true;
4135 }
4136 else if( keepsRelativeDepth( buf() ) )
4137 {
4138 const size_t slices = std::count_if( desc.layerRanges.begin(), desc.layerRanges.end(),
4139 []( const LAYER_RANGE& aRange )
4140 {
4141 return aRange.HasGeometry();
4142 } );
4143 size_t slice = 0;
4144
4145 for( LAYER_RANGE& range : desc.layerRanges )
4146 {
4147 if( !range.HasGeometry() )
4148 continue;
4149
4150 range.depthFar = 1.0f - static_cast<float>( slice ) / static_cast<float>( slices );
4151 range.depthNear = 1.0f - static_cast<float>( slice + 1 ) / static_cast<float>( slices );
4152 slice++;
4153 }
4154
4155 desc.relativeDepth = true;
4156 }
4157
4160
4161 PopulateCameraUBOs( desc );
4162 PopulateCursorUBO( desc );
4163 desc.drawCursor = m_drawCursor;
4164 return desc;
4165}
4166
4167
4169{
4170 // A merged view for tests that inspect combined counts. The buffers are the dynamic pool's, so
4171 // the static ranges' offsets are not valid against them.
4173
4174 for( int shape = 0; shape < RHI_SHAPES; ++shape )
4175 {
4176 desc.count[shape] += m_staticPool.lastCount[shape];
4177 desc.negCount[shape] += m_staticPool.lastNegCount[shape];
4178 }
4179
4180 desc.layerRanges.insert( desc.layerRanges.end(), m_staticLayerRanges.begin(), m_staticLayerRanges.end() );
4181
4182 appendSceneRanges( desc );
4183 appendBitmapDraws( desc, m_staticPool.bitmapQuads );
4184 return desc;
4185}
4186
4187
4189{
4190 // The grid shader's NDC to world projection. NDC y points up and screen y down, so
4191 // y_screen = (1 - (ndc_y + 1) * 0.5) * height.
4192 float w = static_cast<float>( m_screenWidth );
4193 float h = static_cast<float>( m_screenHeight );
4194 float hw = w * 0.5f;
4195 float hh = h * 0.5f;
4196
4197 const auto& S = m_screenWorldMatrix;
4198
4199 // M = screenWorldMatrix * ndcToScreen, with ndcToScreen = [hw 0 hw; 0 -hh hh; 0 0 1] row-major
4200 float M00 = static_cast<float>( S.m_data[0][0] ) * hw;
4201 float M01 = static_cast<float>( S.m_data[0][1] ) * -hh;
4202 float M02 = static_cast<float>( S.m_data[0][0] * hw + S.m_data[0][1] * hh + S.m_data[0][2] );
4203 float M10 = static_cast<float>( S.m_data[1][0] ) * hw;
4204 float M11 = static_cast<float>( S.m_data[1][1] ) * -hh;
4205 float M12 = static_cast<float>( S.m_data[1][0] * hw + S.m_data[1][1] * hh + S.m_data[1][2] );
4206 float M20 = static_cast<float>( S.m_data[2][0] ) * hw;
4207 float M21 = static_cast<float>( S.m_data[2][1] ) * -hh;
4208 float M22 = static_cast<float>( S.m_data[2][0] * hw + S.m_data[2][1] * hh + S.m_data[2][2] );
4209
4210 // A std140 mat4 is column-major. Vulkan and Metal flip NDC y relative to OpenGL, so column 1,
4211 // the ndc.y term, is negated there.
4212 RHI_BACKEND backend = m_deviceProxy.GetBackend();
4213 bool flipY = RhiBackendNeedsYFlip( backend );
4214 float ySign = flipY ? -1.0f : 1.0f;
4215
4216 float* ip = aDesc.gridUBO.invProjection;
4217 ip[0] = M00; ip[1] = M10; ip[2] = 0.0f; ip[3] = M20;
4218 ip[4] = M01 * ySign; ip[5] = M11 * ySign; ip[6] = 0.0f; ip[7] = M21 * ySign;
4219 ip[8] = 0.0f; ip[9] = 0.0f; ip[10] = 1.0f; ip[11] = 0.0f;
4220 ip[12] = M02; ip[13] = M12; ip[14] = 0.0f; ip[15] = M22;
4221
4222 // Inverting the grid shader's transform keeps geometry and grid on the same math
4223 MATRIX3x3D ndcToWorld( M00, M01, M02,
4224 M10, M11, M12,
4225 M20, M21, M22 );
4226 MATRIX3x3D worldToNdc = ndcToWorld.Inverse();
4227
4228 float* fp = aDesc.frameUBO.projection;
4229 fp[0] = static_cast<float>( worldToNdc.m_data[0][0] );
4230 fp[1] = static_cast<float>( worldToNdc.m_data[1][0] );
4231 fp[2] = 0.0f;
4232 fp[3] = 0.0f;
4233 fp[4] = static_cast<float>( worldToNdc.m_data[0][1] );
4234 fp[5] = static_cast<float>( worldToNdc.m_data[1][1] );
4235 fp[6] = 0.0f;
4236 fp[7] = 0.0f;
4237 // Scale z so GAL depths land in about [-1,1] before the backend adjustment, as unscaled ones would
4238 // be clipped
4239 fp[8] = 0.0f;
4240 fp[9] = 0.0f;
4242 : 1.0f / static_cast<float>( GAL::MAX_DEPTH );
4243 fp[11] = 0.0f;
4244 fp[12] = static_cast<float>( worldToNdc.m_data[0][2] );
4245 fp[13] = static_cast<float>( worldToNdc.m_data[1][2] );
4246 fp[14] = 0.0f;
4247 fp[15] = 1.0f;
4248 RhiAdjustProjectionForBackend( fp, m_deviceProxy.GetBackend() );
4249
4250 // Shaders size antialiased edges in backing pixels, m_dpiScale times the logical size the
4251 // projection uses
4252 float bw = w * m_dpiScale;
4253 float bh = h * m_dpiScale;
4254 aDesc.frameUBO.viewport[0] = bw;
4255 aDesc.frameUBO.viewport[1] = bh;
4256 aDesc.frameUBO.viewScale = static_cast<float>( m_zoomFactor );
4257
4258 aDesc.gridUBO.viewport[0] = bw;
4259 aDesc.gridUBO.viewport[1] = bh;
4260 aDesc.gridUBO.gridOrigin[0] = static_cast<float>( m_gridOrigin.x );
4261 aDesc.gridUBO.gridOrigin[1] = static_cast<float>( m_gridOrigin.y );
4262 VECTOR2D visibleGrid = GetVisibleGridSize();
4263 aDesc.gridUBO.gridSpacing[0] = static_cast<float>( visibleGrid.x );
4264 aDesc.gridUBO.gridSpacing[1] = static_cast<float>( visibleGrid.y );
4265 aDesc.gridUBO.gridAngle = 0.0f;
4266
4267 switch( m_gridStyle )
4268 {
4269 case GRID_STYLE::LINES: aDesc.gridUBO.gridStyle = 0; break;
4270 case GRID_STYLE::DOTS: aDesc.gridUBO.gridStyle = 1; break;
4271 case GRID_STYLE::SMALL_CROSS: aDesc.gridUBO.gridStyle = 2; break;
4272 default: aDesc.gridUBO.gridStyle = 0; break;
4273 }
4274
4278
4279 // The shader draws the axes and the grid whenever their colour is visible
4280 if( !m_axesEnabled )
4281 {
4282 aDesc.gridUBO.axisColorX[3] = 0.0f;
4283 aDesc.gridUBO.axisColorY[3] = 0.0f;
4284 }
4285
4286 if( !gridShown() )
4287 aDesc.gridUBO.gridColor[3] = 0.0f;
4288
4289 const double pixel = screenPixelSize();
4290
4291 aDesc.gridUBO.minorLineWidth = std::fmax( 1.0f, m_gridLineWidth );
4292 aDesc.gridUBO.majorLineWorld = 2.0f * gridMinorLineWidth( pixel, m_dpiScale );
4293 aDesc.gridUBO.worldPixelSize = static_cast<float>( pixel / m_dpiScale );
4294 aDesc.gridUBO.gridTick = m_gridTick;
4295}
4296
4297
4299{
4300 float w = std::fmax( static_cast<float>( m_screenWidth ), 1.0f );
4301 float h = std::fmax( static_cast<float>( m_screenHeight ), 1.0f );
4302 bool flipY = RhiBackendNeedsYFlip( m_deviceProxy.GetBackend() );
4303
4305 aDesc.cursorUBO.cursorPos[0] = static_cast<float>( cursorScreen.x / ( w * 0.5 ) - 1.0 );
4306 float ndcY = static_cast<float>( 1.0 - cursorScreen.y / ( h * 0.5 ) );
4307 aDesc.cursorUBO.cursorPos[1] = flipY ? -ndcY : ndcY;
4308 aDesc.cursorUBO.viewport[0] = w * m_dpiScale;
4309 aDesc.cursorUBO.viewport[1] = h * m_dpiScale;
4310 // Dimmed while no tool has asked for it and it shows only because it is always shown
4311 const COLOR4D cursorColor = getCursorColor();
4312
4313 toFloat4( aDesc.cursorUBO.crosshairColor, cursorColor );
4314 aDesc.cursorUBO.snapColor[0] = 1.0f;
4315 aDesc.cursorUBO.snapColor[1] = 1.0f;
4316 aDesc.cursorUBO.snapColor[2] = 1.0f;
4317 aDesc.cursorUBO.snapColor[3] = 1.0f;
4318 aDesc.cursorUBO.crosshairWidth = 1.0f;
4319 aDesc.cursorUBO.snapRadius = 5.0f;
4320 aDesc.cursorUBO.showSnap = 0;
4321
4322 switch( m_crossHairMode )
4323 {
4324 case CROSS_HAIR_MODE::SMALL_CROSS: aDesc.cursorUBO.cursorType = 0; break;
4327 }
4328
4329 aDesc.cursorUBO.cursorSize = 80.0f;
4330
4331 // No cursor while no tool has asked for one and it is not always shown
4332 aDesc.drawCursor = IsCursorEnabled();
4333}
4334
4335
4337{
4338 m_isUpdating = true;
4339}
4340
4341
4343{
4344 m_isUpdating = false;
4345}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:47
const wxImage * GetOriginalImageData() const
Definition bitmap_base.h:69
VECTOR2I GetSizePixels() const
EDA_ANGLE Rotation() const
bool IsMirroredX() const
bool IsMirroredY() const
KIID GetImageID() const
Definition bitmap_base.h:74
int GetPPI() const
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
double Sin() const
Definition eda_angle.h:177
double AsRadians() const
Definition eda_angle.h:119
double Cos() const
Definition eda_angle.h:196
virtual bool IsStroke() const
Definition glyph.h:40
virtual bool IsOutline() const
Definition glyph.h:39
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:391
double g
Green component.
Definition color4d.h:392
COLOR4D Darkened(double aFactor) const
Return a color that is darker by a given factor, without modifying object.
Definition color4d.h:279
double a
Alpha component.
Definition color4d.h:394
double b
Blue component.
Definition color4d.h:393
GAL that turns drawing commands into instanced primitives for the RHI device.
Definition gal_modern.h:50
void addCapsuleInstance(const VECTOR2D &aStart, const VECTOR2D &aEnd, double aWidth, const COLOR4D &aColor)
void DrawArc(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle) override
Draw an arc.
GEOMETRY_BUFFER_SET m_gridPool
The grids of the grid items, which the static target draws over the grid and under all else.
Definition gal_modern.h:574
void endUpdate() override
Disable item update mode.
void Scale(const VECTOR2D &aScale) override
Scale the context.
static void countGroupArenas(const GAL_GROUP &aGroup, uint32_t *aCounts)
Fill aCounts with how many of a group's instances go in each instance arena.
void StartNegativesLayer() override
Begins rendering in a new layer that will be copied to the main layer in EndNegativesLayer().
bool keepsRelativeDepth(const GEOMETRY_BUFFER_SET &aPool) const
The pool is a kept target whose ranges are drawn in depth slices, see Flush()
Definition gal_modern.h:858
void SetTarget(RENDER_TARGET aTarget) override
Set the target for rendering.
double xformAngle() const
Definition gal_modern.h:420
void countGroupAlpha(const GAL_GROUP &aGroup, SCENE_LAYER &aLayer, int aDelta)
void beginUpdate() override
Enable item update mode.
void SetSceneDrawOrdered(bool aOrdered) override
Tell the retained scene whether cached groups are placed in an order that matters.
static std::array< uint64_t, 2 > computeGroupFingerprint(const GAL_GROUP &aGroup)
double m_bitmapTextHeight
glyph height while bitmap text is stroked, else 0
Definition gal_modern.h:391
RHI_INSTANCE & addArcInstance(const VECTOR2D &aCenter, double aRadius, double aStartAngle, double aSweepAngle, double aWidth, const COLOR4D &aColor)
void notePolarity(bool aNegative)
Note the polarity of what is about to be drawn, so that a pool remembers where it changed.
std::vector< std::pair< uint64_t, RHI_TextureHandle > > m_retiredTextures
Definition gal_modern.h:597
void writeGroupToScene(const GAL_GROUP &aGroup, SCENE_LAYER &aLayer)
Copy a resident group's elements into its slots, applying the layer's hoisted alpha.
void ClearCache() override
Delete all data created during caching of graphic items.
std::multimap< double, int > m_lodGroups
groups with a scale threshold, by it
Definition gal_modern.h:748
float m_strokeLengthPad
see SetStrokeLengthPad(), 0 when off
Definition gal_modern.h:389
void ClearTarget(RENDER_TARGET aTarget) override
Clear the target for rendering.
void emitInstance(RHI_SHAPE aShape, const RHI_INSTANCE &aInstance)
Append to the current target's pool.
Definition gal_modern.h:430
void SetSceneScale(double aScale) override
Set the view scale that group placements are tested against.
void DrawPolylines(const std::vector< std::vector< VECTOR2D > > &aPointLists) override
Draw multiple polylines.
void refillDynamicTarget(RENDER_TARGET aTarget)
Rotate a dynamic target's ring, empty the new slot and open a range at the current depth.
void countBitmapRefs(const GAL_GROUP &aGroup, int aDelta)
Count a group's quads against the cached textures they draw, so a texture a cached group still shows ...
void SetLayerDepth(double aLayerDepth) override
Set the depth of the layer (position on the z-axis)
bool m_nonCachedRefilled
non-cached pool was emptied for the frame being drawn
Definition gal_modern.h:548
void evictBitmapCacheEntries()
Evict least recently used entries down to the entry and byte caps, sparing those used this frame sinc...
uint64_t m_bufferGeneration
Bumped whenever a resource is retired.
Definition gal_modern.h:594
void PopulateCameraUBOs(KIGFX::FRAME_DRAW_DESC &aDesc) const
Update the camera-dependent UBOs of an existing descriptor.
void ClearScreen() override
Clear the screen.
void EndDiffLayer() override
Ends rendering of a differential layer.
void Rotate(double aAngle) override
Rotate the context.
void Restore() override
Restore the context.
std::array< PATCH_SPAN, RHI_SHAPES > m_staticPatches
Definition gal_modern.h:638
KIGFX::FRAME_DRAW_DESC GetStaticDrawDesc() const
Draw descriptor of the static pool.
static bool sceneLayerComposited(const SCENE_LAYER &aLayer)
Whether a scene layer goes through the temp target and is composited with its alpha.
Definition gal_modern.h:741
GEOMETRY_BUFFER_SET m_overlaySets[BUFFER_RING_SIZE]
Definition gal_modern.h:544
void ResizeScreen(int aWidth, int aHeight) override
Resize the canvas.
bool retainedTieOrderChanged(int aGroupId, GAL_GROUP &aGroup, SCENE_LAYER &aLayer)
void DrawGrid() override
Draw the grid.
void retireBuffer(RHI_BufferHandle aBuffer)
void retireTexture(RHI_TextureHandle aTexture)
bool gridShown() const
Definition gal_modern.h:735
static constexpr int BUFFER_RING_SIZE
Definition gal_modern.h:476
bool assignSlicesByOrder(std::vector< LAYER_RANGE > &aRanges, size_t aFirst) const
Give ranges their slices of the depth range from one mapping of layer order, the same for every targe...
bool patchStaticGroup(int aGroup, INSTANCE_FN aInstance, VERTEX_FN aVertex)
Patch a cached group in place in the static pool, applying aInstance to each instance and aVertex to ...
bool uploadSceneArena(SCENE_ARENA< T > &aArena)
Upload the arena's pending spans, growing its buffer first.
uint64_t m_bitmapFrameStartSeq
use counter at frame start, entries used since are pinned
Definition gal_modern.h:872
MATRIX3x3D m_currentTransform
Definition gal_modern.h:894
void Flush() override
Force all remaining objects to be drawn.
GAL_MODERN(GAL_DISPLAY_OPTIONS &aOptions, RHI_DEVICE_PROXY &aDeviceProxy)
std::vector< LAYER_RANGE > m_gridLayerRanges
Definition gal_modern.h:575
bool m_transformIsIdentity
The GPU applies world to screen, so most geometry skips the matrix math on the hot path.
Definition gal_modern.h:398
GEOMETRY_BUFFER_SET m_bufferSets[BUFFER_RING_SIZE]
The non-cached pool.
Definition gal_modern.h:541
uint64_t m_bitmapUseCounter
bumped on every cache touch
Definition gal_modern.h:871
SCENE_LAYER * m_openSceneLayer
Definition gal_modern.h:749
std::vector< BITMAP_QUAD > m_groupScratchBitmapQuads
Definition gal_modern.h:892
void EndDrawing() override
End the drawing, needs to be called for every new frame.
bool SyncScene()
Upload scene changes made outside a drawing context, such as groups crossing their level of detail on...
void sortSceneOrder()
Put the declared layers back in decreasing depth order after one was added or moved.
~GAL_MODERN() override
std::unordered_map< int, std::vector< STATIC_GROUP_SLOT > > m_staticGroupSlots
A group may be drawn into the static pool more than once per build, so a patch touches every slot.
Definition gal_modern.h:613
uint64_t m_placeSeq
Definition gal_modern.h:743
RENDER_TARGET GetTarget() const override
Get the currently used target for rendering.
std::array< uint64_t, 2 > GroupContentFingerprint(int aGroupNumber) const override
KIGFX::FRAME_DRAW_DESC GetDrawDesc() const
Snapshot buffer handles, draw counts and grid and cursor uniforms after EndDrawing()
void ChangeGroupDepth(int aGroupNumber, int aDepth) override
Change the depth (Z-axis position) of the group.
static constexpr int ARENA_INSTANCE_COUNT
Arenas of one retained scene layer.
Definition gal_modern.h:284
void DrawCurve(const VECTOR2D &aStartPoint, const VECTOR2D &aControlPointA, const VECTOR2D &aControlPointB, const VECTOR2D &aEndPoint, double aFilterValue=0.0) override
Draw a cubic bezier spline.
bool ensureBuffer(RHI_BufferHandle &aBuffer, size_t &aCapacity, size_t aNeeded, size_t aStride)
Grow aBuffer to hold aNeeded elements.
RENDER_THREAD * m_renderThread
Definition gal_modern.h:374
int GetSwapInterval() const override
Return the swap interval. -1 for adaptive, 0 for disabled/unknown.
void SetSceneLayerOverlayAlpha(int aLayer) override
Blend a cached layer the way the other canvases blend the overlay they draw it in.
static constexpr int arenaOf(int aShape, bool aNegative)
Definition gal_modern.h:288
std::vector< std::pair< uint64_t, RHI_BufferHandle > > m_retiredBuffers
Definition gal_modern.h:596
RHI_VERTEX fillVertex(const COLOR4D &aColor)
A vertex filled in aColor at the layer depth, for emitTriangle() to place.
int BeginGroup() override
Begin a group.
bool IsInitialized() const override
Return the initialization status for the canvas.
Definition gal_modern.h:55
void appendSceneRanges(FRAME_DRAW_DESC &aDesc) const
void SetNegativeDrawMode(bool aSetting) override
Set negative draw mode in the renderer.
uint16_t m_strokePixelPad
see SetStrokePixelPad(), in sixteenths of a pixel
Definition gal_modern.h:388
std::vector< MATRIX3x3D > m_transformStack
Definition gal_modern.h:895
static constexpr int ARENA_POLYGON
Definition gal_modern.h:285
RENDER_TARGET m_currentTarget
Definition gal_modern.h:379
bool m_groupNegative
the group being recorded has drawn a knockout
Definition gal_modern.h:866
void DrawHoleWall(const VECTOR2D &aCenterPoint, double aHoleRadius, double aWallWidth) override
Draw a hole wall ring.
bool m_bitmapGlyphs
see SetBitmapGlyphs()
Definition gal_modern.h:392
static std::shared_ptr< const RHI_RUN_TABLE > updateSceneCullRuns(SCENE_LAYER &aLayer)
Bring a layer's culling bounds up to date, recounting only runs whose elements changed.
static uint32_t AllocateWindowId()
bool drawBitmapGlyphs(const wxString &aText, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle)
Emit bitmap text glyph by glyph.
void EndGroup() override
End the group.
std::unordered_map< KIID, BITMAP_CACHE_ENTRY > m_bitmapTextureCache
Definition gal_modern.h:870
static std::shared_ptr< RENDER_THREAD > AcquireRenderThread(bool aEnableValidation)
Return the render thread every RHI canvas in the process shares, starting it on first use and replaci...
void compactSceneArena(SCENE_LAYER &aLayer, SCENE_ARENA< T > &aArena, int aArenaId)
float m_dpiScale
see SetDpiScale()
Definition gal_modern.h:383
void DrawPad(const VECTOR2D &aCenter, double aHalfWidth, double aHalfHeight, double aCornerRadius, float aShapeType, double aRotation, const COLOR4D &aColor) override
Draw a filled pad as one primitive in aColor.
RHI_DEVICE_PROXY & m_deviceProxy
Definition gal_modern.h:373
std::vector< int > m_sceneOrder
layer ids in draw order
Definition gal_modern.h:747
void emitTriangle(const VECTOR2D &aA, const VECTOR2D &aB, const VECTOR2D &aC, const RHI_VERTEX &aFill)
Append a triangle given in world coordinates under the current transform.
static constexpr int ARENA_COUNT
Definition gal_modern.h:286
double m_strokeLod
see SetStrokeLevelOfDetail(), 0 when off
Definition gal_modern.h:390
void freeSceneSlot(SCENE_ARENA< T > &aArena, uint32_t aStart, uint32_t aCount)
static constexpr size_t BITMAP_CACHE_MAX_ENTRIES
Definition gal_modern.h:868
void ChangeGroupUndimmed(int aGroupNumber, bool aUndimmed) override
Exempt the group from its layer's dim, see SetSceneLayerDim().
std::array< uint64_t, 2 > m_gridPoolHash
hash of what the GPU buffers hold
Definition gal_modern.h:578
void DrawEllipseArc(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle) override
Draw an elliptical arc in world coordinates.
void DeleteGroup(int aGroupNumber) override
Delete the group from the memory.
SHAPE_INSTANCES m_groupScratchInstances
BeginGroup() swaps these with the current pool's vectors and EndGroup() swaps them back.
Definition gal_modern.h:890
void beginLayerRange(float aDepth)
void DrawLine(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a line.
bool currentTargetOpen() const
Whether drawing to the current target lands in a pool that this frame has emptied.
Definition gal_modern.h:555
void BeginSceneLayer(int aLayer) override
Open a cached layer of the retained scene during a rebuild.
void SetSceneLayerDim(int aLayer, const COLOR4D &aToward, double aKeep) override
Fade a cached layer towards a colour in the shader, so dimming it recolours none of its groups.
void submitSpan(const std::vector< T > &aElements, RHI_BufferHandle aBuffer, const PATCH_SPAN &aSpan)
Queue an upload of a span of aElements to the same place in aBuffer.
void Translate(const VECTOR2D &aTranslation) override
Translate the context.
std::vector< LAYER_RANGE > m_staticLayerRanges
Kept per pool so static ranges survive BeginDrawing when m_staticDirty is false.
Definition gal_modern.h:898
GEOMETRY_BUFFER_SET m_staticPool
Pool for TARGET_CACHED.
Definition gal_modern.h:567
void DrawPolygon(const std::deque< VECTOR2D > &aPointList) override
Draw a polygon.
void PopulateCursorUBO(KIGFX::FRAME_DRAW_DESC &aDesc) const
Update only the cursor UBO fields, for overlay-only frames that reuse the scene geometry.
void rewriteResidentGroup(int aGroupNumber, GAL_GROUP &aGroup)
Write a resident group back into its scene layer after its elements changed.
void DrawTrack(const VECTOR2D &aStart, const VECTOR2D &aEnd, double aWidth, const COLOR4D &aColor) override
Draw a filled track capsule in aColor without touching the fill and stroke state.
void StartDiffLayer() override
Begins rendering of a differential layer.
GEOMETRY_BUFFER_SET & poolForCurrentTarget()
TARGET_CACHED draws to the static pool and every other target to its dynamic ring slot.
Definition gal_modern.h:803
void DrawGlyph(const KIFONT::GLYPH &aGlyph, int aNth=0, int aTotal=1) override
Draw a polygon representing a font glyph.
void DrawSegment(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, double aWidth) override
Draw a rounded segment.
void resetScene()
Empty every arena and forget residency ahead of a full rebuild.
void SetGroupPlacement(int aGroup, int aLayer, double aLodScale, bool aShown, const RETAINED_GROUP_ORDER &aOrder) override
Declare where a cached group belongs in the retained scene.
void DrawSegmentChain(const std::vector< VECTOR2D > &aPointList, double aWidth) override
Draw a chain of rounded segments.
void recomputeTransformIdentity()
Exact comparison is enough since a false negative only takes the slow path.
Definition gal_modern.h:401
double screenPixelSize() const
The world size of a screen pixel, which grid lines are measured in.
void DrawEllipse(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation) override
Draw a closed ellipse.
KIGFX::FRAME_DRAW_DESC GetDynamicDrawDesc() const
Draw descriptor of the current dynamic ring slot, which is rebuilt every frame.
void strokeLineChain(const SHAPE_LINE_CHAIN &aChain)
Stroke a chain as segments, adding a last-to-first segment when it is closed.
void placeGroupInScene(int aGroupId, GAL_GROUP &aGroup, SCENE_LAYER &aLayer)
void DrawCursor(const VECTOR2D &aCursorPosition) override
Draw the cursor.
VECTOR2D xform(const VECTOR2D &aPt) const
Definition gal_modern.h:409
std::vector< LAYER_RANGE > m_overlayLayerRanges
Definition gal_modern.h:900
uint32_t allocSceneSlot(SCENE_ARENA< T > &aArena, uint32_t aCount)
void BitmapText(const wxString &aText, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle) override
Draw a text using a bitmap font.
GEOMETRY_BUFFER_SET & obuf()
Definition gal_modern.h:799
void drawGridSource(const GRID_SOURCE &aSrc)
void emitTriangulatedPolygon(const SHAPE_POLY_SET &aPolySet, const COLOR4D &aColor)
void fillOutline(SHAPE_LINE_CHAIN aOutline, const COLOR4D &aColor)
Fill an outline, closed or not, without stroking it.
std::unordered_map< int, SCENE_LAYER > m_sceneLayers
by VIEW layer id
Definition gal_modern.h:746
bool flushScene()
Settle hoisted alpha, compact and upload.
std::vector< RHI_VERTEX > m_groupScratchPolygonVertices
Definition gal_modern.h:891
void BeginDrawing() override
Start/end drawing functions, draw calls can be only made in between the calls to BeginDrawing()/EndDr...
void DrawRectangle(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a rectangle.
void clearStaticSlots()
Forget where groups sit in the static pool and the patches pending for it.
Definition gal_modern.h:641
void trackGroupLod(int aGroupId, GAL_GROUP &aGroup, bool aTrack)
Keep m_lodGroups to the shown groups whose threshold a zoom can actually cross.
bool m_drawingGridSources
emissions go to m_gridPool
Definition gal_modern.h:577
void SetSceneLayerState(int aLayer, int aOrder, bool aVisible, bool aHasNegatives) override
Describe a cached layer to the retained scene.
GEOMETRY_BUFFER_SET & buf()
Definition gal_modern.h:796
void DrawArcSegment(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aWidth, double aMaxError) override
Draw an arc segment.
double xformScale() const
Definition gal_modern.h:414
void drawEllipseShape(const SHAPE_ELLIPSE &aEllipse)
Fill and stroke an ellipse per the current settings, filling an arc as a sector.
RHI_INSTANCE & addPadInstance(const VECTOR2D &aCenter, double aHalfWidth, double aHalfHeight, double aCornerRadius, float aShapeType, double aRotation, const COLOR4D &aColor)
void DrawPolyline(const std::deque< VECTOR2D > &aPointList) override
Draw a polyline.
void Transform(const MATRIX3x3D &aTransformation) override
Transform the context.
void DrawCircle(const VECTOR2D &aCenterPoint, double aRadius) override
Draw a circle using world coordinates.
std::vector< LAYER_RANGE > & layerRangesForCurrentTarget()
Definition gal_modern.h:902
void buildSceneRuns(SCENE_LAYER &aLayer)
Work out a layer's polarity runs again after its residents changed or moved.
void drawGridSources()
Draw the grid sources into m_gridPool, each over its own area as the Cairo canvas paints them.
void releaseBitmapQuads(std::vector< BITMAP_QUAD > &aQuads, bool aOwnedOnly)
Retire the textures and vertex buffers the quads own, then empty the list.
void ReleaseRetiredBuffers(uint64_t aConsumedGeneration)
Free retired buffers and textures once the render thread has consumed a frame built after them.
void EndNegativesLayer() override
Ends rendering of a negatives layer and draws it to the main layer.
void DestroyBuffers()
Destroy every GPU resource this GAL holds and forget the handles.
void DrawGroup(int aGroupNumber) override
Draw the stored group.
void Save() override
Save the context.
void ReleaseBuffers()
Drop all GPU handles without destroying them.
Definition gal_modern.h:181
std::unordered_map< int, GAL_GROUP > m_groups
Definition gal_modern.h:880
void ChangeGroupColor(int aGroupNumber, const COLOR4D &aNewColor) override
Change the color used to draw the group.
std::vector< LAYER_RANGE > m_dynamicLayerRanges
Definition gal_modern.h:899
void removeGroupFromScene(int aGroupId, GAL_GROUP &aGroup)
void DrawBitmap(const BITMAP_BASE &aBitmap, double alphaBlend=1.0) override
Draw a bitmap image.
bool m_staticRebuiltThisFlush
see TakeStaticChanged()
Definition gal_modern.h:588
RHI_INSTANCE makeInstance(const COLOR4D &aColor)
An instance in aColor at the layer depth, with the current polarity and padding.
virtual void SetLayerDepth(double aLayerDepth)
Set the depth of the layer (position on the z-axis)
bool IsCursorEnabled() const
Return information about cursor visibility.
virtual void SetIsFill(bool aIsFillEnabled)
Enable/disable fill.
MATRIX3x3D m_worldScreenMatrix
World transformation.
VECTOR2D GetVisibleGridSize() const
Return the visible grid size in x and y directions.
double m_layerDepth
The actual layer depth.
COLOR4D gridSourceColor(const GRID_SOURCE &aSrc) const
The colour a grid source is drawn in, brightened while it is being edited.
MATRIX3x3D m_screenWorldMatrix
Screen transformation.
bool m_axesEnabled
Should the axes be drawn.
float m_gridLineWidth
Line width of the grid.
GR_TEXT_H_ALIGN_T GetHorizontalJustify() const
void normalize(T &a, T &b)
Ensure that the first element is smaller than the second.
virtual void SetFillColor(const COLOR4D &aColor)
Set the fill color.
double m_zoomFactor
The zoom factor.
GRID_STYLE m_gridStyle
Grid display style.
COLOR4D m_axesColor
Color of the axes.
float m_lineWidth
The line width.
GRID_LATTICE gridLattice(const GRID_SOURCE &aSrc) const
Thin a grid source's pitch out until its lines are far enough apart on screen.
void drawGridCoverageShape(const GRID_SOURCE &aSrc)
Draw the area a grid source covers in its own frame with the current fill and stroke.
virtual void SetLineWidth(float aLineWidth)
Set the line width.
COLOR4D getCursorColor() const
Get the actual cursor color to draw.
COLOR4D m_fillColor
The fill color.
double m_worldUnitLength
The unit length of the world coordinates [inch].
void SetAxesColor(const COLOR4D &aAxesColor)
Set the axes color.
virtual void SetStrokeColor(const COLOR4D &aColor)
Set the stroke color.
BOX2D gridScreenBBox(const GRID_SOURCE &aSrc) const
VECTOR2D m_cursorPosition
Current cursor position (world coordinates)
virtual void SetIsStroke(bool aIsStrokeEnabled)
Enable/disable stroked outlines.
const VECTOR2I & GetGlyphSize() const
int m_gridTick
Every tick line gets the double width.
double m_worldScale
The scale factor world->screen.
VECTOR2D m_gridOrigin
The grid origin.
bool m_globalFlipY
Flag for Y axis flipping.
void SetScreenSize(const VECTOR2I &aSize)
bool m_isFillEnabled
Is filling of graphic objects enabled ?
virtual void ComputeWorldScreenMatrix()
Compute the world <-> screen transformation matrix.
float gridMinorLineWidth(double aWorldPixelSize, double aScaleFactor) const
Compute the world width of a minor grid line.
COLOR4D m_gridColor
Color of the grid.
COLOR4D m_strokeColor
The color of the outlines.
bool m_isStrokeEnabled
Are the outlines stroked ?
std::vector< GRID_SOURCE > m_gridSources
Sources overlayed on the display grid.
virtual void BitmapText(const wxString &aText, const VECTOR2I &aPosition, const EDA_ANGLE &aAngle)
Draw a text using a bitmap font.
bool m_globalFlipX
Flag for X axis flipping.
GR_TEXT_V_ALIGN_T GetVerticalJustify() const
KIGFX::CROSS_HAIR_MODE m_crossHairMode
Crosshair drawing mode.
GAL(GAL_DISPLAY_OPTIONS &aOptions)
static const int MAX_DEPTH
static double calcAngleStep(double aRadius)
Compute the angle step when drawing arcs/circles approximated with lines.
Raised when a GPU buffer allocation is predicted to exceed the available video memory.
UI-thread access to GPU resources without touching the device, which only the render thread may mutat...
Definition kiid.h:46
void SetIdentity()
Set the matrix to the identity matrix.
Definition matrix3x3.h:236
void SetRotation(T aAngle)
Set the rotation components of the matrix.
Definition matrix3x3.h:271
void SetScale(VECTOR2< T > aScale)
Set the scale components of the matrix.
Definition matrix3x3.h:283
T m_data[3][3]
Definition matrix3x3.h:61
void SetTranslation(VECTOR2< T > aTranslation)
Set the translation components of the matrix.
Definition matrix3x3.h:252
MATRIX3x3 Inverse() const
Determine the inverse of the matrix.
Definition matrix3x3.h:380
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:61
FORCE_INLINE void addData(const uint8_t *data, size_t length)
Definition mmh3_hash.h:74
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:138
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:157
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
int GetMajorRadius() const
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
const VECTOR2I & GetCenter() const
bool IsArc() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
void Insert(size_t aVertex, const VECTOR2I &aP)
Represent a set of closed polygons.
bool IsTriangulationUpToDate() const
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={}, bool aBuildIndexes=false)
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
uni_iter is a non-mutating iterator that walks through unicode code points in the UTF8 encoded string...
Definition utf8.h:226
An 8 bit string that is assuredly encoded in UTF8, and supplies special conversion support to and fro...
Definition utf8.h:67
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
A type-safe container of any type.
Definition ki_any.h:92
@ BLUE
Definition color4d.h:52
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ RADIANS_T
Definition eda_angle.h:31
static void markSceneDirty(ARENA &aArena, uint32_t aStart, uint32_t aCount)
static uint16_t relativeDepth(uint16_t aDepth, float aBase)
static RHI_INSTANCE deadInstance()
static uint8_t packColorChannel(double aValue)
static void fillDead(RHI_INSTANCE *aFirst, uint32_t aCount)
static void hashBytes(MMH3_HASH &aHash, const void *aData, size_t aSize)
static void forEachElement(GROUP &aGroup, INSTANCE_FN aInstance, VERTEX_FN aVertex)
static void buildDescForPool(FRAME_DRAW_DESC &aDesc, const auto &aPool, const std::vector< LAYER_RANGE > &aRanges)
static size_t retainedPrefixHash(const RETAINED_GROUP_ORDER &aOrder)
static SHAPE_LINE_CHAIN roundedOutline(IT aBegin, IT aEnd)
static void drawPolylineSegments(GAL_MODERN &aGal, IT aBegin, IT aEnd)
static void appendPoolRanges(FRAME_DRAW_DESC &aDesc, const POOL &aPool, const std::vector< LAYER_RANGE > &aRanges, std::vector< LAYER_RANGE > &aDest, size_t aAt, uint32_t aBitmapBase)
static void toFloat4(float *aDest, const COLOR4D &aColor)
static RHI_VERTEX deadVertex()
static void hashPool(MMH3_HASH &aHash, const POOL &aPool, const std::vector< LAYER_RANGE > &aRanges)
static void appendBitmapDraws(FRAME_DRAW_DESC &aDesc, const QUADS &aQuads)
static std::array< uint64_t, 2 > hashDigest(MMH3_HASH &aHash)
static void updateRuns(std::vector< RHI_RUN_BOUNDS > &aRuns, const std::vector< T > &aElements, const std::vector< std::pair< uint32_t, uint32_t > > &aDirty, size_t aCounted, bool aAll, size_t aRunLength, COUNT aCount)
const wxChar *const traceGalGroups
Flag to enable debug output of GAL cached group creation and drawing.
static constexpr std::size_t hash_val(const Types &... args)
Definition hash.h:48
std::string source
@ S
Solder (HASL/SMOBC)
MATRIX3x3< double > MATRIX3x3D
Definition matrix3x3.h:469
unsigned int GlyphCount()
The number of glyphs in the font, starting at LookupGlyph( '!
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:30
std::vector< uint8_t > ImageToRgba(const wxImage &aImage)
The pixels of aImage as tightly packed RGBA, transparent where it has alpha or a mask.
constexpr double GRID_DIM_ALPHA
Opacity of the background wash laid over a selected grid item's area, to fade the grids showing throu...
@ SMALL_CROSS
Use small cross instead of dots for the grid.
@ DOTS
Use dots for the grid.
@ LINES
Use lines for the grid.
constexpr uint32_t RHI_CULL_RUN_LENGTH
RHI_SHAPE
Shapes a frame draws, each with its own array and pipelines.
@ RHI_SHAPE_POLYGON
@ RHI_SHAPE_TRACK
@ RHI_SHAPE_PAD
@ RHI_SHAPE_ARC
RENDER_TARGET
RENDER_TARGET: Possible rendering targets.
Definition definitions.h:32
@ TARGET_NONCACHED
Auxiliary rendering target (noncached)
Definition definitions.h:34
@ TARGET_CACHED
Main rendering target (cached)
Definition definitions.h:33
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
std::array< uint32_t, RHI_SHAPES > RHI_SHAPE_COUNTS
Elements of each shape, indexed by RHI_SHAPE.
RHI_RUN_BOUNDS RhiVertexRun(const RHI_VERTEX *aVertices, size_t aCount)
constexpr int RHI_INSTANCED_SHAPES
constexpr size_t BITMAP_CACHE_MAX_BYTES
Texture memory the GPU canvases may hold for placed bitmaps before they evict the oldest.
std::vector< uint8_t > RgbToRgba(const uint8_t *aRgb, const uint8_t *aAlpha, size_t aPixelCount)
Expand packed RGB to tightly packed RGBA.
wxImage FitImageToTexture(const wxImage &aImage, int aMaxTextureSize)
Fit an image into the largest texture a GPU can hold.
constexpr int RHI_SHAPES
constexpr uint32_t RHI_CULL_VERTEX_RUN_LENGTH
Polygon vertices per run, three per triangle.
constexpr double GRID_EDGE_DARKEN
How far the hairline round a grid's coverage sits below its own colour.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
std::shared_ptr< PNS_LOG_VIEWER_OVERLAY > overlay
constexpr uint32_t RHI_GLYPH_TABLE_SIZE
Length of the shaders' glyph table.
RHI_HANDLE< RHI_TAG_TEXTURE > RHI_TextureHandle
Definition rhi_handle.h:84
RHI_HANDLE< RHI_TAG_BUFFER > RHI_BufferHandle
Definition rhi_handle.h:83
@ RHI_INSTANCE_FADE_SMALL
text stroke that fades as its glyph height (param 2) shrinks
@ RHI_INSTANCE_BITMAP_GLYPH
a track that draws the bitmap font glyph named by param 2
@ RHI_INSTANCE_OVERLAY_ALPHA
premultiplied with alpha squared, as the OpenGL overlay leaves it
@ RHI_INSTANCE_LENGTH_PAD
a track longer at each end by param 2 pixels
@ RHI_INSTANCE_NEGATIVE
@ RHI_INSTANCE_PIXEL_CIRCLE
a circle on whole pixels, as the OpenGL canvas draws it
@ RHI_INSTANCE_UNDIMMED
keeps its colour when its layer is dimmed
@ RHI_INSTANCE_GROW_OUTWARD
a PIXEL_CIRCLE stroke grows outward from its inner edge
@ RHI_INSTANCE_SCREEN_SPACE
@ RHI_INSTANCE_SCALE_LOD
a track shown only while the view scale is above param 3
@ RHI_INSTANCE_HARD_EDGE
no antialiased edge, for layers that take a pixel once
std::unique_ptr< RHI_DEVICE > RhiCreatePlatformDevice(bool aEnableVulkanValidation)
Create the device of the one RHI backend this platform is built with, Metal on macOS and Vulkan elsew...
bool RhiBackendNeedsYFlip(RHI_BACKEND backend)
void RhiAdjustProjectionForBackend(float *m, RHI_BACKEND backend)
Convert a column-major OpenGL projection, with NDC Z in [-1, 1] and Y up, in place to the backend's c...
RHI_BACKEND
Identifies which graphics backend a device implements.
Definition rhi_types.h:28
void RhiUploadAdoptStaging(RHI_STAGING &aUpload, std::vector< T > &&aVector)
Move the vector into the upload without a copy, for data the producer does not read after queuing.
void RhiUploadCopyStaging(RHI_STAGING &aUpload, const void *aSrc, size_t aBytes)
Copy the source into the upload, for data the producer keeps using after queuing.
const int scale
double orientation
Rotation about origin, radians CCW.
VECTOR2D extent
Cartesian: (dx, dy) = size/2 from origin.
VECTOR2D origin
A storage class for 128-bit hash value.
Definition hash_128.h:33
uint64_t Value64[2]
Definition hash_128.h:58
unsigned char pixels[1024 *1107 *3]
float cursorPos[2]
NDC [-1, 1].
float cursorSize
small cross extent in pixels
int cursorType
0 = small cross, 1 = fullscreen, 2 = diagonal 45°
GPU geometry of one frame, built by GAL_MODERN after EndDrawing() and passed through the FRAME_MAILBO...
RHI_SHAPE_COUNTS negCount
FRAME_UBO_DATA frameUBO
bool gridFirst
Draw the grid before the layers instead of over them.
bool relativeDepth
Element depth is relative to its layer's slice in LAYER_RANGE::depthNear, and the projection scales d...
RHI_SHAPE_COUNTS count
uint64_t bufferGeneration
Generation of the producer's buffers this descriptor was built from.
bool testAgainstStatic
Slices share the static target's layer order mapping, so this target can be depth tested against a co...
std::vector< LAYER_RANGE > layerRanges
std::vector< POLARITY_RUN > polarityRuns
static constexpr float RELATIVE_DEPTH_RANGE
Largest depth offset an element can have from its layer when relativeDepth is set.
CURSOR_UBO_DATA cursorUBO
std::vector< BITMAP_DRAW_DESC > bitmapDraws
std::array< RHI_BufferHandle, RHI_SHAPES > buffer
Each shape's buffer, normal elements before negatives.
float projection[16]
column-major for GLSL
float viewScale
the view's scale, which levels of detail are compared with
The spacing of a grid source's lattice as it is drawn at the current zoom.
VECTOR2D pitch
along x and y, or of radius and angle for a polar grid
GPU texture per bitmap, keyed on the image KIID so redrawing reuses the upload.
Definition gal_modern.h:837
std::array< uint64_t, 2 > contentHash
Definition gal_modern.h:842
RHI_TextureHandle texture
Definition gal_modern.h:838
size_t bytes
texture memory, which can be less than the image takes
Definition gal_modern.h:839
uint64_t lastUse
use counter for LRU eviction
Definition gal_modern.h:840
bool ownsTexture
false when the shared cache owns the texture
Definition gal_modern.h:273
std::array< uint64_t, 2 > contentHash
Definition gal_modern.h:274
RHI_BufferHandle vertexBuffer
Definition gal_modern.h:271
uint32_t slotCount[ARENA_COUNT]
Definition gal_modern.h:322
std::vector< RHI_VERTEX > polygonVertices
Definition gal_modern.h:293
RETAINED_GROUP_ORDER order
Definition gal_modern.h:314
float baseDepth
Layer depth the group was cached at.
Definition gal_modern.h:308
uint64_t placeSeq
When the group was placed in the scene, which orders a layer's polarity runs.
Definition gal_modern.h:305
std::vector< BITMAP_QUAD > bitmapQuads
Definition gal_modern.h:294
bool undimmed
Keeps its colour when its layer is dimmed, see ChangeGroupUndimmed.
Definition gal_modern.h:302
uint32_t slotStart[ARENA_COUNT]
Definition gal_modern.h:321
bool negative
Recorded as a knockout, so it erases instead of drawing.
Definition gal_modern.h:299
bool resident
Slot held in each arena of sceneLayer while resident.
Definition gal_modern.h:319
int sceneLayer
Placement in the retained scene, set by SetGroupPlacement.
Definition gal_modern.h:311
std::optional< std::multimap< double, int >::iterator > lodEntry
Entry in m_lodGroups while a zoom can show or hide the group.
Definition gal_modern.h:316
bool drawsNegative
polarity of the last element drawn
Definition gal_modern.h:494
std::vector< POLARITY_RUN > polarityRuns
built from the marks by Flush()
Definition gal_modern.h:493
std::array< size_t, RHI_SHAPES > capacity
Definition gal_modern.h:527
std::shared_ptr< const RHI_RUN_TABLE > cullRuns
Bounds of the arrays above as last processed, for culling off-screen runs.
Definition gal_modern.h:534
RHI_SHAPE_COUNTS Sizes() const
Definition gal_modern.h:496
RHI_SHAPE_COUNTS lastCount
Elements of each shape as last processed, normal and negative.
Definition gal_modern.h:530
std::vector< RHI_VERTEX > polygonVertices
Definition gal_modern.h:489
std::array< RHI_BufferHandle, RHI_SHAPES > buffer
Definition gal_modern.h:526
std::vector< POLARITY_MARK > polarityMarks
Definition gal_modern.h:492
std::vector< BITMAP_QUAD > bitmapQuads
Definition gal_modern.h:490
Half-open index span touched by patches since the last upload, empty when end <= begin.
Definition gal_modern.h:617
A point in a pool's drawing order where the polarity of what is drawn changes.
Definition gal_modern.h:480
RHI_SHAPE_COUNTS at
elements of each shape drawn before it
Definition gal_modern.h:483
Retained scene arena holding one slot per placed group, patched in place so changing one item uploads...
Definition gal_modern.h:668
std::vector< PATCH_SPAN > dirty
spans to upload at the next Flush
Definition gal_modern.h:680
std::unordered_map< uint32_t, std::vector< uint32_t > > freeSlots
Freed slot starts by slot length, reused last-in first-out so a group re-cached with the same size ge...
Definition gal_modern.h:678
uint32_t dead
elements sitting in freed slots
Definition gal_modern.h:672
std::vector< T > elements
CPU mirror of the GPU buffer, dead slots included.
Definition gal_modern.h:669
size_t capacity
elements the GPU buffer can hold
Definition gal_modern.h:671
bool translucent
some element is drawn with less than full alpha
Definition gal_modern.h:708
bool visible
hidden layers keep their groups but are not uploaded
Definition gal_modern.h:689
bool overlayAlpha
see SetSceneLayerOverlayAlpha
Definition gal_modern.h:692
bool inOrder
declared, so it has a place in the draw order
Definition gal_modern.h:688
std::vector< POLARITY_RUN > runs
The layer's content in the order and polarity it was placed in.
Definition gal_modern.h:714
int bakedAlpha
hoisted alpha, or -1 when elements keep their own
Definition gal_modern.h:706
uint32_t alphaCount[256]
Elements per alpha value.
Definition gal_modern.h:705
std::vector< int > residents
groups holding a slot here
Definition gal_modern.h:697
SCENE_ARENA< RHI_VERTEX > polygons
Definition gal_modern.h:695
uint32_t knockoutGroups
residents recorded as knockouts
Definition gal_modern.h:716
size_t cullCounted[ARENA_COUNT]
Elements each arena held when cullRuns was counted.
Definition gal_modern.h:722
std::shared_ptr< const RHI_RUN_TABLE > cullRuns
Bounds of the arenas as last uploaded, for culling off-screen runs.
Definition gal_modern.h:719
SCENE_ARENA< RHI_INSTANCE > instances[ARENA_INSTANCE_COUNT]
Definition gal_modern.h:694
std::vector< int > bitmapResidents
the residents that carry bitmap quads
Definition gal_modern.h:698
std::optional< std::unordered_multimap< size_t, int > > retainedPrefixIndex
Definition gal_modern.h:699
Where a cached group landed in the static pool at the last full build, so a colour or depth change is...
Definition gal_modern.h:607
Render-time projection of a grid: GRID_GEOMETRY (kind/origin/pitch/orientation/extent) plus rendering...
bool highlighted
Render with edit-mode emphasis (selected grid).
float gridSpacing[2]
visible spacing from GetVisibleGridSize
float minorLineWidth
pixels, at least 1 and drawn rounded to whole pixels
float majorLineWorld
world units, see GAL::gridMinorLineWidth()
float worldPixelSize
world units that majorLineWorld is divided by
int gridStyle
0 = lines, 1 = dots, 2 = small cross
int gridTick
coarse grid multiplier
One layer's instance ranges within the flat GPU buffers, normal instances before negatives.
RHI_SHAPE_COUNTS negStart
RHI_SHAPE_COUNTS count
bool hasNegatives
Subtractive instances go through a temp FBO so their erasure stays within the layer.
bool singleCoverage
Draw straight into the target using the range's own depth slice, either taking each pixel once at tra...
float alpha
Compositing alpha.
RHI_SHAPE_COUNTS negCount
RENDER_TARGET target
Picks the static (cached) or dynamic scene pool for this range.
RHI_SHAPE_COUNTS start
Each shape's normal elements, then for an instanced shape its negatives.
uint32_t runStart
The range's runs in FRAME_DRAW_DESC::polarityRuns, none when its order does not matter.
A stretch of one layer range drawn in one polarity.
RHI_SHAPE_COUNTS start
RHI_SHAPE_COUNTS count
std::array< uint64_t, 2 > contentHash
bool SamePrefix(const RETAINED_GROUP_ORDER &aOther) const
Fade of a whole layer towards a colour, laid out as the push constants of layer_dim....
float keep
1 leaves the layer as it is
RHI_BUFFER_TYPE type
Definition rhi_buffer.h:35
RHI_BufferHandle targetBuffer
Per-instance data for instanced 2D rendering, packed into 40 bytes.
uint16_t depth
void SetDepth(float aNormalized)
void SetFlag(RHI_INSTANCE_FLAG aFlag)
bool HasFlag(RHI_INSTANCE_FLAG aFlag) const
uint16_t scaleX
uint16_t pixelPad
extra stroke width in sixteenths of a pixel
uint16_t scaleY
void SetColor(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA=255)
void SetParam(int aIndex, float aValue)
void ClearFlag(RHI_INSTANCE_FLAG aFlag)
void SetRotation(float aRadians)
void SetPosition(float aX, float aY)
RHI_TEXTURE_FORMAT format
Definition rhi_texture.h:40
RHI_FILTER filter
Definition rhi_texture.h:41
Position is in world coordinates, color is premultiplied alpha and u, v hold half floats.
Definition rhi_vertex.h:37
uint16_t u
Definition rhi_vertex.h:44
uint8_t a
Definition rhi_vertex.h:43
void SetPosition(float aX, float aY)
Definition rhi_vertex.h:47
void SetColor(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA=255)
Definition rhi_vertex.h:48
void SetUV(float aU, float aV)
Definition rhi_vertex.h:52
netlist clear()
KIBIS top(path, &reporter)
VECTOR2I center
VECTOR2I end
int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
#define M_PI
wxLogTrace helper definitions.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707