Divide Framework 0.1
A free and open-source 3D Framework under heavy development
Loading...
Searching...
No Matches
RenderPassExecutor.cpp
Go to the documentation of this file.
1
2
5
8
10
12
16
24
29
31
37
38
39namespace Divide
40{
41 namespace
42 {
44 {
45 U16 _index{ U16_MAX };
46 U16 _framesSinceLastUsed{ U16_MAX };
47 };
48
49 // Remove materials that haven't been indexed in this amount of frames to make space for new ones
51 // Use to partition parallel jobs
53
54 template<typename DataContainer>
57
58
59 inline bool Contains( const BufferUpdateRange& lhs, const BufferUpdateRange& rhs ) noexcept
60 {
61 return lhs._firstIDX <= rhs._firstIDX && lhs._lastIDX >= rhs._lastIDX;
62 }
63
64 inline BufferUpdateRange GetPrevRangeDiff( const BufferUpdateRange& crtRange, const BufferUpdateRange& prevRange ) noexcept
65 {
66 if ( crtRange.range() == 0u )
67 {
68 return prevRange;
69 }
70
72 // We only care about the case where the previous range is not fully contained by the current one
73 if ( prevRange.range() > 0u && prevRange != crtRange && !Contains( crtRange, prevRange ) )
74 {
75 if ( prevRange._firstIDX < crtRange._firstIDX && prevRange._lastIDX > crtRange._lastIDX )
76 {
77 ret = prevRange;
78 }
79 else if ( prevRange._firstIDX < crtRange._firstIDX )
80 {
81 ret._firstIDX = prevRange._firstIDX;
82 ret._lastIDX = crtRange._firstIDX;
83 }
84 else if ( prevRange._lastIDX > crtRange._lastIDX )
85 {
86 ret._firstIDX = crtRange._lastIDX;
87 ret._lastIDX = prevRange._lastIDX;
88 }
89 else
90 {
92 }
93 }
94
95 return ret;
96 }
97
99 {
100 bool ret = false;
101 if ( target._firstIDX > source._firstIDX )
102 {
103 target._firstIDX = source._firstIDX;
104 ret = true;
105 }
106 if ( target._lastIDX < source._lastIDX )
107 {
108 target._lastIDX = source._lastIDX;
109 ret = true;
110 }
111
112 return ret;
113 };
114
116 {
117 if ( range._bufferUpdateRange._firstIDX > idx )
118 {
119 range._bufferUpdateRange._firstIDX = idx;
120 }
121 if ( range._bufferUpdateRange._lastIDX < idx )
122 {
123 range._bufferUpdateRange._lastIDX = idx;
124 }
125
126 range._highWaterMark = std::max( range._highWaterMark, idx + 1u );
127 }
128
130 {
131 LockGuard<SharedMutex> w_lock( range._lock );
132 UpdateBufferRangeLocked( range, idx );
133 }
134
135 void UpdateBufferRange( RenderPassExecutor::ExecutorBufferRange& range, const U32 minIdx, const U32 maxIdx )
136 {
137 LockGuard<SharedMutex> w_lock( range._lock );
138 UpdateBufferRangeLocked( range, minIdx );
139 UpdateBufferRangeLocked( range, maxIdx );
140 }
141
142 template<typename DataContainer>
144 {
145 if ( target.range() == 0u )
146 {
147 return;
148 }
149
151
152 const size_t bufferAlignmentRequirement = ShaderBuffer::AlignmentRequirement( executorBuffer._gpuBuffer->getUsage() );
153 const size_t bufferPrimitiveSize = executorBuffer._gpuBuffer->getPrimitiveSize();
154 if ( bufferPrimitiveSize < bufferAlignmentRequirement )
155 {
156 // We need this due to alignment concerns
157 if ( target._firstIDX % 2u != 0 )
158 {
159 target._firstIDX -= 1u;
160 }
161 if ( target._lastIDX % 2u == 0 )
162 {
163 target._lastIDX += 1u;
164 }
165 }
166
167 bufferPtr data = nullptr;
168 if constexpr ( std::is_same<DataContainer, RenderPassExecutor::BufferIndirectionData>::value )
169 {
170 data = &executorBuffer._data[target._firstIDX];
171
172 }
173 else
174 {
175 data = &executorBuffer._data._gpuData[target._firstIDX];
176 }
177
178 memCmdInOut._bufferLocks.push_back( executorBuffer._gpuBuffer->writeData( { target._firstIDX, target.range() }, data));
179 }
180
181 template<typename DataContainer>
183 {
185
186 BufferUpdateRange writeRange, prevWriteRange;
187 {
188 LockGuard<SharedMutex> r_lock( executorBuffer._range._lock );
189
190 if ( !MergeBufferUpdateRanges( executorBuffer._range._bufferUpdateRangeHistory.back(), executorBuffer._range._bufferUpdateRange ) )
191 {
192 NOP();
193 }
194 std::swap(writeRange, executorBuffer._range._bufferUpdateRange);
195
196 // We don't need to write everything again as big chunks have been written as part of the normal frame update process
197 // Try and find only the items unoutched this frame
198 prevWriteRange = GetPrevRangeDiff( executorBuffer._range._bufferUpdateRangeHistory.back(), executorBuffer._range._bufferUpdateRangePrev );
199 executorBuffer._range._bufferUpdateRangePrev.reset();
200 }
201
202 WriteToGPUBufferInternal( executorBuffer, writeRange, memCmdInOut );
203 WriteToGPUBufferInternal( executorBuffer, prevWriteRange, memCmdInOut );
204 }
205
206 template<typename DataContainer>
207 bool NodeNeedsUpdate( ExecutorBuffer<DataContainer>& executorBuffer, const U32 indirectionIDX )
208 {
210
211 {
212 SharedLock<SharedMutex> w_lock( executorBuffer._nodeProcessedLock );
213 if ( contains( executorBuffer._nodeProcessedThisFrame, indirectionIDX ) )
214 {
215 return false;
216 }
217 }
218
219 LockGuard<SharedMutex> w_lock( executorBuffer._nodeProcessedLock );
220 // Check again
221 if ( !contains( executorBuffer._nodeProcessedThisFrame, indirectionIDX ) )
222 {
223 executorBuffer._nodeProcessedThisFrame.push_back( indirectionIDX );
224 }
225 return true;
226 }
227
228 template<typename DataContainer>
230 {
232
233 {
234 LockGuard<SharedMutex> w_lock( executorBuffer._range._lock );
235 const BufferUpdateRange rangeWrittenThisFrame = executorBuffer._range._bufferUpdateRangeHistory.back();
236
237 // At the end of the frame, bump our history queue by one position and prepare the tail for a new write
238 PROFILE_SCOPE( "History Update", Profiler::Category::Scene );
239 for ( U8 i = 0u; i < executorBuffer._queueLength - 1u; ++i )
240 {
241 executorBuffer._range._bufferUpdateRangeHistory[i] = executorBuffer._range._bufferUpdateRangeHistory[i + 1];
242 }
243 executorBuffer._range._bufferUpdateRangeHistory[executorBuffer._queueLength - 1u].reset();
244
245 // We can gather all of our history (once we evicted the oldest entry) into our "previous frame written range" entry
246 executorBuffer._range._bufferUpdateRangePrev.reset();
247 for ( U32 i = 0u; i < executorBuffer._gpuBuffer->queueLength() - 1u; ++i )
248 {
250 }
251
252 executorBuffer._range._highWaterMark = 0u;
253 executorBuffer._gpuBuffer->incQueue();
254 }
255
256 // We need to increment our buffer queue to get the new write range into focus
257 LockGuard<SharedMutex> w_lock( executorBuffer._nodeProcessedLock );
259 }
260 }
261
264 std::array<bool, RenderPassExecutor::MAX_INDIRECTION_ENTRIES> RenderPassExecutor::s_indirectionFreeList{};
265
269
270
271 [[nodiscard]] U32 BufferUpdateRange::range() const noexcept
272 {
273 return _lastIDX >= _firstIDX ? _lastIDX - _firstIDX + 1u : 0u;
274 }
275
276 void BufferUpdateRange::reset() noexcept
277 {
278 _firstIDX = U32_MAX;
279 _lastIDX = 0u;
280 }
281
283 : _parent( parent )
284 , _context( context )
285 , _stage( stage )
286 {
287 _renderQueue = std::make_unique<RenderQueue>( parent.parent(), stage );
288
289 ShaderBufferDescriptor bufferDescriptor = {};
294 bufferDescriptor._bufferParams._elementSize = sizeof( IndirectIndexedDrawCommand );
295 bufferDescriptor._ringBufferLength = Config::MAX_FRAMES_IN_FLIGHT + 1u;
296 Util::StringFormat( bufferDescriptor._name, "CMD_DATA_{}", TypeUtil::RenderStageToString( stage ) );
297 _cmdBuffer = _context.newSB( bufferDescriptor );
298 }
299
300 void RenderPassExecutor::OnStartup( [[maybe_unused]] const GFXDevice& gfx )
301 {
302 s_indirectionFreeList.fill( true );
303
305 }
306
307 void RenderPassExecutor::OnShutdown( [[maybe_unused]] const GFXDevice& gfx )
308 {
309 s_globalDataInit = false;
310 s_OITCompositionPipeline = nullptr;
312 s_ResolveGBufferPipeline = nullptr;
313
315 }
316
317 void RenderPassExecutor::postInit( const Handle<ShaderProgram> OITCompositionShader,
318 const Handle<ShaderProgram> OITCompositionShaderMS,
319 const Handle<ShaderProgram> ResolveGBufferShaderMS )
320 {
321 if ( !s_globalDataInit )
322 {
323 s_globalDataInit = true;
324
325
326 PipelineDescriptor pipelineDescriptor;
327 pipelineDescriptor._stateBlock._cullMode = CullMode::NONE;
328 pipelineDescriptor._stateBlock._depthTestEnabled = false;
329 pipelineDescriptor._stateBlock._depthWriteEnabled = false;
331 pipelineDescriptor._shaderProgramHandle = ResolveGBufferShaderMS;
332 s_ResolveGBufferPipeline = _context.newPipeline( pipelineDescriptor );
333
335 state0.enabled( true );
336 state0.blendOp( BlendOperation::ADD );
337 state0.blendSrc( BlendProperty::INV_SRC_ALPHA );
338 state0.blendDest( BlendProperty::ONE );
339
340 pipelineDescriptor._shaderProgramHandle = OITCompositionShader;
341 s_OITCompositionPipeline = _context.newPipeline( pipelineDescriptor );
342
343 pipelineDescriptor._shaderProgramHandle = OITCompositionShaderMS;
344 s_OITCompositionMSPipeline = _context.newPipeline( pipelineDescriptor );
345
346 }
347
348 _transformBuffer._data._freeList.fill( true );
349 _materialBuffer._data._lookupInfo.fill( { INVALID_MAT_HASH, g_invalidMaterialIndex } );
350
351 ShaderBufferDescriptor bufferDescriptor = {};
355 bufferDescriptor._ringBufferLength = Config::MAX_FRAMES_IN_FLIGHT + 1u;
356 {// Node Transform buffer
357 bufferDescriptor._bufferParams._elementCount = to_U32( _transformBuffer._data._gpuData.size() );
358 bufferDescriptor._bufferParams._elementSize = sizeof( NodeTransformData );
359 Util::StringFormat( bufferDescriptor._name, "NODE_TRANSFORM_DATA_{}", TypeUtil::RenderStageToString( _stage ) );
360 _transformBuffer._gpuBuffer = _context.newSB( bufferDescriptor );
361 _transformBuffer._range._bufferUpdateRangeHistory.resize( bufferDescriptor._ringBufferLength );
362 _transformBuffer._queueLength = bufferDescriptor._ringBufferLength;
363 }
364 {// Node Material buffer
365 bufferDescriptor._bufferParams._elementCount = to_U32( _materialBuffer._data._gpuData.size() );
366 bufferDescriptor._bufferParams._elementSize = sizeof( NodeMaterialData );
367 Util::StringFormat( bufferDescriptor._name, "NODE_MATERIAL_DATA_{}", TypeUtil::RenderStageToString( _stage ) );
368 _materialBuffer._gpuBuffer = _context.newSB( bufferDescriptor );
369 _materialBuffer._range._bufferUpdateRangeHistory.resize( bufferDescriptor._ringBufferLength );
370 _materialBuffer._queueLength = bufferDescriptor._ringBufferLength;
371 }
372 {// Indirection Buffer
373 bufferDescriptor._bufferParams._elementCount = to_U32( _indirectionBuffer._data.size() );
374 bufferDescriptor._bufferParams._elementSize = sizeof( NodeIndirectionData );
375 Util::StringFormat( bufferDescriptor._name, "NODE_INDIRECTION_DATA_{}", TypeUtil::RenderStageToString( _stage ) );
376 _indirectionBuffer._gpuBuffer = _context.newSB( bufferDescriptor );
379 }
380 }
381
383 {
385
386 U8 boneCount = 0u;
387 U8 selectionFlag = 0u;
388 AnimEvaluator::FrameIndex frameIndex{};
389
391
392 if ( !NodeNeedsUpdate( _transformBuffer, indirectionIDX ) )
393 {
394 return;
395 }
396
397 PROFILE_SCOPE( "Buffer idx update", Profiler::Category::Scene );
398 U32 transformIdx = U32_MAX;
399 {
400 LockGuard<Mutex> w_lock( _transformBuffer._data._freeListlock );
401 for ( U32 idx = 0u; idx < Config::MAX_VISIBLE_NODES; ++idx )
402 {
403 if ( _transformBuffer._data._freeList[idx] )
404 {
405 _transformBuffer._data._freeList[idx] = false;
406 transformIdx = idx;
407 break;
408 }
409 }
410 DIVIDE_ASSERT( transformIdx != U32_MAX );
411 }
412
413 NodeTransformData& transformOut = _transformBuffer._data._gpuData[transformIdx];
414 // Out last used transform matrix is now our previous transform
416
417 const SceneGraphNode* node = rComp->parentSGN();
418
420 {
421 const AnimationComponent* animComp = node->get<AnimationComponent>();
422 boneCount = animComp->boneCount();
423 if ( animComp->playAnimations() )
424 {
425 frameIndex = animComp->frameIndex();
426 }
427 }
429 {
430 selectionFlag = to_U8( node->get<SelectionComponent>()->selectionType() );
431 }
432
433 const TransformComponent* const transform = node->get<TransformComponent>();
434 transform->getWorldMatrixInterpolated( transformOut._worldMatrix );
435 transform->getWorldRotationMatrixInterpolated( transformOut._normalMatrixW );
436 transformOut._normalMatrixW.setRow( 3, node->get<BoundsComponent>()->getBoundingSphere().asVec4() );
437
438 transformOut._normalMatrixW.element( 0, 3 ) = to_F32( Util::PACK_UNORM4x8(
439 0u,
440 0u,
441 rComp->getLoDLevel( _stage ),
442 rComp->occlusionCull() ? 1u : 0u
443 ) );
444
445 transformOut._normalMatrixW.element( 1, 3 ) = to_F32( std::max( frameIndex._curr, 0) );
446 transformOut._normalMatrixW.element( 2, 3 ) = to_F32( boneCount );
447
448
449 UpdateBufferRangeLocked( _transformBuffer._range, transformIdx );
450
451 if ( 0u == transformIdx ||
452 _indirectionBuffer._data[indirectionIDX][TRANSFORM_IDX] != transformIdx ||
453 _indirectionBuffer._data[indirectionIDX][SELECTION_FLAG] != selectionFlag)
454 {
455 _indirectionBuffer._data[indirectionIDX][TRANSFORM_IDX] = transformIdx;
456 _indirectionBuffer._data[indirectionIDX][SELECTION_FLAG] = selectionFlag;
457 UpdateBufferRange( _indirectionBuffer._range, indirectionIDX );
458 }
459 }
460
462 {
464
465 cacheHit = false;
466
467 NodeMaterialData tempData{};
468 // Get the colour matrix (base colour, metallic, etc)
469 rComp->getMaterialData( tempData );
470
471 // Match materials
472 const size_t materialHash = HashMaterialData( tempData );
473
474 BufferMaterialData::LookupInfoContainer& infoContainer = _materialBuffer._data._lookupInfo;
475 {// Try and match an existing material
476 SharedLock<SharedMutex> r_lock( _materialBuffer._range._lock );
477 PROFILE_SCOPE( "processVisibleNode - try match material", Profiler::Category::Scene );
478 {
479 const size_t count = infoContainer.size();
480 for ( size_t idx = 0u; idx < count; ++idx )
481 {
482 const auto [hash, _] = infoContainer[idx];
483 if ( hash == materialHash )
484 {
485 cacheHit = true;
486 infoContainer[idx]._framesSinceLastUsed = 0u;
487 return to_U16(idx);
488 }
489 }
490 }
491 }
492
493 // If we fail, try and find an empty slot and update it
494 PROFILE_SCOPE( "processVisibleNode - process unmatched material", Profiler::Category::Scene );
495
496 LockGuard<SharedMutex> w_lock(_materialBuffer._range._lock);
497 // No match found (cache miss) so try again and add a new entry if we still fail
498 const size_t count = infoContainer.size();
499 for ( size_t idx = 0u; idx < count; ++idx )
500 {
501 const auto [hash, _] = infoContainer[idx];
502 if ( hash == materialHash )
503 {
504 cacheHit = true;
505 infoContainer[idx]._framesSinceLastUsed = 0u;
506 return to_U16(idx);
507 }
508 }
509
510 BufferCandidate bestCandidate{ g_invalidMaterialIndex, 0u };
511
512 for ( U16 idx = 0u; idx < count; ++idx )
513 {
514 const auto [hash, framesSinceLastUsed] = infoContainer[idx];
515 // Two cases here. We either have empty slots (e.g. startup, cache clear, etc) ...
516 if ( hash == INVALID_MAT_HASH )
517 {
518 // ... in which case our current idx is what we are looking for ...
519 bestCandidate._index = idx;
520 bestCandidate._framesSinceLastUsed = g_maxMaterialFrameLifetime;
521 break;
522 }
523 // ... else we need to find a slot with a stale entry (but not one that is still in flight!)
524 if ( framesSinceLastUsed >= std::max( g_maxMaterialFrameLifetime, bestCandidate._framesSinceLastUsed ) )
525 {
526 bestCandidate._index = idx;
527 bestCandidate._framesSinceLastUsed = framesSinceLastUsed;
528 // Keep going and see if we can find an even older entry
529 }
530 }
531
532 DIVIDE_ASSERT( bestCandidate._index != g_invalidMaterialIndex, "RenderPassExecutor::processVisibleNode error: too many concurrent materials! Increase Config::MAX_CONCURRENT_MATERIALS" );
533
534 infoContainer[bestCandidate._index] = { materialHash, 0u };
535 assert( bestCandidate._index < _materialBuffer._data._gpuData.size() );
536
537 _materialBuffer._data._gpuData[bestCandidate._index] = tempData;
538
539 return bestCandidate._index;
540 }
541
542
544 {
546
547 U32 minMaterial = U32_MAX, maxMaterial = 0u;
548 U32 minIndirection = U32_MAX, maxIndirection = 0u;
549
550 for ( U32 i = start; i < end; ++i )
551 {
553 if ( !NodeNeedsUpdate( _materialBuffer, indirectionIDX ) )
554 {
555 continue;
556 }
557
558 [[maybe_unused]] bool cacheHit = false;
559 const U16 idx = processVisibleNodeMaterial( queue[i], cacheHit );
560 DIVIDE_ASSERT( idx != g_invalidMaterialIndex && idx != U16_MAX );
561
562 if ( idx < minMaterial)
563 {
564 minMaterial = idx;
565 }
566 if ( idx > maxMaterial)
567 {
568 maxMaterial = idx;
569 }
570
571 if ( 0u == idx || _indirectionBuffer._data[indirectionIDX][MATERIAL_IDX] != idx )
572 {
573 _indirectionBuffer._data[indirectionIDX][MATERIAL_IDX] = idx;
574
575 if ( indirectionIDX < minIndirection )
576 {
577 minIndirection = indirectionIDX;
578 }
579 if ( indirectionIDX > maxIndirection )
580 {
581 maxIndirection = indirectionIDX;
582 }
583 }
584 }
585
586 UpdateBufferRange( _indirectionBuffer._range, minIndirection, maxIndirection );
587 UpdateBufferRange( _materialBuffer._range, minMaterial, maxMaterial );
588 }
589
590 [[nodiscard]] constexpr size_t MIN_NODE_COUNT( const size_t N, const size_t L) noexcept
591 {
592 return N == 0u ? L : N;
593 }
594
595 size_t RenderPassExecutor::buildDrawCommands( const PlayerIndex index, const RenderPassParams& params, const bool doPrePass, const bool doOITPass, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
596 {
598
599 constexpr bool doMainPass = true;
600
602
604
605 for ( RenderBin::SortedQueue& sQueue : _sortedQueues )
606 {
607 sQueue.resize( 0 );
608 sQueue.reserve( Config::MAX_VISIBLE_NODES );
609 }
610
611 const size_t queueTotalSize = _renderQueue->getSortedQueues( {}, _sortedQueues );
612
613 //Erase nodes with no draw commands
615 {
616 erase_if( queue, []( RenderingComponent* item ) noexcept
617 {
619 } );
620 }
621
622 bool updateTaskDirty = false;
623
625 Task* updateTask = CreateTask( TASK_NOP );
626 {
627 PROFILE_SCOPE( "buildDrawCommands - process nodes: Transforms", Profiler::Category::Scene );
628
629 U32& nodeCount = *passData._lastNodeCount;
630 nodeCount = 0u;
632 {
633 const U32 queueSize = to_U32( queue.size() );
634 if ( queueSize > g_nodesPerPrepareDrawPartition )
635 {
636 Start( *CreateTask( updateTask, [this, index, &queue, queueSize]( const Task& )
637 {
638 for ( U32 i = 0u; i < queueSize / 2; ++i )
639 {
640 processVisibleNodeTransform( index, queue[i] );
641 }
642 } ), pool );
643 Start( *CreateTask( updateTask, [this, index, &queue, queueSize]( const Task& )
644 {
645 for ( U32 i = queueSize / 2; i < queueSize; ++i )
646 {
647 processVisibleNodeTransform( index, queue[i] );
648 }
649 } ), pool );
650 updateTaskDirty = true;
651 }
652 else
653 {
654 for ( U32 i = 0u; i < queueSize; ++i )
655 {
656 processVisibleNodeTransform( index, queue[i] );
657 }
658 }
659 nodeCount += queueSize;
660 }
661 assert( nodeCount < Config::MAX_VISIBLE_NODES );
662 }
663 {
664 PROFILE_SCOPE( "buildDrawCommands - process nodes: Materials", Profiler::Category::Scene );
666 {
667 const U32 queueSize = to_U32( queue.size() );
668 if ( queueSize > g_nodesPerPrepareDrawPartition )
669 {
670 const U32 midPoint = queueSize / 2;
671 Start( *CreateTask( updateTask, [this, &queue, midPoint]( const Task& )
672 {
673 parseMaterialRange( queue, 0u, midPoint );
674 } ), pool );
675 Start( *CreateTask( updateTask, [this, &queue, midPoint, queueSize]( const Task& )
676 {
677 parseMaterialRange( queue, midPoint, queueSize );
678 } ), pool );
679 updateTaskDirty = true;
680 }
681 else
682 {
683 parseMaterialRange( queue, 0u, queueSize );
684 }
685 }
686 }
687 if (updateTaskDirty)
688 {
689 PROFILE_SCOPE( "buildDrawCommands - process nodes: Waiting for tasks to finish", Profiler::Category::Scene );
690 Start( *updateTask, pool );
691 Wait( *updateTask, pool );
692 }
693
694 RenderStagePass stagePass = params._stagePass;
695 const U32 startOffset = Config::MAX_VISIBLE_NODES * IndexForStage( stagePass );
696 const auto retrieveCommands = [&]()
697 {
699 {
700 for ( RenderingComponent* rComp : queue )
701 {
703 }
704 }
705 };
706
707 {
708 const RenderPassType prevType = stagePass._passType;
709 if ( doPrePass )
710 {
711 PROFILE_SCOPE( "buildDrawCommands - retrieve draw commands: PRE_PASS", Profiler::Category::Scene );
713 retrieveCommands();
714 }
715 if ( doMainPass )
716 {
717 PROFILE_SCOPE( "buildDrawCommands - retrieve draw commands: MAIN_PASS", Profiler::Category::Scene );
719 retrieveCommands();
720 }
721 if ( doOITPass )
722 {
723 PROFILE_SCOPE( "buildDrawCommands - retrieve draw commands: OIT_PASS", Profiler::Category::Scene );
725 retrieveCommands();
726 }
727 else
728 {
729 PROFILE_SCOPE( "buildDrawCommands - retrieve draw commands: TRANSPARENCY_PASS", Profiler::Category::Scene );
731 retrieveCommands();
732 }
733 stagePass._passType = prevType;
734 }
735
736 const U32 cmdCount = to_U32( _drawCommands.size() );
737 *passData._lastCommandCount = cmdCount;
738
739
740 if ( cmdCount > 0u )
741 {
742 PROFILE_SCOPE( "buildDrawCommands - update command buffer", Profiler::Category::Graphics );
743 memCmdInOut._bufferLocks.push_back( _cmdBuffer->writeData( { startOffset, cmdCount }, _drawCommands.data() ) );
744 }
745 {
746 PROFILE_SCOPE( "buildDrawCommands - update material buffer", Profiler::Category::Graphics );
747 WriteToGPUBuffer( _materialBuffer, memCmdInOut );
748 }
749 {
750 PROFILE_SCOPE( "buildDrawCommands - update transform buffer", Profiler::Category::Graphics );
751 WriteToGPUBuffer( _transformBuffer, memCmdInOut );
752 }
753 {
754 PROFILE_SCOPE( "buildDrawCommands - update indirection buffer", Profiler::Category::Graphics );
755 WriteToGPUBuffer( _indirectionBuffer, memCmdInOut );
756 }
757
758 auto cmd = GFX::EnqueueCommand<GFX::BindShaderResourcesCommand>( bufferInOut );
759 cmd->_usage = DescriptorSetUsage::PER_BATCH;
760 {
761 DescriptorSetBinding& binding = AddBinding( cmd->_set, 0u, ShaderStageVisibility::NONE ); //Command buffer only
762 Set( binding._data, _cmdBuffer.get(), { startOffset, Config::MAX_VISIBLE_NODES});
763 }
764 {
766 Set( binding._data, _cmdBuffer.get(), { startOffset, Config::MAX_VISIBLE_NODES } );
767 }
768 {
770 Set(binding._data, _transformBuffer._gpuBuffer.get(), { 0u, MIN_NODE_COUNT(_transformBuffer._range._highWaterMark, Config::MAX_VISIBLE_NODES )});
771 }
772 {
774 Set( binding._data, _indirectionBuffer._gpuBuffer.get(), { 0u, MIN_NODE_COUNT(_indirectionBuffer._range._highWaterMark, Config::MAX_VISIBLE_NODES )});
775 }
776 {
778 Set( binding._data, _materialBuffer._gpuBuffer.get(), { 0u, MIN_NODE_COUNT(_materialBuffer._range._highWaterMark, Config::MAX_CONCURRENT_MATERIALS )});
779 }
780
781 return queueTotalSize;
782 }
783
785 const RenderPassParams& params,
786 const CameraSnapshot& cameraSnapshot,
787 const bool hasInvalidNodes,
788 const bool doPrePass,
789 const bool doOITPass,
790 GFX::CommandBuffer& bufferInOut,
791 GFX::MemoryBarrierCommand& memCmdInOut )
792 {
794
795 if ( hasInvalidNodes )
796 {
797 bool nodeRemoved = true;
798 while ( nodeRemoved )
799 {
800 nodeRemoved = false;
801 for ( size_t i = 0; i < _visibleNodesCache.size(); ++i )
802 {
803 if ( !_visibleNodesCache.node( i )._materialReady )
804 {
806 nodeRemoved = true;
807 break;
808 }
809 }
810 }
811 }
812
813 RenderStagePass stagePass = params._stagePass;
814 const SceneRenderState& sceneRenderState = _parent.parent().projectManager()->activeProject()->getActiveScene()->state()->renderState();
815 {
816 Mutex memCmdLock;
817
818 _renderQueue->clear();
819
820 const auto cbk = [&]( const Task* /*parentTask*/, const U32 start, const U32 end )
821 {
822 GFX::MemoryBarrierCommand postDrawMemCmd{};
823 for ( U32 i = start; i < end; ++i )
824 {
825 const VisibleNode& node = _visibleNodesCache.node( i );
827 if ( Attorney::RenderingCompRenderPass::prepareDrawPackage( *rComp, cameraSnapshot, sceneRenderState, stagePass, postDrawMemCmd, true ) )
828 {
829 _renderQueue->addNodeToQueue( node._node, stagePass, node._distanceToCameraSq );
830 }
831 }
832
833 LockGuard<Mutex> w_lock(memCmdLock);
834 memCmdInOut._bufferLocks.insert(memCmdInOut._bufferLocks.cend(), postDrawMemCmd._bufferLocks.cbegin(), postDrawMemCmd._bufferLocks.cend());
835 };
836
837 if ( _visibleNodesCache.size() < g_nodesPerPrepareDrawPartition )
838 {
839 cbk( nullptr, 0, to_U32(_visibleNodesCache.size()) );
840 }
841 else
842 {
843 ParallelForDescriptor descriptor = {};
844 descriptor._iterCount = to_U32( _visibleNodesCache.size() );
845 descriptor._partitionSize = g_nodesPerPrepareDrawPartition;
847 descriptor._useCurrentThread = true;
849 }
850 _renderQueue->sort( stagePass );
851 }
852
854
856 queueParams._stagePass = stagePass;
857 queueParams._binType = RenderBinType::COUNT;
858 queueParams._filterByBinType = false;
859 _renderQueue->populateRenderQueues( queueParams, _renderQueuePackages );
860
861 return buildDrawCommands( index, params, doPrePass, doOITPass, bufferInOut, memCmdInOut );
862 }
863
865 const CameraSnapshot& cameraSnapshot,
866 bool transparencyPass,
867 const RenderingOrder renderOrder,
868 GFX::CommandBuffer& bufferInOut,
869 GFX::MemoryBarrierCommand& memCmdInOut )
870 {
872
873 RenderStagePass stagePass = params._stagePass;
875 const SceneRenderState& sceneRenderState = _parent.parent().projectManager()->activeProject()->getActiveScene()->state()->renderState();
876
877 _renderQueue->clear( targetBin );
878
879 Mutex memCmdLock;
880 GFX::MemoryBarrierCommand postDrawMemCmd{};
881
882 const U32 nodeCount = to_U32( _visibleNodesCache.size() );
883
884 const auto cbk = [&]( const Task* /*parentTask*/, const U32 start, const U32 end )
885 {
886 for ( U32 i = start; i < end; ++i )
887 {
888 const VisibleNode& node = _visibleNodesCache.node( i );
889 SceneGraphNode* sgn = node._node;
890 if ( sgn->getNode().renderState().drawState( stagePass ) )
891 {
892 if ( Attorney::RenderingCompRenderPass::prepareDrawPackage( *sgn->get<RenderingComponent>(), cameraSnapshot, sceneRenderState, stagePass, postDrawMemCmd, false ) )
893 {
894 _renderQueue->addNodeToQueue( sgn, stagePass, node._distanceToCameraSq, targetBin );
895 }
896 }
897 }
898
899 LockGuard<Mutex> w_lock( memCmdLock );
900 memCmdInOut._bufferLocks.insert( memCmdInOut._bufferLocks.cend(), postDrawMemCmd._bufferLocks.cbegin(), postDrawMemCmd._bufferLocks.cend() );
901 };
902
903 if ( nodeCount > g_nodesPerPrepareDrawPartition * 2 )
904 {
905 PROFILE_SCOPE( "prepareRenderQueues - parallel gather", Profiler::Category::Scene );
906 ParallelForDescriptor descriptor = {};
907 descriptor._iterCount = nodeCount;
908 descriptor._partitionSize = g_nodesPerPrepareDrawPartition;
910 descriptor._useCurrentThread = true;
912 }
913 else
914 {
915 PROFILE_SCOPE( "prepareRenderQueues - serial gather", Profiler::Category::Scene );
916 cbk( nullptr, 0u, nodeCount );
917 }
918
919 // Sort all bins
920 _renderQueue->sort( stagePass, targetBin, renderOrder );
921
923
924 // Draw everything in the depth pass but only draw stuff from the translucent bin in the OIT Pass and everything else in the colour pass
926
927 queueParams._stagePass = stagePass;
928 if ( IsDepthPass( stagePass ) )
929 {
930 queueParams._binType = RenderBinType::COUNT;
931 queueParams._filterByBinType = false;
932 }
933 else
934 {
935 queueParams._binType = RenderBinType::TRANSLUCENT;
936 queueParams._filterByBinType = !transparencyPass;
937 }
938
939 _renderQueue->populateRenderQueues( queueParams, _renderQueuePackages );
940
941
942 for ( const auto& [rComp, pkg] : _renderQueuePackages )
943 {
945 }
946
948 {
949 auto cmd = GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut );
950 Util::StringFormat( cmd->_scopeName, "Post Render pass for stage [ {} ]", TypeUtil::RenderStageToString( stagePass._stage ), to_U32( stagePass._stage ) );
951
952 _renderQueue->postRender( Attorney::ProjectManagerRenderPass::renderState( _parent.parent().projectManager().get() ),
953 params._stagePass,
954 bufferInOut );
955
956 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
957 }
958 }
959
960 void RenderPassExecutor::prePass( const RenderPassParams& params, const CameraSnapshot& cameraSnapshot, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
961 {
963
965
966 GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut )->_scopeName = " - PrePass";
967
968
969 GFX::BeginRenderPassCommand renderPassCmd{};
970 renderPassCmd._name = "DO_PRE_PASS";
971 renderPassCmd._target = params._target;
972 renderPassCmd._descriptor = params._targetDescriptorPrePass;
973 renderPassCmd._clearDescriptor = params._clearDescriptorPrePass;
974
975 GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut, renderPassCmd );
976
977 prepareRenderQueues( params, cameraSnapshot, false, RenderingOrder::COUNT, bufferInOut, memCmdInOut );
979
980 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
981 }
982
983 void RenderPassExecutor::occlusionPass( [[maybe_unused]] const PlayerIndex idx,
984 const CameraSnapshot& cameraSnapshot,
985 [[maybe_unused]] const size_t visibleNodeCount,
986 const RenderTargetID& sourceDepthBuffer,
987 const RenderTargetID& targetHiZBuffer,
988 GFX::CommandBuffer& bufferInOut ) const
989 {
991
992 GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut )->_scopeName = "HiZ Construct & Cull";
993
994 GFX::EnqueueCommand<GFX::MemoryBarrierCommand>( bufferInOut )->_bufferLocks.emplace_back( BufferLock
995 {
996 ._range = {0u, U32_MAX },
998 ._buffer = _cmdBuffer->getBufferImpl()
999 });
1000
1001 // Update HiZ Target
1002 const auto [hizTexture, hizSampler] = _context.constructHIZ( sourceDepthBuffer, targetHiZBuffer, bufferInOut );
1003 // Run occlusion culling CS
1005 hizTexture,
1006 hizSampler,
1007 cameraSnapshot,
1009 bufferInOut );
1010
1011 GFX::EnqueueCommand<GFX::MemoryBarrierCommand>( bufferInOut )->_bufferLocks.emplace_back( BufferLock
1012 {
1013 ._range = {0u, U32_MAX },
1015 ._buffer = _cmdBuffer->getBufferImpl()
1016 });
1017
1018 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
1019 }
1020
1021 void RenderPassExecutor::mainPass( const RenderPassParams& params, const CameraSnapshot& cameraSnapshot, RenderTarget& target, const bool prePassExecuted, const bool hasHiZ, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
1022 {
1024
1026
1027 GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut )->_scopeName = " - MainPass";
1028
1029 if ( params._target != INVALID_RENDER_TARGET_ID ) [[likely]]
1030 {
1031
1032 GFX::BeginRenderPassCommand renderPassCmd{};
1033 renderPassCmd._name = "DO_MAIN_PASS";
1034 renderPassCmd._target = params._target;
1035 renderPassCmd._descriptor = params._targetDescriptorMainPass;
1036 renderPassCmd._clearDescriptor = params._clearDescriptorMainPass;
1037
1038 GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut, renderPassCmd );
1039
1040 auto cmd = GFX::EnqueueCommand<GFX::BindShaderResourcesCommand>( bufferInOut );
1041 cmd->_usage = DescriptorSetUsage::PER_PASS;
1042 if ( params._stagePass._stage == RenderStage::DISPLAY )
1043 {
1044 const RenderTarget* MSSource = _context.renderTargetPool().getRenderTarget( params._target );
1046
1048 Set( binding._data, normalsAtt->texture(), normalsAtt->_descriptor._sampler );
1049 }
1050 if ( hasHiZ )
1051 {
1053 const RenderTarget* hizTarget = _context.renderTargetPool().getRenderTarget( params._targetHIZ );
1054 RTAttachment* hizAtt = hizTarget->getAttachment( RTAttachmentType::COLOUR );
1055 Set( binding._data, hizAtt->texture(), hizAtt->_descriptor._sampler );
1056 }
1057 else if ( prePassExecuted )
1058 {
1061 Set( binding._data, depthAtt->texture(), depthAtt->_descriptor._sampler );
1062 }
1063
1064 prepareRenderQueues( params, cameraSnapshot, false, RenderingOrder::COUNT, bufferInOut, memCmdInOut );
1066 }
1067
1068 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
1069 }
1070
1071 void RenderPassExecutor::woitPass( const RenderPassParams& params, const CameraSnapshot& cameraSnapshot, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
1072 {
1074
1075 assert( params._stagePass._passType == RenderPassType::OIT_PASS );
1076
1077 GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut )->_scopeName = " - W-OIT Pass";
1078
1079 // Step1: Draw translucent items into the accumulation and revealage buffers
1080 GFX::BeginRenderPassCommand* beginRenderPassOitCmd = GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut );
1081 beginRenderPassOitCmd->_name = "OIT PASS 1";
1082 beginRenderPassOitCmd->_target = params._targetOIT;
1083 beginRenderPassOitCmd->_clearDescriptor[to_base( GFXDevice::ScreenTargets::ACCUMULATION )] = { VECTOR4_ZERO, true };
1084 beginRenderPassOitCmd->_clearDescriptor[to_base( GFXDevice::ScreenTargets::REVEALAGE )] = { VECTOR4_UNIT, true };
1085 beginRenderPassOitCmd->_clearDescriptor[to_base( GFXDevice::ScreenTargets::NORMALS )] = { VECTOR4_ZERO, true };
1086 beginRenderPassOitCmd->_clearDescriptor[to_base( GFXDevice::ScreenTargets::MODULATE )] = { VECTOR4_ZERO, false };
1087 beginRenderPassOitCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::ACCUMULATION )] = true;
1088 beginRenderPassOitCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::REVEALAGE )] = true;
1089 beginRenderPassOitCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::NORMALS )] = true;
1090 beginRenderPassOitCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::MODULATE )] = true;
1093
1094 prepareRenderQueues( params, cameraSnapshot, true, RenderingOrder::COUNT, bufferInOut, memCmdInOut );
1095 GFX::EnqueueCommand<GFX::EndRenderPassCommand>( bufferInOut )->_transitionMask[to_base( GFXDevice::ScreenTargets::MODULATE )] = false;
1096
1101
1102 // Step2: Composition pass
1103 // Don't clear depth & colours and do not write to the depth buffer
1104 GFX::BeginRenderPassCommand* beginRenderPassCompCmd = GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut);
1105 beginRenderPassCompCmd->_name = "OIT PASS 2";
1106 beginRenderPassCompCmd->_target = params._target;
1107 beginRenderPassCompCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::ALBEDO )] = true;
1108 beginRenderPassCompCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::VELOCITY )] = false;
1109 beginRenderPassCompCmd->_descriptor._drawMask[to_base( GFXDevice::ScreenTargets::NORMALS )] = true;
1112
1113 GFX::EnqueueCommand<GFX::SetCameraCommand>( bufferInOut )->_cameraSnapshot = Camera::GetUtilityCamera( Camera::UtilityCamera::_2D )->snapshot();
1114 GFX::EnqueueCommand<GFX::BindPipelineCommand>( bufferInOut )->_pipeline = params._useMSAA ? s_OITCompositionMSPipeline : s_OITCompositionPipeline;
1115 {
1116 auto cmd = GFX::EnqueueCommand<GFX::BindShaderResourcesCommand>( bufferInOut );
1117 cmd->_usage = DescriptorSetUsage::PER_DRAW;
1118 {
1120 Set( binding._data, accumAtt->texture(), accumAtt->_descriptor._sampler );
1121 }
1122 {
1124 Set( binding._data, revAtt->texture(), revAtt->_descriptor._sampler );
1125 }
1126 {
1128 Set( binding._data, normalsAtt->texture(), normalsAtt->_descriptor._sampler );
1129 }
1130 }
1131 GFX::EnqueueCommand<GFX::DrawCommand>( bufferInOut )->_drawCommands.emplace_back();
1132 GFX::EnqueueCommand<GFX::EndRenderPassCommand>( bufferInOut );
1133 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
1134 }
1135
1136 void RenderPassExecutor::transparencyPass( const RenderPassParams& params, const CameraSnapshot& cameraSnapshot, GFX::CommandBuffer& bufferInOut, GFX::MemoryBarrierCommand& memCmdInOut )
1137 {
1139
1141
1142 //Grab all transparent geometry
1143 GFX::BeginRenderPassCommand beginRenderPassTransparentCmd{};
1144 beginRenderPassTransparentCmd._name = "DO_TRANSPARENCY_PASS";
1145 beginRenderPassTransparentCmd._target = params._target;
1146 beginRenderPassTransparentCmd._descriptor = params._targetDescriptorMainPass;
1147
1148 GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut, beginRenderPassTransparentCmd );
1149 prepareRenderQueues( params, cameraSnapshot, true, RenderingOrder::BACK_TO_FRONT, bufferInOut, memCmdInOut );
1150 GFX::EnqueueCommand<GFX::EndRenderPassCommand>( bufferInOut );
1151 }
1152
1154 {
1156
1157 // If we rendered to the multisampled screen target, we can now copy the colour to our regular buffer as we are done with it at this point
1158 if ( params._target == RenderTargetNames::SCREEN && params._useMSAA )
1159 {
1160 GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut )->_scopeName = " - Resolve Screen Targets";
1161
1162 GFX::BeginRenderPassCommand* beginRenderPassCommand = GFX::EnqueueCommand<GFX::BeginRenderPassCommand>( bufferInOut );
1163 beginRenderPassCommand->_target = RenderTargetNames::NORMALS_RESOLVED;
1164 beginRenderPassCommand->_clearDescriptor[0u] = { VECTOR4_ZERO, true };
1165 beginRenderPassCommand->_descriptor._drawMask[0u] = true;
1166 beginRenderPassCommand->_name = "RESOLVE_MAIN_GBUFFER";
1167
1168 GFX::EnqueueCommand<GFX::BindPipelineCommand>( bufferInOut )->_pipeline = s_ResolveGBufferPipeline;
1169
1170 const RenderTarget* MSSource = _context.renderTargetPool().getRenderTarget( params._target );
1172
1173 auto cmd = GFX::EnqueueCommand<GFX::BindShaderResourcesCommand>( bufferInOut );
1174 cmd->_usage = DescriptorSetUsage::PER_DRAW;
1176 Set( binding._data, normalsAtt->texture(), normalsAtt->_descriptor._sampler );
1177
1178 GFX::EnqueueCommand<GFX::DrawCommand>( bufferInOut )->_drawCommands.emplace_back();
1179 GFX::EnqueueCommand<GFX::EndRenderPassCommand>( bufferInOut );
1180
1181 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
1182 }
1183 }
1184
1186 {
1188
1189 bool ret = false;
1190 const I32 nodeCount = to_I32( _visibleNodesCache.size() );
1191 for ( I32 i = nodeCount - 1; i >= 0; i-- )
1192 {
1193 VisibleNode& node = _visibleNodesCache.node( i );
1194 if ( node._node == nullptr || (node._materialReady && !Attorney::SceneGraphNodeRenderPassManager::canDraw( node._node, stagePass )) )
1195 {
1196 node._materialReady = false;
1197 ret = true;
1198 }
1199 }
1200 return ret;
1201 }
1202
1204 {
1206
1207 assert( params._stagePass._stage == _stage );
1208
1209 if ( !camera->updateLookAt() )
1210 {
1211 NOP();
1212 }
1213 const CameraSnapshot& camSnapshot = camera->snapshot();
1214
1215 GFX::BeginDebugScopeCommand* beginDebugScopeCmd = GFX::EnqueueCommand<GFX::BeginDebugScopeCommand>( bufferInOut );
1216 if ( params._passName.empty() )
1217 {
1218 Util::StringFormat<Str<64>>( beginDebugScopeCmd->_scopeName, "Custom pass ( {} )", TypeUtil::RenderStageToString( _stage ) );
1219 }
1220 else
1221 {
1222 Util::StringFormat<Str<64>>( beginDebugScopeCmd->_scopeName, "Custom pass ( {} - {} )", TypeUtil::RenderStageToString( _stage ), params._passName );
1223 }
1224
1225
1226
1228
1230 if ( params._singleNodeRenderGUID == 0 ) [[unlikely]]
1231 {
1232 // Render nothing!
1233 NOP();
1234 }
1235 else if ( params._singleNodeRenderGUID == -1 ) [[likely]]
1236 {
1237 // Cull the scene and grab the visible nodes
1238 I64 ignoreGUID = params._sourceNode == nullptr ? -1 : params._sourceNode->getGUID();
1239
1240 NodeCullParams cullParams = {};
1241 Attorney::ProjectManagerRenderPass::initDefaultCullValues( _parent.parent().projectManager().get(), _stage, cullParams );
1242
1243 cullParams._clippingPlanes = params._clippingPlanes;
1244 cullParams._stage = _stage;
1245 cullParams._minExtents = params._minExtents;
1246 cullParams._ignoredGUIDS = { &ignoreGUID, 1 };
1247 cullParams._cameraEyePos = camSnapshot._eye;
1248 cullParams._frustum = &camera->getFrustum();
1249 cullParams._cullMaxDistance = std::min( cullParams._cullMaxDistance, camSnapshot._zPlanes.y );
1250 cullParams._maxLoD = params._maxLoD;
1251
1254 {
1256 }
1258 {
1260 }
1261 Attorney::ProjectManagerRenderPass::cullScene( _parent.parent().projectManager().get(), cullParams, cullFlags, _visibleNodesCache );
1262 }
1263 else
1264 {
1266 }
1267
1268 const bool drawTranslucents = (params._drawMask & to_U8( 1 << to_base( RenderPassParams::Flags::DRAW_TRANSLUCENT_NODES))) && _stage != RenderStage::SHADOW;
1269
1270 constexpr bool doMainPass = true;
1271 // PrePass requires a depth buffer
1272 const bool doPrePass = _stage != RenderStage::SHADOW &&
1275 const bool doOITPass = drawTranslucents && params._targetOIT != INVALID_RENDER_TARGET_ID;
1276 const bool doOcclusionPass = doPrePass && params._targetHIZ != INVALID_RENDER_TARGET_ID;
1277
1278 bool hasInvalidNodes = false;
1279 {
1280 PROFILE_SCOPE( "doCustomPass: Validate draw", Profiler::Category::Scene );
1281 if ( doPrePass )
1282 {
1284 hasInvalidNodes = validateNodesForStagePass( params._stagePass ) || hasInvalidNodes;
1285 }
1286 if ( doMainPass )
1287 {
1289 hasInvalidNodes = validateNodesForStagePass( params._stagePass ) || hasInvalidNodes;
1290 }
1291 if ( doOITPass )
1292 {
1294 hasInvalidNodes = validateNodesForStagePass( params._stagePass ) || hasInvalidNodes;
1295 }
1296 else if ( drawTranslucents )
1297 {
1299 hasInvalidNodes = validateNodesForStagePass( params._stagePass ) || hasInvalidNodes;
1300 }
1301 }
1302
1303 if ( params._feedBackContainer != nullptr )
1304 {
1305 params._feedBackContainer->resize( _visibleNodesCache.size() );
1306 std::memcpy( params._feedBackContainer->data(), _visibleNodesCache.data(), _visibleNodesCache.size() * sizeof( VisibleNode ) );
1307 if ( hasInvalidNodes )
1308 {
1309 // This may hurt ... a lot ... -Ionut
1310 dvd_erase_if( *params._feedBackContainer, []( VisibleNode& node )
1311 {
1312 return node._node == nullptr || !node._materialReady;
1313 } );
1314 };
1315 }
1316
1317 // Tell the Rendering API to draw from our desired PoV
1318 GFX::SetCameraCommand* camCmd = GFX::EnqueueCommand<GFX::SetCameraCommand>( bufferInOut );
1319 camCmd->_cameraSnapshot = camSnapshot;
1320
1321 if ( params._refreshLightData )
1322 {
1323 Attorney::ProjectManagerRenderPass::prepareLightData( _parent.parent().projectManager().get(), _stage, camSnapshot, memCmdInOut );
1324 }
1325
1326 GFX::EnqueueCommand<GFX::SetClipPlanesCommand>( bufferInOut )->_clippingPlanes = params._clippingPlanes;
1327
1328 // We prepare all nodes for the MAIN_PASS rendering. PRE_PASS and OIT_PASS are support passes only. Their order and sorting are less important.
1330 const size_t visibleNodeCount = prepareNodeData( playerIdx, params, camSnapshot, hasInvalidNodes, doPrePass, doOITPass, bufferInOut, memCmdInOut );
1331
1332# pragma region PRE_PASS
1333 // We need the pass to be PRE_PASS even if we skip the prePass draw stage as it is the default state subsequent operations expect
1335 if ( doPrePass )
1336 {
1337 prePass( params, camSnapshot, bufferInOut, memCmdInOut );
1339 {
1340 resolveMainScreenTarget( params, bufferInOut );
1341 }
1342 }
1343# pragma endregion
1344# pragma region HI_Z
1345 if ( doOcclusionPass )
1346 {
1347 //ToDo: Find a way to skip occlusion culling for low number of nodes in view but also keep light culling up and running -Ionut
1348 assert( params._stagePass._passType == RenderPassType::PRE_PASS );
1349
1350 // This also renders into our HiZ texture that we may want to use later in PostFX
1351 occlusionPass( playerIdx, camSnapshot, visibleNodeCount, RenderTargetNames::SCREEN, params._targetHIZ, bufferInOut );
1352 }
1353# pragma endregion
1354
1355# pragma region LIGHT_PASS
1356 _context.getRenderer().prepareLighting( _stage, { 0, 0, target->getWidth(), target->getHeight() }, camSnapshot, bufferInOut );
1357# pragma endregion
1358
1359# pragma region MAIN_PASS
1360 // Same as for PRE_PASS. Subsequent operations expect a certain state
1363 {
1364 _context.getRenderer().postFX().prePass( playerIdx, camSnapshot, bufferInOut );
1365 }
1366 if ( doMainPass ) [[likely]]
1367 {
1368 // If we draw translucents, no point in resolving now. We can resolve after translucent pass
1369 mainPass( params, camSnapshot, *target, doPrePass, doOcclusionPass, bufferInOut, memCmdInOut );
1370 }
1371 else [[unlikely]]
1372 {
1374 }
1375# pragma endregion
1376
1377# pragma region TRANSPARENCY_PASS
1378 if ( drawTranslucents )
1379 {
1380 // If doIOTPass is false, use forward pass shaders (i.e. MAIN_PASS again for transparents)
1381 if ( doOITPass )
1382 {
1384 woitPass( params, camSnapshot, bufferInOut, memCmdInOut );
1385 }
1386 else
1387 {
1389 transparencyPass( params, camSnapshot, bufferInOut, memCmdInOut );
1390 }
1391 }
1392# pragma endregion
1393
1394 GFX::EnqueueCommand<GFX::EndDebugScopeCommand>( bufferInOut );
1395 }
1396
1398 {
1400
1401 ExecutorBufferPostRender( _indirectionBuffer );
1402 ExecutorBufferPostRender( _materialBuffer );
1403 ExecutorBufferPostRender( _transformBuffer );
1404 {
1405 PROFILE_SCOPE( "Increment Lifetime", Profiler::Category::Scene );
1406 for ( BufferMaterialData::LookupInfo& it : _materialBuffer._data._lookupInfo )
1407 {
1408 it._framesSinceLastUsed += ( it._hash != INVALID_MAT_HASH ) ? 1u : 0u;
1409 }
1410 }
1411 {
1412 PROFILE_SCOPE( "Clear Freelists", Profiler::Category::Scene );
1413 LockGuard<Mutex> w_lock( _transformBuffer._data._freeListlock );
1414 _transformBuffer._data._freeList.fill( true );
1415 }
1416
1417 _cmdBuffer->incQueue();
1418 }
1419
1421 {
1422 return to_U32( _renderQueuePackages.size() );
1423 }
1424
1426 {
1428
1431 U32 entry = U32_MAX;
1432 for ( U32 i = 0u; i < MAX_INDIRECTION_ENTRIES; ++i )
1433 {
1434 if ( s_indirectionFreeList[i] )
1435 {
1436 s_indirectionFreeList[i] = false;
1437 entry = i;
1438 break;
1439 }
1440 }
1441 DIVIDE_ASSERT( entry != U32_MAX, "Insufficient space left in indirection buffer. Consider increasing available storage!" );
1443 }
1444
1446 {
1448
1450 if ( entry == U32_MAX )
1451 {
1453 return;
1454 }
1456 assert( !s_indirectionFreeList[entry] );
1458 }
1459
1460} //namespace Divide
#define DIVIDE_ASSERT(...)
#define DIVIDE_UNEXPECTED_CALL()
#define NOP()
#define FORCE_INLINE
#define PROFILE_SCOPE_AUTO(CATEGORY)
Definition: Profiler.h:87
#define PROFILE_SCOPE(NAME, CATEGORY)
Definition: Profiler.h:86
AnimEvaluator::FrameIndex frameIndex() const noexcept
void playAnimations(const bool state) noexcept
static SceneRenderState & renderState(Divide::ProjectManager *mgr) noexcept
static void prepareLightData(Divide::ProjectManager *mgr, const RenderStage stage, const CameraSnapshot &cameraSnapshot, GFX::MemoryBarrierCommand &memCmdInOut)
static void cullScene(Divide::ProjectManager *mgr, const NodeCullParams &cullParams, const U16 cullFlags, VisibleNodeList<> &nodesOut)
static void initDefaultCullValues(Divide::ProjectManager *mgr, const RenderStage stage, NodeCullParams &cullParamsInOut) noexcept
static void findNode(Divide::ProjectManager *mgr, const vec3< F32 > &cameraEye, const I64 nodeGUID, VisibleNodeList<> &nodesOut)
static void getCommandBuffer(RenderingComponent *renderable, RenderPackage *const pkg, GFX::CommandBuffer &bufferInOut)
static void setIndirectionBufferEntry(RenderingComponent *renderable, const U32 indirectionBufferEntry) noexcept
static U32 getIndirectionBufferEntry(RenderingComponent *const renderable) noexcept
static void retrieveDrawCommands(RenderingComponent &renderable, const RenderStagePass stagePass, const U32 cmdOffset, DrawCommandContainer &cmdsInOut)
static bool hasDrawCommands(RenderingComponent &renderable) noexcept
static bool prepareDrawPackage(RenderingComponent &renderable, const CameraSnapshot &cameraSnapshot, const SceneRenderState &sceneRenderState, RenderStagePass renderStagePass, GFX::MemoryBarrierCommand &postDrawMemCmd, const bool refreshData)
static bool canDraw(const SceneGraphNode *node, const RenderStagePass &stagePass)
vec4< F32 > asVec4() const noexcept
const BoundingSphere & getBoundingSphere() const noexcept
static Camera * GetUtilityCamera(const UtilityCamera type)
Definition: Camera.cpp:120
const Frustum & getFrustum() const noexcept
Returns the most recent/up-to-date frustum.
Definition: Camera.inl:202
const CameraSnapshot & snapshot() const noexcept
Returns the internal camera snapshot data (eye, orientation, etc)
Definition: Camera.inl:43
bool updateLookAt()
Return true if the cached camera state wasn't up-to-date.
Definition: Camera.cpp:384
Rough around the edges Adapter pattern abstracting the actual rendering API and access to the GPU.
Definition: GFXDevice.h:215
void occlusionCull(const RenderPass::PassData &passData, Handle< Texture > hizBuffer, SamplerDescriptor sampler, const CameraSnapshot &cameraSnapshot, bool countCulledNodes, GFX::CommandBuffer &bufferInOut)
Definition: GFXDevice.cpp:2351
ShaderBuffer_uptr newSB(const ShaderBufferDescriptor &descriptor)
Definition: GFXDevice.inl:214
GFXRTPool & renderTargetPool() noexcept
Definition: GFXDevice.inl:133
Renderer & getRenderer() const
Definition: GFXDevice.inl:117
Pipeline * newPipeline(const PipelineDescriptor &descriptor)
Create and return a new graphics pipeline. This is only used for caching and doesn't use the object a...
Definition: GFXDevice.cpp:3074
const GFXShaderData::PrevFrameData & previousFrameData(PlayerIndex player) const noexcept
Definition: GFXDevice.cpp:2083
std::pair< Handle< Texture >, SamplerDescriptor > constructHIZ(RenderTargetID depthBuffer, RenderTargetID HiZTarget, GFX::CommandBuffer &cmdBufferInOut)
Definition: GFXDevice.cpp:2272
RenderTarget * getRenderTarget(const RenderTargetID target) const
Definition: GFXRTPool.cpp:50
FORCE_INLINE I64 getGUID() const noexcept
Definition: GUIDWrapper.h:51
Kernel & parent() noexcept
FORCE_INLINE PlatformContext & platformContext() noexcept
Definition: Kernel.h:129
static void OnStartup()
Definition: Material.cpp:121
static void OnShutdown()
Definition: Material.cpp:126
PlatformContext & context() noexcept
TaskPool & taskPool(const TaskPoolType type) noexcept
void prePass(PlayerIndex idx, const CameraSnapshot &cameraSnapshot, GFX::CommandBuffer &bufferInOut)
Definition: PostFX.cpp:143
ProjectManager & parent() noexcept
Handle< Texture > texture() const
RTAttachmentDescriptor _descriptor
Definition: RTAttachment.h:128
eastl::fixed_vector< RenderingComponent *, Config::MAX_VISIBLE_NODES, false > SortedQueue
Definition: RenderBin.h:115
static constexpr U8 MATERIAL_IDX
ExecutorBuffer< BufferIndirectionData > _indirectionBuffer
static constexpr U8 SELECTION_FLAG
static void OnRenderingComponentCreation(RenderingComponent *rComp)
bool validateNodesForStagePass(RenderStagePass stagePass)
static void OnRenderingComponentDestruction(RenderingComponent *rComp)
void woitPass(const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
RenderQueuePackages _renderQueuePackages
ExecutorBuffer< BufferMaterialData > _materialBuffer
U16 processVisibleNodeMaterial(RenderingComponent *rComp, bool &cacheHit)
void transparencyPass(const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
void postInit(Handle< ShaderProgram > OITCompositionShader, Handle< ShaderProgram > OITCompositionShaderMS, Handle< ShaderProgram > ResolveGBufferShaderMS)
static Pipeline * s_OITCompositionPipeline
ExecutorBuffer< BufferTransformData > _transformBuffer
static constexpr U16 g_invalidMaterialIndex
void processVisibleNodeTransform(PlayerIndex index, RenderingComponent *rComp)
static constexpr U32 MAX_INDIRECTION_ENTRIES
void mainPass(const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, RenderTarget &target, bool prePassExecuted, bool hasHiZ, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
static constexpr size_t INVALID_MAT_HASH
void occlusionPass(PlayerIndex idx, const CameraSnapshot &cameraSnapshot, size_t visibleNodeCount, const RenderTargetID &sourceDepthBuffer, const RenderTargetID &targetHiZBuffer, GFX::CommandBuffer &bufferInOut) const
static Pipeline * s_OITCompositionMSPipeline
size_t prepareNodeData(PlayerIndex index, const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, bool hasInvalidNodes, const bool doPrePass, const bool doOITPass, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
void resolveMainScreenTarget(const RenderPassParams &params, GFX::CommandBuffer &bufferInOut) const
void doCustomPass(PlayerIndex idx, Camera *camera, RenderPassParams params, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
static void OnShutdown(const GFXDevice &gfx)
size_t buildDrawCommands(PlayerIndex index, const RenderPassParams &params, bool doPrePass, bool doOITPass, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
DrawCommandContainer _drawCommands
static constexpr U8 TRANSFORM_IDX
RenderPassExecutor(RenderPassManager &parent, GFXDevice &context, RenderStage stage)
RenderBin::SortedQueues _sortedQueues
void prePass(const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
void parseMaterialRange(RenderBin::SortedQueue &queue, U32 start, U32 end)
static Pipeline * s_ResolveGBufferPipeline
static std::array< bool, MAX_INDIRECTION_ENTRIES > s_indirectionFreeList
void prepareRenderQueues(const RenderPassParams &params, const CameraSnapshot &cameraSnapshot, bool transparencyPass, RenderingOrder renderOrder, GFX::CommandBuffer &bufferInOut, GFX::MemoryBarrierCommand &memCmdInOut)
static void OnStartup(const GFXDevice &gfx)
PassData getPassData() const noexcept
Definition: RenderPass.cpp:93
const RenderPass & getPassForStage(RenderStage renderStage) const noexcept
bool usesAttachment(RTAttachmentType type, RTColourAttachmentSlot slot=RTColourAttachmentSlot::SLOT_0) const
RTAttachment * getAttachment(RTAttachmentType type, RTColourAttachmentSlot slot=RTColourAttachmentSlot::SLOT_0) const
U16 getWidth() const noexcept
U16 getHeight() const noexcept
PostFX & postFX()
Definition: Renderer.h:61
void prepareLighting(RenderStage stage, const Rect< I32 > &viewport, const CameraSnapshot &cameraSnapshot, GFX::CommandBuffer &bufferInOut)
Definition: Renderer.cpp:152
U8 getLoDLevel(RenderStage renderStage) const noexcept
void getMaterialData(NodeMaterialData &dataOut) const
bool HasComponents(ComponentType componentType) const
FORCE_INLINE T * get() const
Returns a pointer to a specific component. Returns null if the SGN does not have the component reques...
T & getNode() noexcept
static size_t AlignmentRequirement(BufferUsageType usage) noexcept
Definition: ShaderBuffer.cpp:8
void getWorldRotationMatrixInterpolated(mat4< F32 > &matOut) const
void getWorldMatrixInterpolated(mat4< F32 > &matrixOut) const
T & element(U8 row, U8 column) noexcept
void setRow(I32 index, U value) noexcept
static mat4< T > Multiply(const mat4< T > &matrixA, const mat4< T > &matrixB) noexcept
ret = A * B
constexpr U16 MAX_VISIBLE_NODES
Estimated maximum number of visible objects per render pass (this includes debug primitives)
Definition: config.h:120
constexpr U8 MAX_FRAMES_IN_FLIGHT
Maximum number of active frames until we start waiting on a fence/sync.
Definition: config.h:100
FORCE_INLINE T * EnqueueCommand(CommandBuffer &buffer)
constexpr Optick::Category::Type Streaming
Definition: Profiler.h:65
constexpr Optick::Category::Type Scene
Definition: Profiler.h:66
constexpr Optick::Category::Type Graphics
Definition: Profiler.h:60
const char * RenderStageToString(const RenderStage stage) noexcept
Definition: GFXDevice.cpp:54
Str StringFormat(const char *fmt, Args &&...args)
U32 PACK_UNORM4x8(const vec4< F32_NORM > &value)
Definition: MathHelper.cpp:369
void UpdateBufferRange(RenderPassExecutor::ExecutorBufferRange &range, const U32 idx)
void WriteToGPUBufferInternal(ExecutorBuffer< DataContainer > &executorBuffer, BufferUpdateRange target, GFX::MemoryBarrierCommand &memCmdInOut)
bool Contains(const BufferUpdateRange &lhs, const BufferUpdateRange &rhs) noexcept
BufferUpdateRange GetPrevRangeDiff(const BufferUpdateRange &crtRange, const BufferUpdateRange &prevRange) noexcept
bool NodeNeedsUpdate(ExecutorBuffer< DataContainer > &executorBuffer, const U32 indirectionIDX)
void WriteToGPUBuffer(ExecutorBuffer< DataContainer > &executorBuffer, GFX::MemoryBarrierCommand &memCmdInOut)
FORCE_INLINE bool MergeBufferUpdateRanges(BufferUpdateRange &target, const BufferUpdateRange &source) noexcept
FORCE_INLINE void UpdateBufferRangeLocked(RenderPassExecutor::ExecutorBufferRange &range, const U32 idx) noexcept
void ExecutorBufferPostRender(ExecutorBuffer< DataContainer > &executorBuffer)
Handle console commands that start with a forward slash.
Definition: AIProcessor.cpp:7
std::lock_guard< mutex > LockGuard
Definition: SharedMutex.h:55
static constexpr bool IsDepthPass(const RenderStagePass stagePass) noexcept
constexpr U32 to_U32(const T value)
std::mutex Mutex
Definition: SharedMutex.h:40
constexpr U16 to_U16(const T value)
void Wait(const Task &task, TaskPool &pool)
Definition: Task.cpp:20
int32_t I32
RenderBinType
Definition: RenderBin.h:73
@ TRANSLUCENT
Translucent items use a [0.0...1.0] alpha value supplied via an opacity map or via the albedo's alpha...
uint8_t U8
U16 IndexForStage(RenderStagePass renderStagePass)
Task * CreateTask(Predicate &&threadedFunction, bool allowedInIdle=true)
Definition: TaskPool.inl:45
constexpr F32 to_F32(const T value)
constexpr auto TASK_NOP
Definition: Task.h:57
std::shared_lock< mutex > SharedLock
Definition: SharedMutex.h:49
constexpr U16 U16_MAX
Project & parent
Definition: DefaultScene.h:41
uint16_t U16
void Set(DescriptorSetBindingData &dataInOut, ShaderBuffer *buffer, const BufferRange range) noexcept
DescriptorSetBinding & AddBinding(DescriptorSet &setInOut, U8 slot, U16 stageVisibilityMask)
static constexpr U8 TotalPassCountForStage(const RenderStage renderStage)
static const vec4< F32 > VECTOR4_UNIT
Definition: MathVectors.h:1438
constexpr RenderTargetID INVALID_RENDER_TARGET_ID
size_t HashMaterialData(const NodeMaterialData &dataIn)
void efficient_clear(eastl::fixed_vector< T, nodeCount, bEnableOverflow, OverflowAllocator > &fixed_vector)
Definition: Vector.h:52
constexpr U8 to_U8(const T value)
constexpr U32 U32_MAX
void Start(Task &task, TaskPool &pool, TaskPriority priority=TaskPriority::DONT_CARE, const DELEGATE< void > &onCompletionFunction={})
Definition: Task.cpp:9
bool contains(eastl::vector< T, A > &vec, const T &val)
Definition: Vector.h:103
constexpr size_t MIN_NODE_COUNT(const size_t N, const size_t L) noexcept
void Parallel_For(TaskPool &pool, const ParallelForDescriptor &descriptor, const DELEGATE< void, const Task *, U32, U32 > &cbk)
Definition: TaskPool.cpp:428
RenderingOrder
Definition: RenderBin.h:63
constexpr I32 to_I32(const T value)
U32 RenderTargetID
bool dvd_erase_if(eastl::vector< T, A > &vec, Predicate &&pred)
Definition: Vector.h:109
int64_t I64
uint32_t U32
Project const SceneEntry & entry
Definition: DefaultScene.h:41
static const vec4< F32 > VECTOR4_ZERO
Definition: MathVectors.h:1435
void * bufferPtr
constexpr auto to_base(const Type value) -> Type
BufferUpdateUsage _updateUsage
Definition: BufferParams.h:40
BufferUsageType _usageType
Definition: BufferParams.h:41
BufferUpdateFrequency _updateFrequency
Definition: BufferParams.h:39
BufferRange _range
Definition: BufferLocks.h:57
size_t _elementSize
Buffer primitive size in bytes.
Definition: BufferParams.h:50
BufferFlags _flags
Definition: BufferParams.h:48
DescriptorSetBindingData _data
RTClearDescriptor _clearDescriptor
Definition: Commands.inl:97
CameraSnapshot _cameraSnapshot
Definition: Commands.inl:155
static constexpr RTColourAttachmentSlot ALBEDO
Definition: GFXDevice.h:228
static constexpr RTColourAttachmentSlot NORMALS
Definition: GFXDevice.h:230
static constexpr RTColourAttachmentSlot MODULATE
Definition: GFXDevice.h:231
static constexpr RTColourAttachmentSlot VELOCITY
Definition: GFXDevice.h:229
static constexpr RTColourAttachmentSlot ACCUMULATION
Definition: GFXDevice.h:232
static constexpr RTColourAttachmentSlot REVEALAGE
Definition: GFXDevice.h:233
FrustumClipPlanes _clippingPlanes
const Frustum * _frustum
U32 _partitionSize
How many elements should we process per async task.
Definition: TaskPool.h:45
bool _useCurrentThread
If true, we'll process a for partition on the calling thread.
Definition: TaskPool.h:51
TaskPriority _priority
Each async task will start with the same priority specified here.
Definition: TaskPool.h:47
U32 _iterCount
For loop iteration count.
Definition: TaskPool.h:43
PrimitiveTopology _primitiveTopology
Definition: Pipeline.h:48
Handle< ShaderProgram > _shaderProgramHandle
Definition: Pipeline.h:47
RTBlendStates _blendStates
Definition: Pipeline.h:45
RenderStateBlock _stateBlock
Definition: Pipeline.h:46
SamplerDescriptor _sampler
Definition: RTAttachment.h:64
std::array< BlendingSettings, to_base(RTColourAttachmentSlot::COUNT)> _settings
std::array< LookupInfo, Config::MAX_CONCURRENT_MATERIALS > LookupInfoContainer
eastl::fixed_vector< U32, MAX_INDIRECTION_ENTRIES, false > _nodeProcessedThisFrame
RTClearDescriptor _clearDescriptorPrePass
const SceneGraphNode * _sourceNode
RTDrawDescriptor _targetDescriptorPrePass
RTDrawDescriptor _targetDescriptorMainPass
FeedBackContainer * _feedBackContainer
FrustumClipPlanes _clippingPlanes
RTClearDescriptor _clearDescriptorMainPass
RenderPassType _passType
static RenderTargetID NORMALS_RESOLVED
Definition: GFXDevice.h:199
static RenderTargetID SCREEN
Definition: GFXDevice.h:197
SceneGraphNode * _node
void remove(const size_t idx)
bufferPtr data() const noexcept
size_t size() const noexcept
const T & node(const size_t idx) const noexcept