50void ApplyRenderThreadPriority(
const wxString& aPriority )
52 const bool low = aPriority.CmpNoCase( wxS(
"low" ) ) == 0;
53 const bool high = aPriority.CmpNoCase( wxS(
"high" ) ) == 0;
59 wxLogTrace(
traceRhi, wxS(
"RenderThread: could not set thread priority '%s'" ), aPriority );
65 KIGFX::WINDOW_STATE::Clock::time_point aNow )
67 if( aThen.time_since_epoch().count() == 0 )
70 return static_cast<long long>(
71 std::chrono::duration_cast<std::chrono::milliseconds>( aNow - aThen ).count() );
76 KIGFX::WINDOW_STATE::Clock::time_point aEnd )
78 if( aStart.time_since_epoch().count() == 0 || aEnd.time_since_epoch().count() == 0 )
81 return std::chrono::duration<double, std::milli>( aEnd - aStart ).count();
110 const uint32_t room = renderSize < aMaxTextureSize
122 uint32_t aMaxTextureSize )
124 uint32_t factor = std::clamp<uint32_t>( aRequested, 1, 4 );
149 const bool sameContent = aDrawn.
valid && aDrawn.
epoch == aEpoch && ( !aDrawsGrid || sameGrid );
156 if( shift &&
std::abs( shift->x ) <=
static_cast<int>( aDrawn.
margin )
165 plan.drawMargin = aMargin;
181 const float w =
static_cast<float>( aWS.
StaticWidth() );
182 const float h =
static_cast<float>( aWS.
StaticHeight() );
186 layer.uvOffset[0] = (
static_cast<float>( aWS.
staticMargin ) - aShift.
x ) * ss / w;
187 layer.uvOffset[1] = (
static_cast<float>( aWS.
staticMargin ) - aShift.
y ) * ss / h;
204 KIGFX::WINDOW_STATE::Clock::time_point aNow,
205 std::chrono::milliseconds aBackoff =
206 std::chrono::milliseconds( 250 ) )
211 return std::chrono::duration_cast<std::chrono::milliseconds>(
239 return static_cast<bool>( aWS.
swapchain );
242 auto now = WINDOW_STATE::Clock::now();
253 wxLogTrace(
traceRhi, wxS(
"RenderThread: refreshing surface before "
254 "swapchain retry windowId=%u reason=%s "
255 "attempt=%llu native=%p" ),
270 wxLogTrace(
traceRhi, wxS(
"RenderThread: refreshed surface windowId=%u -> %s" ),
291 wxLogTrace(
traceRhi, wxS(
"RenderThread: EnsureSwapchain reason=%s windowId=%u "
292 "attempt=%llu %ux%u -> %s" ),
305 return static_cast<bool>( aWS.
swapchain );
323 if(
m_running.load( std::memory_order_acquire ) )
328 std::promise<bool> started;
329 std::future<bool> initialized = started.get_future();
333 if( !initialized.get() )
347 return m_running.load( std::memory_order_acquire );
419 auto done = std::make_shared<std::promise<void>>();
420 std::future<void> completed = done->get_future();
424 fence.
onComplete = [done, work = std::move( aWork )]()
435 return completed.wait_for( aTimeout ) == std::future_status::ready;
443 std::vector<uint8_t> pixels;
446 uint32_t backingWidth = 0;
447 uint32_t backingHeight = 0;
451 auto out = std::make_shared<READBACK>();
453 auto readback = [
this, aWindowId, out]()
467 std::vector<COMPOSITOR_LAYER> layers;
478 pictureDesc.
width = rw;
483 ?
m_device->CreateFramebuffer( pictureDesc )
486 std::vector<uint8_t> renderPixels(
static_cast<size_t>( rw ) * rh * 4 );
488 const bool read = picture
490 &&
m_device->ReadbackFramebufferPixels( picture, 0, renderPixels.data(),
491 renderPixels.size() );
494 m_device->DestroyFramebuffer( picture );
503 out->pixels = std::move( renderPixels );
506 if( !
runFenced( std::move( readback ), std::chrono::seconds( 5 ) ) || out->pixels.empty() )
509 const size_t pixelCount =
static_cast<size_t>( out->width ) * out->height;
511 if( !aImage.Create(
static_cast<int>( out->width ),
static_cast<int>( out->height ),
false ) )
514 unsigned char* rgb = aImage.GetData();
517 for(
size_t i = 0; i < pixelCount; ++i )
518 std::memcpy( rgb + i * 3, out->pixels.data() + i * 4, 3 );
520 if( out->backingWidth != out->width || out->backingHeight != out->height )
522 aImage.Rescale(
static_cast<int>( out->backingWidth ),
static_cast<int>( out->backingHeight ),
523 wxIMAGE_QUALITY_BOX_AVERAGE );
526 return aImage.IsOk();
532 wxLogTrace(
traceRhi, wxS(
"RenderThread: Shutdown requested (running=%s)" ),
533 m_running.load( std::memory_order_acquire ) ?
"yes" :
"no" );
535 if( !
m_running.load( std::memory_order_acquire ) )
546 wxLogTrace(
traceRhi, wxS(
"RenderThread: waiting for thread join" ) );
551 wxLogTrace(
traceRhi, wxS(
"RenderThread: joined" ) );
557 wxLogTrace(
traceRhi, wxS(
"RenderThread: starting" ) );
561 bool initialized =
false;
572 wxLogTrace(
traceRhi, wxS(
"RenderThread: device init FAILED" ) );
577 if( deviceReady && !initialized )
578 wxLogTrace(
traceRhi, wxS(
"RenderThread: InitRenderInfrastructure FAILED" ) );
580 catch(
const std::exception& e )
582 wxLogTrace(
traceRhi, wxS(
"RenderThread: EXCEPTION during init: %s" ), e.what() );
586 wxLogTrace(
traceRhi, wxS(
"RenderThread: UNKNOWN EXCEPTION during init" ) );
598 aStarted.set_value(
false );
604 m_running.store(
true, std::memory_order_release );
611 wxLogTrace(
traceRhi, wxS(
"RenderThread: device '%s' initialized OK" ),
m_device->GetName() );
613 aStarted.set_value(
true );
615 wxLogTrace(
traceRhi, wxS(
"RenderThread: entering main loop" ) );
632 std::this_thread::sleep_for( std::chrono::milliseconds( 8 ) );
654 needsTick = needsTick || !ws.swapchain;
656 std::chrono::microseconds tick = needsTick ? std::chrono::milliseconds( 16 )
657 : std::chrono::milliseconds( 500 );
662 - WINDOW_STATE::Clock::now() );
664 tick = std::clamp( due, std::chrono::microseconds( 0 ), tick );
675 std::memory_order_acquire )
678 std::memory_order_acquire );
682 catch(
const std::exception& e )
684 wxLogTrace(
traceRhi, wxS(
"RenderThread: EXCEPTION in main loop phase=%d: %s" ),
685 m_loopPhase.load( std::memory_order_relaxed ), e.what() );
689 wxLogTrace(
traceRhi, wxS(
"RenderThread: UNKNOWN EXCEPTION in main loop "
695 wxLogTrace(
traceRhi, wxS(
"RenderThread: shutting down (windows=%zu)" ),
m_windows.size() );
713 m_running.store(
false, std::memory_order_release );
715 wxLogTrace(
traceRhi, wxS(
"RenderThread: shutdown complete" ) );
728 using T = std::decay_t<
decltype( arg )>;
730 if constexpr( std::is_same_v<T, RHI_CMD_FRAME_REQUEST> )
735 else if constexpr( std::is_same_v<T, RHI_CMD_SWAPCHAIN_CREATE> )
741 else if constexpr( std::is_same_v<T, RHI_CMD_SWAPCHAIN_RESIZE> )
746 else if constexpr( std::is_same_v<T, RHI_CMD_SWAPCHAIN_DESTROY> )
750 else if constexpr( std::is_same_v<T, RHI_CMD_FENCE> )
780 using T = std::decay_t<
decltype( arg )>;
782 if constexpr( std::is_same_v<T, RHI_GEOMETRY_UPLOAD> )
784 if( arg.targetBuffer && arg.HasStaging() )
786 m_device->UpdateBuffer( arg.targetBuffer, arg.stagingData,
787 arg.stagingSize, arg.targetOffset );
790 else if constexpr( std::is_same_v<T, RHI_TEXTURE_UPLOAD> )
792 if( arg.targetTexture && arg.HasStaging() )
793 m_device->UploadTexture( arg.targetTexture, arg.stagingData, arg.stagingSize );
795 else if constexpr( std::is_same_v<T, RHI_BUFFER_CREATE> )
800 m_device->RealizeBuffer( arg.handle, arg.desc );
802 else if constexpr( std::is_same_v<T, RHI_TEXTURE_CREATE> )
805 m_device->RealizeTexture( arg.handle, arg.desc );
807 else if constexpr( std::is_same_v<T, RHI_BUFFER_DESTROY> )
809 m_device->DestroyBuffer( arg.handle );
811 else if constexpr( std::is_same_v<T, RHI_TEXTURE_DESTROY> )
813 m_device->DestroyTexture( arg.handle );
839 auto setVert = [](
RHI_VERTEX& v,
float px,
float py,
float tu,
float tv )
846 setVert( unitQuad[0], -1.0f, -1.0f, 0.0f, 0.0f );
847 setVert( unitQuad[1], 1.0f, -1.0f, 1.0f, 0.0f );
848 setVert( unitQuad[2], 1.0f, 1.0f, 1.0f, 1.0f );
849 setVert( unitQuad[3], -1.0f, -1.0f, 0.0f, 0.0f );
850 setVert( unitQuad[4], 1.0f, 1.0f, 1.0f, 1.0f );
851 setVert( unitQuad[5], -1.0f, 1.0f, 0.0f, 1.0f );
853 auto makeBuffer = [&](
RHI_BUFFER_TYPE aType,
const void* aData,
size_t aSize )
862 m_device->UpdateBuffer( buffer, aData, aSize );
868 const RHI_VERTEX corners[4] = { unitQuad[0], unitQuad[1], unitQuad[2], unitQuad[5] };
869 const uint16_t quadIndices[6] = { 0, 1, 2, 0, 2, 3 };
967 std::shared_ptr<const RHI_GLYPH_ATLAS_DATA> atlas;
981 texDesc.
width = atlas ? atlas->width : 1;
982 texDesc.
height = atlas ? atlas->height : 1;
987 const std::vector<uint8_t> blankTexel( 4, 0 );
992 bufDesc.
sizeBytes = blankTable.size() *
sizeof( float );
1002 const std::vector<uint8_t>& texels = atlas ? atlas->rgba : blankTexel;
1003 const std::vector<float>&
table = atlas ? atlas->table : blankTable;
1026 wxLogTrace(
traceRhi, wxS(
"RenderThread: FrameRequest windowId=%u UNKNOWN" ),
1053 const bool swapchainMissing = !ws.swapchain;
1055 if( swapchainMissing )
1058 if( swapchainMissing && ws.swapchain && ( ws.frameResultPending || ws.presentSuccessCount > 0 ) )
1063 switch(
m_device->PollSurfaceExtent( ws.swapchain ) )
1067 auto due = WINDOW_STATE::Clock::now() + std::chrono::milliseconds( 50 );
1073 if( ws.frameResultPending || ws.presentSuccessCount > 0 )
1076 ws.nextPresentRetryAt = {};
1082 ws.active = ws.frameResultPending || ws.presentSuccessCount > 0;
1086 auto due = WINDOW_STATE::Clock::now() + std::chrono::milliseconds( 50 );
1097 if( !ws.swapchain && ( ws.frameResultPending || ws.presentSuccessCount > 0 ) )
1099 auto due = WINDOW_STATE::Clock::now() + std::chrono::milliseconds( 50 );
1104 if( ws.nextPresentRetryAt.time_since_epoch().count() != 0 )
1106 if( WINDOW_STATE::Clock::now() < ws.nextPresentRetryAt )
1112 ws.nextPresentRetryAt = {};
1113 ws.active = ws.frameResultPending;
1126 return a->lastActivityTime > b->lastActivityTime;
1139 const bool newInput = frameMailbox && frameMailbox->
PendingInputSerial() > ws->drawnInputSerial;
1140 const bool capped = ws->swapchain && ws->refreshHz > 0.0f
1141 && !
m_device->PresentWaitsForDisplay( ws->swapchain )
1142 && !
m_device->PresentReplacesQueued( ws->swapchain );
1143 const WINDOW_STATE::Clock::time_point frameStart = WINDOW_STATE::Clock::now();
1145 if( capped && !newInput && !aIgnorePacing && frameStart < ws->nextFrameAt )
1153 ws->nextFrameAt =
RhiNextFrameAt( frameStart,
static_cast<double>( ws->refreshHz ),
1154 m_device->LastDisplayRefresh( ws->swapchain ) );
1165 m_device->WaitForPresentSlot( ws->swapchain, std::chrono::milliseconds( 50 ), allowed );
1168 if( std::unique_ptr<FRAME_PACKET> packet = frameMailbox->
Load() )
1174 ws->drawDesc = std::move( packet->drawDesc );
1175 ws->staticDrawDesc = std::move( packet->staticDrawDesc );
1176 ws->dynamicDrawDesc = std::move( packet->dynamicDrawDesc );
1177 ws->staticChanged = packet->staticChanged;
1178 ws->dynamicChanged = packet->dynamicChanged;
1179 ws->staticEpoch = packet->staticEpoch;
1180 ws->dynamicEpoch = packet->dynamicEpoch;
1181 ws->drawnInputSerial = packet->inputSerial;
1188 if( factor != ws->supersampleFactor )
1190 ws->supersampleFactor = factor;
1195 ws->needsStaticInvalidate =
true;
1200 const bool ownCameras = packet->staticEpoch != 0;
1206 aDst.frameUBO = ws->drawDesc.frameUBO;
1207 aDst.gridUBO = ws->drawDesc.gridUBO;
1208 aDst.drawGrid = aDst.drawGrid || ws->drawDesc.drawGrid;
1211 aDst.cursorUBO = ws->drawDesc.cursorUBO;
1212 aDst.drawCursor = aDst.drawCursor || ws->drawDesc.drawCursor;
1213 aDst.linePipelineKind = ws->drawDesc.linePipelineKind;
1215 for(
int c = 0; c < 4; ++c )
1216 aDst.backgroundColor[c] = ws->drawDesc.backgroundColor[c];
1219 propagateFrameState( ws->staticDrawDesc );
1220 propagateFrameState( ws->dynamicDrawDesc );
1224 WINDOW_STATE::Clock::time_point now = WINDOW_STATE::Clock::now();
1225 uint64_t pendingGenerations = ws->requestedGeneration >= ws->lastRenderedGeneration
1226 ? ws->requestedGeneration - ws->lastRenderedGeneration
1229 if( pendingGenerations >= 60
1231 && ( ws->lastLagLogTime.time_since_epoch().count() == 0
1234 wxLogTrace(
traceRhi, wxS(
"RenderThread: window backlog windowId=%u pending=%llu "
1235 "reqGen=%llu rendGen=%llu reqAge=%lldms "
1236 "renderAge=%lldms phase=%d" ),
1238 static_cast<unsigned long long>( pendingGenerations ),
1239 static_cast<unsigned long long>( ws->requestedGeneration ),
1240 static_cast<unsigned long long>( ws->lastRenderedGeneration ),
1244 ws->lastLagLogTime = now;
1247 const uint64_t attemptedGeneration = ws->requestedGeneration;
1248 const uint64_t presentFailuresBefore = ws->presentFailureCount;
1253 ws->consecutivePresentFailures = 0;
1254 ws->nextPresentRetryAt = {};
1256 else if( ws->frameResultPending && ws->swapchain
1257 && ws->presentFailureCount > presentFailuresBefore )
1259 ws->consecutivePresentFailures = std::min( ws->consecutivePresentFailures + 1, 5u );
1260 auto delay = std::chrono::milliseconds( 50u << ( ws->consecutivePresentFailures - 1 ) );
1261 ws->nextPresentRetryAt = WINDOW_STATE::Clock::now() + delay;
1267 if( presented && ws->frameResultPending )
1269 ws->lastRenderedGeneration = attemptedGeneration;
1270 ws->frameResultPending =
false;
1273 ws->active = !presented && ws->nextPresentRetryAt.time_since_epoch().count() != 0;
1274 ws->framesProcessed++;
1288 [[maybe_unused]]
double geometryMs = 0.0;
1289 [[maybe_unused]]
double overlayMs = 0.0;
1290 [[maybe_unused]]
double compositeMs = 0.0;
1291 [[maybe_unused]]
double presentMs = 0.0;
1296 wxLogTrace(
traceRhi, wxS(
"RenderThread: RenderWindow windowId=%u SKIP "
1297 "(backing %ux%u)" ),
1312 m_loopPhase.store( 4, std::memory_order_relaxed );
1326 return aRendered.
valid && aRendered.
epoch == aEpoch && aRendered.
drawGrid == aDesc.drawGrid
1327 && aRendered.
frameUBO == aDesc.frameUBO && ( !aDesc.drawGrid || aRendered.
gridUBO == aDesc.gridUBO );
1333 aRendered.
valid =
true;
1334 aRendered.
epoch = aEpoch;
1335 aRendered.
drawGrid = aDesc.drawGrid;
1336 aRendered.
frameUBO = aDesc.frameUBO;
1337 aRendered.
gridUBO = aDesc.gridUBO;
1353 uint32_t staticDrawMargin = 0;
1363 renderStatic =
false;
1366 staticDrawMargin =
plan.drawMargin;
1405 return aRange.target != TARGET_OVERLAY && aRange.HasGeometry();
1419 return aRange.target == TARGET_OVERLAY && aRange.HasGeometry()
1420 && RhiRangeReaches( aRange, reach );
1478 return std::any_of( aDesc.layerRanges.begin(), aDesc.layerRanges.end(),
1481 return aRange.difference;
1492 geometryMs = geometryTimer.
msecs();
1498 overlayMs = overlayTimer.
msecs();
1505 std::vector<COMPOSITOR_LAYER> layers;
1513 layers.push_back( { aWS.
dynamicFBO, 1.0f } );
1520 m_loopPhase.store( 5, std::memory_order_relaxed );
1529 compositeMs = compositeTimer.
msecs();
1546 presentRect = &viewOfStatic;
1549 bool presented =
false;
1553 m_loopPhase.store( 6, std::memory_order_relaxed );
1557 presentMs = presentTimer.
msecs();
1560 static const int loseAt = []()
1562 const char* value = std::getenv(
"KICAD_RHI_LOSE_DEVICE" );
1563 return value ? std::atoi( value ) : 0;
1566 static std::atomic<int> presents{ 0 };
1573 wxLogTrace(
traceRhi, wxS(
"RenderThread: Device lost during present windowId=%u" ), aWS.
windowId );
1588 wxLogTrace(
traceRhi, wxS(
"RenderThread: Present FAILED windowId=%u "
1589 "srcFBO=%s reqGen=%llu presentFail=%llu "
1590 "result=%d reqAge=%lldms renderAge=%lldms" ),
1591 aWS.
windowId, compositorOutput ?
"valid" :
"null",
1594 static_cast<int>( presentResult ),
1617 wxLogTrace(
traceRhi, wxS(
"Perf: RenderWindow windowId=%u gen=%llu total=%.3f "
1618 "queue=%.3f geometry=%.3f overlay=%.3f composite=%.3f "
1619 "present=%.3f reuseStatic=%d tracks=%u arcs=%u "
1620 "pads=%u polys=%u bitmaps=%llu layers=%llu passes=%llu" ),
1624 queueWaitMs, geometryMs, overlayMs, compositeMs, presentMs,
1647 bool aLogSummary,
GEOMETRY_RANGES aRanges, uint32_t aStaticDrawMargin )
1654 auto drawsRange = [&](
const LAYER_RANGE& aRange )
1660 bool premultipliedRange =
false;
1669 aPipeline = premultiplied != aPipeline ? premultiplied :
m_pipelines->GetOverlayVariant( aPipeline );
1671 else if( premultipliedRange )
1672 aPipeline =
m_pipelines->GetPremultipliedVariant( aPipeline );
1674 m_device->BindPipeline( aPipeline );
1682 wxLogTrace(
traceRhi, wxS(
"RenderThread: RenderGeometry windowId=%u "
1683 "tracks=%u/%u arcs=%u/%u pads=%u/%u polys=%u bitmaps=%zu "
1684 "grid=%d cursor=%d layers=%zu" ),
1700 const bool drawsMargin = staticTarget && aStaticDrawMargin > 0;
1706 viewport.
x =
static_cast<float>( inset );
1707 viewport.
y =
static_cast<float>( inset );
1708 viewport.
width =
static_cast<float>( drawsMargin ? targetWidth : aWS.
RenderWidth() );
1709 viewport.
height =
static_cast<float>( drawsMargin ? targetHeight : aWS.
RenderHeight() );
1712 scissor.
x =
static_cast<int32_t
>( inset );
1713 scissor.
y =
static_cast<int32_t
>( inset );
1714 scissor.
width =
static_cast<uint32_t
>( viewport.
width );
1715 scissor.
height =
static_cast<uint32_t
>( viewport.
height );
1732 return aRange.HasGeometry() && aRange.NeedsTempFBO();
1736 tempDesc.
width = targetWidth;
1737 tempDesc.
height = targetHeight;
1751 return aRange.difference && aRange.HasGeometry() && drawsRange( aRange );
1755 belowDesc.
width = targetWidth;
1756 belowDesc.
height = targetHeight;
1757 belowFBO =
m_device->CreateFramebuffer( belowDesc );
1786 bindPipeline( aPipeline );
1788 m_device->SetPushConstants( &aDim,
sizeof( aDim ) );
1792 uint32_t aFirstInstance = 0,
const RHI_LAYER_DIM& aDim = {} )
1794 if( !aPipeline || !aInstanceBuffer || aInstanceCount == 0 || !frameBG )
1797 bindGeometry( aPipeline, aDim );
1798 m_device->BindVertexBuffer( 1, aInstanceBuffer );
1804 m_device->DrawIndexedInstanced( 6, aInstanceCount, 0, 0, aFirstInstance );
1809 m_device->DrawInstanced( 6, aInstanceCount, 0, 0, aFirstInstance );
1816 || !aBitmap.vertexBuffer || !aBitmap.texture )
1827 texBGDesc.
entries.push_back( { 0, {}, aBitmap.texture, 0, 0 } );
1834 m_device->BindVertexBuffer( 0, aBitmap.vertexBuffer );
1835 m_device->Draw( aBitmap.vertexCount );
1836 m_device->DestroyBindGroup( texBG );
1850 return m_pipelines->GetShapePipeline( aShape, aMode );
1853 auto drawGrid = [&]()
1864 auto drawLayerGeometry = [&](
const LAYER_RANGE& aRange )
1870 return aOwn ? aOwn : aShared;
1877 auto setSlice = [&](
bool aNearHalf,
bool aOrdered )
1893 if( step.count == 0 )
1898 const bool negatives = step.negatives && !polygon;
1904 const std::vector<RHI_RUN_BOUNDS>* runs =
1909 if( !frameBG || !pipeline || !buffer )
1912 bindGeometry( pipeline, aRange.
dim );
1913 m_device->BindVertexBuffer( 0, buffer );
1916 setSlice( step.nearHalf, step.ordered );
1919 [&]( uint32_t aFirst, uint32_t aCount )
1922 m_device->Draw( aCount, aFirst );
1924 drawInstanced( pipeline, buffer, aCount, aFirst, aRange.dim );
1931 bmpEnd =
static_cast<uint32_t
>( dd.
bitmapDraws.size() );
1935 setSlice(
false,
false );
1937 for( uint32_t bi = aRange.
bitmapStart; bi < bmpEnd; ++bi )
1943 if( !aFBO || !aRenderPass )
1950 if( !
m_device->BeginRenderPass( rpBegin ) )
1963 float aAlpha ) ->
bool
1978 bgDesc.
entries.push_back( {
static_cast<uint32_t
>( bgDesc.
entries.size() ), {}, tex, 0, 0 } );
1987 whole.
width =
static_cast<float>( targetWidth );
1988 whole.
height =
static_cast<float>( targetHeight );
1991 wholeScissor.
width = targetWidth;
1992 wholeScissor.
height = targetHeight;
1998 m_device->SetScissor( wholeScissor );
1999 m_device->BindVertexBuffer( 0, quadVB );
2001 m_device->SetPushConstants( &pc,
sizeof( pc ) );
2009 auto compositeTemp = [&](
float aAlpha )
2014 bindPipeline(
m_pipelines->GetInlineCompositorPipeline() );
2015 drawWhole(
m_compositor->GetSingleTexLayout(), { m_device->GetFramebufferColorTexture( tempFBO, 0 ) }, aAlpha );
2019 auto copyBelow = [&]() ->
bool
2021 if( !belowFBO || !
m_pipelines->GetDifferencePipeline()
2032 { m_device->GetFramebufferColorTexture( aTargetFBO, 0 ) }, 1.0f );
2038 auto compositeDifference = [&](
float aAlpha )
2042 { m_device->GetFramebufferColorTexture( tempFBO, 0 ),
2043 m_device->GetFramebufferColorTexture( belowFBO, 0 ) },
2047 auto renderLegacy = [&]()
2058 drawInstanced( shapePipeline(
RHI_SHAPE( shape ), mode ), dd.
buffer[shape],
2067 bindGeometry( zonePipeline, {} );
2079 auto renderLayered = [&]()
2082 const uint32_t staticX =
static_cast<uint32_t
>( aWS.
ShownStaticX() );
2083 const uint32_t staticY =
static_cast<uint32_t
>( aWS.
ShownStaticY() );
2101 const bool keepDepth = drawsStatic || std::any_of( dd.
layerRanges.begin(), dd.
layerRanges.end(), usesTemp );
2108 bool inStaticPass = beginPass( aTargetFBO, firstPass );
2113 const bool drawsGrid = dd.
drawGrid && !overlayPass;
2123 const bool useTempFBO = usesTemp( lr );
2135 drawLayerGeometry( lr );
2142 inStaticPass =
false;
2151 &&
m_device->CopyFramebufferDepth( aTargetFBO, tempFBO ) )
2156 if( !beginPass( tempFBO, tempPass ) )
2159 drawLayerGeometry( lr );
2162 const bool difference = lr.
difference && copyBelow();
2168 compositeDifference( lr.
alpha );
2170 compositeTemp( lr.
alpha );
2194 else if( !overlayPass )
2224 aUBO =
m_device->CreateBuffer( desc );
2230 m_device->UpdateBuffer( aUBO, aData, aSize );
2237 aBG =
m_device->CreateBindGroup( bgDesc );
2240 return static_cast<bool>( aBG );
2255 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainCreate windowId=%u %ux%u dpi=%.1f "
2262 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainCreate SKIPPED (window already "
2277 wxLogTrace(
traceRhi, wxS(
"RenderThread: backing resolution %ux%u" ),
2284 wxLogTrace(
traceRhi, wxS(
"RenderThread: surface=%s" ),
2285 ws.
surface ?
"OK" :
"FAILED" );
2297 wxLogTrace(
traceRhi, wxS(
"RenderThread: window %u created (FBOs: static=%s dynamic=%s)" ),
2299 it->second.staticFBO ?
"OK" :
"null",
2300 it->second.dynamicFBO ?
"OK" :
"null" );
2310 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainResize windowId=%u NOT FOUND" ),
2321 uint32_t newBackingW = 0;
2322 uint32_t newBackingH = 0;
2327 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainResize windowId=%u %ux%u -> %ux%u "
2328 "(backing %ux%u -> %ux%u, changed=%s)" ),
2331 newBackingW, newBackingH, backingChanged ?
"yes" :
"no" );
2338 bool resizeOk =
m_device->ResizeSwapchain( ws.
swapchain, newBackingW, newBackingH,
2341 wxLogTrace(
traceRhi, wxS(
"RenderThread: ResizeSwapchain windowId=%u %ux%u -> %s" ),
2342 aCmd.
windowId, newBackingW, newBackingH,
2343 resizeOk ?
"OK" :
"FAILED" );
2358 if( backingChanged )
2370 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainDestroy windowId=%u" ), aCmd.
windowId );
2376 wxLogTrace(
traceRhi, wxS(
"RenderThread: SwapchainDestroy NOT FOUND" ) );
2395 && std::ranges::any_of(
m_windows, [](
const auto& aEntry ) { return aEntry.second.active; } ) )
2487 m_device->Release( uniforms->frameBG );
2488 m_device->Release( uniforms->gridBG );
2489 m_device->Release( uniforms->frameUBO );
2490 m_device->Release( uniforms->gridUBO );
2501 uint32_t& aBackingW, uint32_t& aBackingH )
2503 aBackingW =
static_cast<uint32_t
>( std::floor(
static_cast<float>( aLogicalW ) * aDpiScale ) );
2504 aBackingH =
static_cast<uint32_t
>( std::floor(
static_cast<float>( aLogicalH ) * aDpiScale ) );
2506 if( aBackingW == 0 )
2509 if( aBackingH == 0 )
2516 wxLogTrace(
traceRhi, wxS(
"RenderThread: device lost, leaving recovery to the canvas" ) );
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
Lock-free single-slot mailbox carrying frame state from the main thread to the render thread.
std::unique_ptr< FRAME_PACKET > Load()
uint64_t PendingInputSerial() const
void CreateWindowFBOs(WINDOW_STATE &aWS)
RHI_BufferHandle m_unitQuadBuffer
RHI_BufferHandle m_quadIndexBuffer
its 2 triangles over those corners
std::mutex m_glyphAtlasMutex
std::unique_ptr< RHI_DEVICE > m_device
SYNC_QUEUE< RHI_UPLOAD_COMMAND > m_uploadQueue
bool prepareCursor(WINDOW_STATE &aWS)
Fill the cursor's uniforms.
std::unordered_set< RHI_WINDOW_ID > m_presentedWindows
bool WaitForFence(std::chrono::milliseconds aTimeout=std::chrono::milliseconds(2000))
Block until every command submitted before this call has been processed.
void ProcessActiveWindows(bool aIgnorePacing=false)
std::unordered_map< RHI_WINDOW_ID, WINDOW_STATE > m_windows
RHI_BufferHandle m_quadCornerBuffer
the unit quad's 4 corners
void ThreadMain(std::promise< bool > aStarted)
RHI_DEVICE_FACTORY m_deviceFactory
void deferFrameUntil(WINDOW_STATE::Clock::time_point aDue)
void DestroyRenderInfrastructure()
bool EnsureSwapchain(WINDOW_STATE &aWS, const char *aReason)
RHI_RenderPassHandle m_geometryRenderPass
void HandleSwapchainDestroy(const RHI_CMD_SWAPCHAIN_DESTROY &aCmd)
std::optional< WINDOW_STATE::Clock::time_point > m_deferredFrameDue
Earliest time a window held to its display's refresh rate is due, so the loop wakes for it.
RHI_RenderPassHandle m_geometryDepthLoadRenderPass
clears colour, keeps depth
void AddDeviceLostListener(const void *aOwner, std::function< void()> aListener)
Call aListener on the render thread when the device is lost.
bool runFenced(std::function< void()> aWork, std::chrono::milliseconds aTimeout)
Run work on the render thread after everything queued before it.
std::atomic< bool > m_uploadsPending
Set by producers and cleared by the drain so the loop can sleep without missing uploads.
bool m_acceptingWork
Guarded by m_workMutex.
void HandleSwapchainResize(const RHI_CMD_SWAPCHAIN_RESIZE &aCmd)
std::mutex m_presentedMutex
Window IDs are never reused, so an entry outliving its window is harmless.
std::atomic< bool > m_running
std::mutex m_deviceMutex
Guards the m_device pointer, not the device, against UI thread reads racing its creation or destructi...
void RemoveDeviceLostListener(const void *aOwner)
Once this returns, the owner's listener is not running and will not be called.
RHI_RenderPassHandle m_geometryLoadRenderPass
static void ComputeBackingResolution(uint32_t aLogicalW, uint32_t aLogicalH, float aDpiScale, uint32_t &aBackingW, uint32_t &aBackingH)
Compute the physical pixel backing size from logical size and DPI scale, at least 1x1.
void DestroyWindowUniforms(WINDOW_STATE &aWS)
bool IsDeviceLost() const
bool InitRenderInfrastructure()
void SetGlyphAtlas(std::shared_ptr< const RHI_GLYPH_ATLAS_DATA > aAtlas)
Give the track shaders the font atlas glyph instances draw from.
void SubmitUpload(RHI_UPLOAD_COMMAND aCmd)
Callable from any thread.
RHI_TextureHandle m_glyphTexture
The font atlas the track shaders draw glyphs from, its glyph table and their bind group.
bool HasPresented(RHI_WINDOW_ID aWindowId) const
RHI_BufferHandle m_glyphTable
bool ReadbackWindowPixels(RHI_WINDOW_ID aWindowId, wxImage &aImage)
Read back a window's picture at backing resolution without the overlay and cursor.
std::atomic< uint32_t > m_maxTextureSize
std::atomic< bool > m_shutdownRequested
std::atomic< RHI_BACKEND > m_backendType
void HandleFence(const RHI_CMD_FENCE &aCmd)
void HandleSwapchainCreate(const RHI_CMD_SWAPCHAIN_CREATE &aCmd)
bool writeUniform(RHI_BufferHandle &aUBO, RHI_BindGroupHandle &aBG, RHI_BindGroupLayoutHandle aLayout, const void *aData, size_t aSize)
Write a uniform block, making its buffer and bind group on first use.
std::atomic< bool > m_deviceLost
RHI_BindGroupHandle m_glyphBG
RHI_BindGroupHandle glyphBindGroup()
RHI_BufferHandle ReserveBufferHandle()
Reserve a handle without creating the GPU resource, so RHI_DEVICE_PROXY can hand it out on the UI thr...
RHI_TextureHandle ReserveTextureHandle()
RHI_RenderPassHandle m_geometryDepthDiscardRenderPass
As m_geometryRenderPass and m_geometryDepthLoadRenderPass, without storing depth nothing reads later.
GEOMETRY_RANGES
The ranges of a draw descriptor one geometry pass draws.
RENDER_THREAD(RHI_DEVICE_FACTORY aDeviceFactory)
void OnDeviceLost()
Stop drawing on a lost device.
void HandleFrameRequest(const RHI_CMD_FRAME_REQUEST &aCmd)
std::mutex m_deviceLostMutex
Held while a listener runs, so removing one waits for it to return.
void Shutdown()
Block until the render thread exits.
std::unordered_map< const void *, std::function< void()> > m_deviceLostListeners
std::condition_variable m_workCV
void recreateSwapchainAfterOutOfDate(WINDOW_STATE &aWS, bool aSurfaceLost=false)
Retire and recreate an out of date swapchain, and its surface too when aSurfaceLost.
std::unique_ptr< RHI_GAL_PIPELINES > m_pipelines
Shared by all windows and created once with the device.
void RenderGeometryInto(WINDOW_STATE &aWS, const FRAME_DRAW_DESC &aDesc, RHI_FramebufferHandle aFBO, bool aLogSummary, GEOMETRY_RANGES aRanges=GEOMETRY_RANGES::ALL, uint32_t aStaticDrawMargin=0)
bool RenderWindow(WINDOW_STATE &aWS)
std::unique_ptr< RHI_COMPOSITOR > m_compositor
RHI_RenderPassHandle m_geometryDepthLoadDiscardRenderPass
SYNC_QUEUE< RHI_CONTROL_COMMAND > m_controlQueue
bool SubmitControl(RHI_CONTROL_COMMAND aCmd)
void drawCursor(WINDOW_STATE &aWS)
Draw the cursor into the open composite pass.
bool Start()
Block until the render thread has created the device and its pipelines.
std::atomic< int > m_loopPhase
see GetLoopPhase()
void RenderGeometry(WINDOW_STATE &aWS)
std::vector< WINDOW_STATE * > m_activeWindowOrder
std::shared_ptr< const RHI_GLYPH_ATLAS_DATA > m_glyphAtlas
void destroyGlyphResources()
void DestroyWindowFBOs(WINDOW_STATE &aWS)
void destroyWindow(WINDOW_STATE &aWS)
Free a window's targets, uniforms, swapchain and surface.
A small class to help profiling.
double msecs(bool aSinceLast=false)
Creates, uses and destroys GPU resources through one graphics API backend.
void Release(RHI_BufferHandle &aHandle)
Destroy the resource, if there is one, and clear the handle.
const wxChar *const traceRhi
Flag to enable debug output of the RHI rendering backend.
static std::vector< PENDING_PROPERTY > plan(const EDA_ITEM &aSource, const EDA_ITEM &aTarget, const std::set< wxString > &aEnabledKeys)
Target properties to write, paired with values read off the source.
The Cairo implementation of the graphics abstraction layer.
GRID_UBO_DATA RhiSupersampleGrid(const GRID_UBO_DATA &aGrid, uint32_t aFactor)
bool RhiViewMovedOnly(const FRAME_UBO_DATA &aDrawn, const FRAME_UBO_DATA &aNow)
RHI_CULL_VIEW RhiCullView(const FRAME_UBO_DATA &aFrame)
Invert the frame's 2D projection over the normalized device square.
std::function< std::unique_ptr< RHI_DEVICE >()> RHI_DEVICE_FACTORY
Creates the RHI_DEVICE.
std::optional< RHI_VIEW_SHIFT > RhiViewShift(const FRAME_UBO_DATA &aDrawn, const FRAME_UBO_DATA &aNow)
constexpr uint32_t RhiCullRunLength(int aShape)
void RhiForVisibleStretches(const std::vector< RHI_RUN_BOUNDS > *aRuns, uint32_t aRunLength, uint32_t aStart, uint32_t aCount, const RHI_CULL_VIEW &aView, DRAW &&aDraw, RHI_CULL_COUNT *aTally=nullptr)
Call aDraw( first, count ) for each stretch of [aStart, aStart + aCount) whose runs can reach aView.
FRAME_UBO_DATA RhiSupersampleFrame(const FRAME_UBO_DATA &aFrame, uint32_t aFactor)
RHI_SHAPE
Shapes a frame draws, each with its own array and pipelines.
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
FRAME_UBO_DATA RhiWidenFrame(const FRAME_UBO_DATA &aFrame, uint32_t aMargin)
constexpr int RHI_INSTANCED_SHAPES
bool RhiGridMovedOnly(const GRID_UBO_DATA &aDrawn, const GRID_UBO_DATA &aNow)
@ MARK
write depth only, so the layer's later draws stay out of these pixels
@ NEGATIVE
erase what the layer has drawn so far, inside a temp target
@ ONCE
blend, but let each pixel take the layer only once
@ BLEND
ordinary alpha blending
std::vector< LAYER_DRAW_STEP > RhiPlanLayerDraws(const LAYER_RANGE &aRange, const std::vector< POLARITY_RUN > &aRuns={})
Order the draws of one layer range.
GRID_UBO_DATA RhiWidenGrid(const GRID_UBO_DATA &aGrid, uint32_t aMargin)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
static void drawBitmap(wxBitmap &aBitmap, wxColor aColor)
static bool ShouldRetrySwapchainCreate(const KIGFX::WINDOW_STATE &aWS, KIGFX::WINDOW_STATE::Clock::time_point aNow, std::chrono::milliseconds aBackoff=std::chrono::milliseconds(250))
static RHI_FRAMEBUFFER_DESC marginFramebufferDesc(const KIGFX::WINDOW_STATE &aWS)
static MARGIN_PLAN planMarginTarget(const KIGFX::WINDOW_STATE::RENDERED_CONTENT &aDrawn, uint64_t aEpoch, const KIGFX::FRAME_DRAW_DESC &aNow, uint32_t aMargin, bool aDrawsGrid)
static double WindowStateDurationMs(KIGFX::WINDOW_STATE::Clock::time_point aStart, KIGFX::WINDOW_STATE::Clock::time_point aEnd)
static RHI_FRAMEBUFFER_DESC layerFramebufferDesc(const KIGFX::WINDOW_STATE &aWS)
static long long WindowStateAgeMs(KIGFX::WINDOW_STATE::Clock::time_point aThen, KIGFX::WINDOW_STATE::Clock::time_point aNow)
static COMPOSITOR_LAYER marginLayer(const KIGFX::WINDOW_STATE &aWS, RHI_FramebufferHandle aFBO, const KIGFX::RHI_VIEW_SHIFT &aShift)
static void destroyDiffCopies(RHI_DEVICE &aDevice, KIGFX::WINDOW_STATE &aWS)
static uint32_t fitSupersampleFactor(uint32_t aRequested, const KIGFX::WINDOW_STATE &aWS, uint32_t aMaxTextureSize)
static uint32_t staticMarginFor(const KIGFX::WINDOW_STATE &aWS, uint32_t aMaxTextureSize)
constexpr uint32_t RHI_UBO_BINDING
MSL is generated with –msl-decoration-binding, so a Metal buffer index is its binding number in every...
std::variant< RHI_CMD_FRAME_REQUEST, RHI_CMD_SWAPCHAIN_CREATE, RHI_CMD_SWAPCHAIN_RESIZE, RHI_CMD_SWAPCHAIN_DESTROY, RHI_CMD_FENCE > RHI_CONTROL_COMMAND
One entry in the render thread control queue.
uint32_t RHI_WINDOW_ID
Routes control commands to a swapchain.
RHI_FRAME_OP_RESULT
Windows headers define ERROR as a macro.
constexpr uint32_t RHI_GLYPH_TABLE_BINDING
Binding of the glyph table beside the atlas texture at 0, see RHI_UBO_BINDING.
constexpr uint32_t RHI_GLYPH_TABLE_SIZE
Length of the shaders' glyph table.
RHI_HANDLE< RHI_TAG_FRAMEBUFFER > RHI_FramebufferHandle
RHI_HANDLE< RHI_TAG_TEXTURE > RHI_TextureHandle
RHI_HANDLE< RHI_TAG_BUFFER > RHI_BufferHandle
RHI_HANDLE< RHI_TAG_PIPELINE > RHI_PipelineHandle
RHI_HANDLE< RHI_TAG_BIND_GROUP > RHI_BindGroupHandle
RHI_HANDLE< RHI_TAG_BIND_GROUP_LAYOUT > RHI_BindGroupLayoutHandle
RHI_HANDLE< RHI_TAG_RENDER_PASS > RHI_RenderPassHandle
TIME_POINT RhiNextFrameAt(TIME_POINT aFrameStart, double aRefreshHz, std::optional< TIME_POINT > aVblank)
The earliest time the frame after one started at aFrameStart may start, for a window whose presents d...
constexpr int RHI_PRESENTS_IN_FLIGHT
Presents allowed to wait for the display at once while frames are paced.
std::variant< RHI_GEOMETRY_UPLOAD, RHI_TEXTURE_UPLOAD, RHI_BUFFER_CREATE, RHI_TEXTURE_CREATE, RHI_BUFFER_DESTROY, RHI_TEXTURE_DESTROY > RHI_UPLOAD_COMMAND
One entry in the render thread upload queue.
Matches the PushConstants block of the compositor shader.
One textured bitmap quad with its own vertex buffer and texture.
Cursor uniform block matching the CursorUBO layout in cursor.frag (std140).
GPU geometry of one frame, built by GAL_MODERN after EndDrawing() and passed through the FRAME_MAILBO...
RHI_LINE_PIPELINE_KIND linePipelineKind
RHI_SHAPE_COUNTS negCount
bool gridFirst
Draw the grid before the layers instead of over them.
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
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.
Per-frame uniform block matching FrameUBO in frame_ubo.glsl (std140).
Grid uniform block matching the GridUBO layout in grid.frag (std140).
One draw of a layer range.
One layer's instance ranges within the flat GPU buffers, normal instances before negatives.
std::array< RHI_BufferHandle, RHI_SHAPES > buffer
Buffers a retained scene layer owns per shape.
bool difference
A diff layer (GAL::StartDiffLayer) that replaces what is under it with the colour difference,...
bool overlayAlpha
Elements flagged RHI_INSTANCE_OVERLAY_ALPHA, drawn with RHI_GAL_PIPELINES::GetPremultipliedVariant.
std::shared_ptr< const RHI_RUN_TABLE > cullRuns
Bounds of the range's buffers so off-screen runs are skipped.
std::array< RHI_BufferHandle, RHI_SHAPES > negBuffer
bool NeedsTempFBO() const
float depthNear
Depth slice the range is drawn into.
float alpha
Compositing alpha.
The world rectangle a frame shows and the world size of one of its pixels.
Fade of a whole layer towards a colour, laid out as the push constants of layer_dim....
How far the view has moved since the static target was drawn, in whole frame pixels.
What a geometry target holds, so a frame asking for the same content at the same camera can reuse it.
uint32_t margin
how much of the static target's margin was drawn
Per-window state owned by the render thread.
RHI_VIEW_SHIFT staticShift
How far the view has moved since the static target was drawn.
RHI_FramebufferHandle diffBelowFBO
Copies of what lies under a difference layer, for the view sized targets and the static one.
RHI_FramebufferHandle staticDiffBelowFBO
RHI_FramebufferHandle dynamicFBO
uint32_t StaticHeight() const
Clock::time_point lastRenderStartTime
uint64_t swapchainCreateAttempts
RHI_FramebufferHandle overlayGeometryFBO
Overlay content, drawn from a transparent start and composited over the dynamic target (see RHI_GAL_P...
FRAME_UNIFORMS uniformSets[UNIFORM_GENERATIONS]
int32_t ShownStaticY() const
Clock::time_point lastFrameRequestTime
RENDERED_CONTENT renderedStatic
static constexpr size_t UNIFORM_GENERATIONS
A frame's passes are still queued when the next frame is built, so frames rotate through several unif...
Clock::time_point lastSwapchainCreateAttemptTime
RHI_SwapchainHandle swapchain
uint32_t supersampleFactor
Supersampling factor.
RHI_FramebufferHandle ShownStaticFBO() const
FRAME_DRAW_DESC dynamicDrawDesc
float refreshHz
A swapchain that presents immediately is drawn at most once per refresh unless the frame answers inpu...
bool staticInMargin
the content shown is in staticFBO
RHI_FramebufferHandle staticTempFBO
The static target's temp target, made when a static layer first needs one.
Clock::time_point lastPresentAttemptTime
RHI_NATIVE_WINDOW_HANDLE nativeHandle
Clock::time_point nextPresentRetryAt
uint64_t lastPresentedGeneration
bool needsStaticInvalidate
int32_t ShownStaticX() const
Where the view starts in the shown static target, in texels.
Clock::time_point lastPresentFailureTime
RHI_FramebufferHandle accumulationFBO
Color only, without depth or stencil.
RENDERED_CONTENT renderedDynamic
FRAME_DRAW_DESC staticDrawDesc
Static and dynamic pool descriptions, each drawn into its own target.
RHI_FramebufferHandle staticViewFBO
Static content drawn without the margin, as during a zoom, goes into this view sized target so the zo...
Clock::time_point lastActivityTime
RHI_FramebufferHandle overlayMarginFBO
Overlay content that stayed the same through a pan, drawn with the static target's margin.
RHI_VIEW_SHIFT overlayShift
How far the view has moved since overlayMarginFBO was drawn.
uint64_t geometryRenderPasses
render passes begun while drawing geometry
uint64_t presentFailureCount
uint32_t staticMargin
Frame pixels the static target holds beyond each view edge so a pan within them is only a shift.
uint64_t dynamicPasses
times dynamicFBO was re-rendered
RHI_SurfaceHandle surface
uint32_t RenderHeight() const
bool overlayGeometryDrawn
uint32_t StaticWidth() const
FRAME_UNIFORMS & Uniforms()
uint64_t requestedGeneration
Clock::time_point lastPresentSuccessTime
FRAME_MAILBOX * mailbox
Owned by the panel.
bool dynamicGeometryDrawn
An empty dynamicFBO is neither drawn nor composited.
RHI_FramebufferHandle staticFBO
uint32_t RenderWidth() const
RHI_FramebufferHandle tempFBO
uint64_t staticPasses
times staticFBO was re-rendered
RENDERED_CONTENT renderedOverlay
the overlay target keeps the static target's margin
Clock::time_point lastRenderCompleteTime
uint64_t presentSuccessCount
KIGFX::RHI_VIEW_SHIFT shift
std::vector< RHI_BIND_GROUP_RESOURCE > entries
RHI_BindGroupLayoutHandle layout
bool flushFrames
Draw the frames requested before the fence, for a reader of their pixels.
std::function< void()> onComplete
float refreshHz
display refresh rate, 0 when unknown
KIGFX::FRAME_MAILBOX * frame2DMailbox
Owned by the panel.
RHI_NATIVE_WINDOW_HANDLE nativeHandle
RHI_STORE_OP depthStoreOp
RHI_LOAD_OP stencilLoadOp
std::vector< RHI_TEXTURE_FORMAT > colorFormats
RHI_FramebufferHandle framebuffer
RHI_RenderPassHandle renderPass
std::vector< RHI_COLOR_ATTACHMENT_DESC > colorAttachments
RHI_DEPTH_STENCIL_ATTACHMENT_DESC depthStencilAttachment
Same pixel space as RHI_VIEWPORT.
Descriptor for creating a surface from a native window.
RHI_NATIVE_WINDOW_HANDLE window
Descriptor for creating a swapchain.
RHI_SurfaceHandle surface
RHI_TEXTURE_FORMAT format
Position is in world coordinates, color is premultiplied alpha and u, v hold half floats.
void SetPosition(float aX, float aY)
void SetColor(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA=255)
void SetUV(float aU, float aV)
Top-left origin, in device pixels.
wxLogTrace helper definitions.