1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsRenderCompositor.h"
4#include "Renderer/BsRendererExtension.h"
5#include "Renderer/BsSkybox.h"
6#include "Renderer/BsCamera.h"
7#include "Renderer/BsRendererUtility.h"
8#include "RenderAPI/BsGpuBuffer.h"
9#include "Utility/BsBitwise.h"
10#include "Mesh/BsMesh.h"
11#include "Material/BsGpuParamsSet.h"
12#include "Renderer/BsGpuResourcePool.h"
13#include "Utility/BsRendererTextures.h"
14#include "Shading/BsStandardDeferred.h"
15#include "Shading/BsTiledDeferred.h"
16#include "Shading/BsLightProbes.h"
17#include "Shading/BsPostProcessing.h"
18#include "Shading/BsShadowRendering.h"
19#include "Shading/BsLightGrid.h"
20#include "BsRendererView.h"
21#include "BsRenderBeastOptions.h"
22#include "BsRendererScene.h"
23#include "BsRenderBeast.h"
24#include "Particles/BsParticleManager.h"
25#include "Particles/BsParticleSystem.h"
26#include "Threading/BsTaskScheduler.h"
27#include "Profiling/BsProfilerGPU.h"
28#include "Shading/BsGpuParticleSimulation.h"
29#include "Profiling/BsProfilerCPU.h"
30
31namespace bs { namespace ct
32{
33 UnorderedMap<StringID, RenderCompositor::NodeType*> RenderCompositor::mNodeTypes;
34
35 /** Renders all elements in a render queue. */
36 void renderQueueElements(const Vector<RenderQueueElement>& elements)
37 {
38 for(auto& entry : elements)
39 {
40 if (entry.applyPass)
41 gRendererUtility().setPass(entry.renderElem->material, entry.passIdx, entry.techniqueIdx);
42
43 gRendererUtility().setPassParams(entry.renderElem->params, entry.passIdx);
44
45 entry.renderElem->draw();
46 }
47 }
48
49 RenderCompositor::~RenderCompositor()
50 {
51 clear();
52 }
53
54 void RenderCompositor::build(const RendererView& view, const StringID& finalNode)
55 {
56 clear();
57
58 bs_frame_mark();
59 {
60 FrameUnorderedMap<StringID, UINT32> processedNodes;
61 mIsValid = true;
62
63 std::function<bool(const StringID&)> registerNode = [&](const StringID& nodeId)
64 {
65 // Find node type
66 auto iterFind = mNodeTypes.find(nodeId);
67 if (iterFind == mNodeTypes.end())
68 {
69 LOGERR("Cannot find render compositor node of type \"" + String(nodeId.c_str()) + "\".");
70 return false;
71 }
72
73 NodeType* nodeType = iterFind->second;
74
75 // Register current node
76 auto iterFind2 = processedNodes.find(nodeId);
77
78 // New node
79 if (iterFind2 == processedNodes.end())
80 {
81 // Mark it as invalid for now
82 processedNodes[nodeId] = -1;
83 }
84
85 // Register node dependencies
86 SmallVector<StringID, 4> depIds = nodeType->getDependencies(view);
87 for (auto& dep : depIds)
88 {
89 if (!registerNode(dep))
90 return false;
91 }
92
93 // Register current node
94 UINT32 curIdx;
95
96 // New node, properly populate its index
97 if (iterFind2 == processedNodes.end())
98 {
99 iterFind2 = processedNodes.find(nodeId);
100
101 curIdx = (UINT32)mNodeInfos.size();
102 mNodeInfos.push_back(NodeInfo());
103 processedNodes[nodeId] = curIdx;
104
105 NodeInfo& nodeInfo = mNodeInfos.back();
106 nodeInfo.node = nodeType->create();
107 nodeInfo.nodeType = nodeType;
108 nodeInfo.lastUseIdx = -1;
109
110 for (auto& depId : depIds)
111 {
112 iterFind2 = processedNodes.find(depId);
113
114 NodeInfo& depNodeInfo = mNodeInfos[iterFind2->second];
115 nodeInfo.inputs.add(depNodeInfo.node);
116 }
117 }
118 else // Existing node
119 {
120 curIdx = iterFind2->second;
121
122 // Check if invalid
123 if (curIdx == (UINT32)-1)
124 {
125 LOGERR("Render compositor nodes recursion detected. Node \"" + String(nodeId.c_str()) + "\" " +
126 "depends on node \"" + String(iterFind->first.c_str()) + "\" which is not available at " +
127 "this stage.");
128 return false;
129 }
130 }
131
132 // Update dependency last use counters
133 for (auto& dep : depIds)
134 {
135 iterFind2 = processedNodes.find(dep);
136
137 NodeInfo& depNodeInfo = mNodeInfos[iterFind2->second];
138 if (depNodeInfo.lastUseIdx == (UINT32)-1)
139 depNodeInfo.lastUseIdx = curIdx;
140 else
141 depNodeInfo.lastUseIdx = std::max(depNodeInfo.lastUseIdx, curIdx);
142 }
143
144 return true;
145 };
146
147 mIsValid = registerNode(finalNode);
148
149 if (!mIsValid)
150 clear();
151 }
152 bs_frame_clear();
153 }
154
155 void RenderCompositor::execute(RenderCompositorNodeInputs& inputs) const
156 {
157 if (!mIsValid)
158 return;
159
160 bs_frame_mark();
161 {
162 FrameVector<const NodeInfo*> activeNodes;
163
164 UINT32 idx = 0;
165 for (auto& entry : mNodeInfos)
166 {
167 inputs.inputNodes = entry.inputs;
168
169#if BS_PROFILING_ENABLED
170 const ProfilerString sampleName = ProfilerString("RC: ") + entry.nodeType->id.c_str();
171 BS_GPU_PROFILE_BEGIN(sampleName);
172 gProfilerCPU().beginSample(sampleName.c_str());
173#endif
174
175 entry.node->render(inputs);
176
177#if BS_PROFILING_ENABLED
178 gProfilerCPU().endSample(sampleName.c_str());
179 BS_GPU_PROFILE_END(sampleName);
180#endif
181
182 activeNodes.push_back(&entry);
183
184 for (UINT32 i = 0; i < (UINT32)activeNodes.size(); ++i)
185 {
186 if (activeNodes[i] == nullptr)
187 continue;
188
189 if (activeNodes[i]->lastUseIdx <= idx)
190 {
191 activeNodes[i]->node->clear();
192 activeNodes[i] = nullptr;
193 }
194 }
195
196 idx++;
197 }
198 }
199 bs_frame_clear();
200
201 if (!mNodeInfos.empty())
202 mNodeInfos.back().node->clear();
203 }
204
205 void RenderCompositor::clear()
206 {
207 for (auto& entry : mNodeInfos)
208 bs_delete(entry.node);
209
210 mNodeInfos.clear();
211 mIsValid = false;
212 }
213
214 void RCNodeSceneDepth::render(const RenderCompositorNodeInputs& inputs)
215 {
216 GpuResourcePool& resPool = GpuResourcePool::instance();
217 const RendererViewProperties& viewProps = inputs.view.getProperties();
218
219 UINT32 width = viewProps.target.viewRect.width;
220 UINT32 height = viewProps.target.viewRect.height;
221 UINT32 numSamples = viewProps.target.numSamples;
222
223 depthTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_D32_S8X24, width, height, TU_DEPTHSTENCIL,
224 numSamples, false));
225 }
226
227 void RCNodeSceneDepth::clear()
228 {
229 GpuResourcePool& resPool = GpuResourcePool::instance();
230 resPool.release(depthTex);
231 }
232
233 SmallVector<StringID, 4> RCNodeSceneDepth::getDependencies(const RendererView& view)
234 {
235 return {};
236 }
237
238 void RCNodeBasePass::render(const RenderCompositorNodeInputs& inputs)
239 {
240 // Allocate necessary textures & targets
241 GpuResourcePool& resPool = GpuResourcePool::instance();
242 const RendererViewProperties& viewProps = inputs.view.getProperties();
243
244 const UINT32 width = viewProps.target.viewRect.width;
245 const UINT32 height = viewProps.target.viewRect.height;
246 const UINT32 numSamples = viewProps.target.numSamples;
247
248 // Note: Consider customizable formats. e.g. for testing if quality can be improved with higher precision normals.
249 albedoTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA8, width, height, TU_RENDERTARGET,
250 numSamples, true));
251 normalTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGB10A2, width, height, TU_RENDERTARGET,
252 numSamples, false));
253 roughMetalTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RG16F, width, height, TU_RENDERTARGET,
254 numSamples, false)); // Note: Metal doesn't need 16-bit float
255 idTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width, height, TU_RENDERTARGET,
256 numSamples, false));
257
258 auto sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
259 auto sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[1]);
260 SPtr<PooledRenderTexture> sceneDepthTex = sceneDepthNode->depthTex;
261 SPtr<PooledRenderTexture> sceneColorTex = sceneColorNode->sceneColorTex;
262
263 bool rebuildRT = false;
264 if (renderTarget != nullptr)
265 {
266 rebuildRT |= renderTarget->getColorTexture(0) != sceneColorTex->texture;
267 rebuildRT |= renderTarget->getColorTexture(1) != albedoTex->texture;
268 rebuildRT |= renderTarget->getColorTexture(2) != normalTex->texture;
269 rebuildRT |= renderTarget->getColorTexture(3) != roughMetalTex->texture;
270 rebuildRT |= renderTarget->getColorTexture(4) != idTex->texture;
271 rebuildRT |= renderTarget->getDepthStencilTexture() != sceneDepthTex->texture;
272 }
273 else
274 rebuildRT = true;
275
276 if (renderTarget == nullptr || rebuildRT)
277 {
278 RENDER_TEXTURE_DESC gbufferDesc;
279 gbufferDesc.colorSurfaces[0].texture = sceneColorTex->texture;
280 gbufferDesc.colorSurfaces[0].face = 0;
281 gbufferDesc.colorSurfaces[0].numFaces = 1;
282 gbufferDesc.colorSurfaces[0].mipLevel = 0;
283
284 gbufferDesc.colorSurfaces[1].texture = albedoTex->texture;
285 gbufferDesc.colorSurfaces[1].face = 0;
286 gbufferDesc.colorSurfaces[1].numFaces = 1;
287 gbufferDesc.colorSurfaces[1].mipLevel = 0;
288
289 gbufferDesc.colorSurfaces[2].texture = normalTex->texture;
290 gbufferDesc.colorSurfaces[2].face = 0;
291 gbufferDesc.colorSurfaces[2].numFaces = 1;
292 gbufferDesc.colorSurfaces[2].mipLevel = 0;
293
294 gbufferDesc.colorSurfaces[3].texture = roughMetalTex->texture;
295 gbufferDesc.colorSurfaces[3].face = 0;
296 gbufferDesc.colorSurfaces[3].numFaces = 1;
297 gbufferDesc.colorSurfaces[3].mipLevel = 0;
298
299 gbufferDesc.depthStencilSurface.texture = sceneDepthTex->texture;
300 gbufferDesc.depthStencilSurface.face = 0;
301 gbufferDesc.depthStencilSurface.mipLevel = 0;
302
303 renderTargetNoMask = RenderTexture::create(gbufferDesc);
304
305 gbufferDesc.colorSurfaces[4].texture = idTex->texture;
306 gbufferDesc.colorSurfaces[4].face = 0;
307 gbufferDesc.colorSurfaces[4].numFaces = 1;
308 gbufferDesc.colorSurfaces[4].mipLevel = 0;
309
310 renderTarget = RenderTexture::create(gbufferDesc);
311 }
312
313 // Prepare all visible objects. Note that this also prepares non-opaque objects.
314 //// Prepare normal renderables
315 const VisibilityInfo& visibility = inputs.view.getVisibilityMasks();
316 const auto numRenderables = (UINT32)inputs.scene.renderables.size();
317 for (UINT32 i = 0; i < numRenderables; i++)
318 {
319 if (!visibility.renderables[i])
320 continue;
321
322 RendererRenderable* rendererRenderable = inputs.scene.renderables[i];
323 rendererRenderable->updatePerCallBuffer(viewProps.viewProjTransform);
324
325 for (auto& element : inputs.scene.renderables[i]->elements)
326 {
327 SPtr<GpuParams> gpuParams = element.params->getGpuParams();
328 for(UINT32 j = 0; j < GPT_COUNT; j++)
329 {
330 const GpuParamBinding& binding = element.perCameraBindings[j];
331 if(binding.slot != (UINT32)-1)
332 gpuParams->setParamBlockBuffer(binding.set, binding.slot, inputs.view.getPerViewBuffer());
333 }
334 }
335 }
336
337 //// Prepare particle systems
338 const ParticlePerFrameData* particleData = inputs.frameInfo.perFrameData.particles;
339 if(particleData)
340 {
341 const auto numParticleSystems = (UINT32)inputs.scene.particleSystems.size();
342
343 const GpuParticleResources& gpuSimResources = GpuParticleSimulation::instance().getResources();
344 for (UINT32 i = 0; i < numParticleSystems; i++)
345 {
346 if (!visibility.particleSystems[i])
347 continue;
348
349 const RendererParticles& rendererParticles = inputs.scene.particleSystems[i];
350 ParticlesRenderElement& renderElement = rendererParticles.renderElement;
351
352 if(!renderElement.isValid())
353 continue;
354
355 ParticleSystem* particleSystem = rendererParticles.particleSystem;
356
357 // Bind textures/buffers from CPU simulation
358 const auto iterFind = particleData->cpuData.find(particleSystem->getId());
359 if (iterFind != particleData->cpuData.end())
360 {
361 ParticleRenderData* renderData = iterFind->second;
362 rendererParticles.bindCPUSimulatedInputs(renderData, inputs.view);
363 }
364 // Bind textures/buffers from GPU simulation
365 else if(rendererParticles.gpuParticleSystem)
366 rendererParticles.bindGPUSimulatedInputs(gpuSimResources, inputs.view);
367 }
368 }
369
370 //// Prepare decals
371 const auto numDecals = (UINT32)inputs.scene.decals.size();
372 for (UINT32 i = 0; i < numDecals; i++)
373 {
374 if (!visibility.decals[i])
375 continue;
376
377 const RendererDecal& rendererDecal = inputs.scene.decals[i];
378 DecalRenderElement& renderElement = rendererDecal.renderElement;
379
380 rendererDecal.updatePerCallBuffer(viewProps.viewProjTransform);
381
382 SPtr<GpuParams> gpuParams = renderElement.params->getGpuParams();
383 for (UINT32 j = 0; j < GPT_COUNT; j++)
384 {
385 const GpuParamBinding& binding = renderElement.perCameraBindings[j];
386 if (binding.slot != (UINT32)-1)
387 gpuParams->setParamBlockBuffer(binding.set, binding.slot, inputs.view.getPerViewBuffer());
388 }
389
390 renderElement.depthInputTexture.set(sceneDepthTex->texture);
391 renderElement.maskInputTexture.set(idTex->texture);
392 }
393
394 Camera* sceneCamera = inputs.view.getSceneCamera();
395
396 // Trigger prepare callbacks
397 if (sceneCamera != nullptr)
398 {
399 for(auto& extension : inputs.extPrepare)
400 {
401 if (extension->check(*sceneCamera))
402 extension->render(*sceneCamera);
403 }
404 }
405
406 // Render base pass
407 RenderAPI& rapi = RenderAPI::instance();
408 rapi.setRenderTarget(renderTarget);
409
410 Rect2 area(0.0f, 0.0f, 1.0f, 1.0f);
411 rapi.setViewport(area);
412
413 // Clear all targets
414 rapi.clearViewport(FBT_COLOR | FBT_DEPTH | FBT_STENCIL, Color::ZERO, 1.0f, 0);
415
416 // Trigger pre-base-pass callbacks
417 if (sceneCamera != nullptr)
418 {
419 for(auto& extension : inputs.extPreBasePass)
420 {
421 if (extension->check(*sceneCamera))
422 extension->render(*sceneCamera);
423 }
424 }
425
426 // Render all visible opaque elements that use the deferred pipeline
427 const Vector<RenderQueueElement>& opaqueElements = inputs.view.getOpaqueQueue(false)->getSortedElements();
428 renderQueueElements(opaqueElements);
429
430 // Determine MSAA coverage if required
431 if (viewProps.target.numSamples > 1)
432 {
433 auto msaaCoverageNode = static_cast<RCNodeMSAACoverage*>(inputs.inputNodes[3]);
434
435 GBufferTextures gbuffer;
436 gbuffer.albedo = albedoTex->texture;
437 gbuffer.normals = normalTex->texture;
438 gbuffer.roughMetal = roughMetalTex->texture;
439 gbuffer.depth = sceneDepthNode->depthTex->texture;
440
441 MSAACoverageMat* mat = MSAACoverageMat::getVariation(viewProps.target.numSamples);
442 rapi.setRenderTarget(msaaCoverageNode->output->renderTexture);
443 mat->execute(inputs.view, gbuffer);
444
445 MSAACoverageStencilMat* stencilMat = MSAACoverageStencilMat::get();
446 rapi.setRenderTarget(sceneDepthNode->depthTex->renderTexture);
447 stencilMat->execute(inputs.view, msaaCoverageNode->output->texture);
448 }
449
450 // Render decals after all normal objects, using a read-only depth buffer
451 rapi.setRenderTarget(renderTargetNoMask, FBT_DEPTH, RT_ALL);
452
453 const Vector<RenderQueueElement>& decalElements = inputs.view.getDecalQueue()->getSortedElements();
454 renderQueueElements(decalElements);
455
456 // Make sure that any compute shaders are able to read g-buffer by unbinding it
457 rapi.setRenderTarget(nullptr);
458
459 // Trigger post-base-pass callbacks
460 if (sceneCamera != nullptr)
461 {
462 for(auto& extension : inputs.extPostBasePass)
463 {
464 if (extension->check(*sceneCamera))
465 extension->render(*sceneCamera);
466 }
467 }
468 }
469
470 void RCNodeBasePass::clear()
471 {
472 GpuResourcePool& resPool = GpuResourcePool::instance();
473
474 resPool.release(albedoTex);
475 resPool.release(normalTex);
476 resPool.release(roughMetalTex);
477 resPool.release(idTex);
478 }
479
480 SmallVector<StringID, 4> RCNodeBasePass::getDependencies(const RendererView& view)
481 {
482 return {
483 RCNodeSceneDepth::getNodeId(), RCNodeSceneColor::getNodeId(), RCNodeParticleSort::getNodeId(),
484 RCNodeMSAACoverage::getNodeId() };
485 }
486
487 void RCNodeSceneColor::render(const RenderCompositorNodeInputs& inputs)
488 {
489 GpuResourcePool& resPool = GpuResourcePool::instance();
490 const RendererViewProperties& viewProps = inputs.view.getProperties();
491
492 UINT32 width = viewProps.target.viewRect.width;
493 UINT32 height = viewProps.target.viewRect.height;
494 UINT32 numSamples = viewProps.target.numSamples;
495
496 UINT32 usageFlags = TU_RENDERTARGET;
497
498 bool tiledDeferredSupported = inputs.featureSet != RenderBeastFeatureSet::DesktopMacOS;
499 if(tiledDeferredSupported && numSamples == 1)
500 usageFlags |= TU_LOADSTORE;
501
502 // Note: Consider customizable HDR format via options? e.g. smaller PF_FLOAT_R11G11B10 or larger 32-bit format
503 sceneColorTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height, usageFlags,
504 numSamples, false));
505
506 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
507 SPtr<PooledRenderTexture> sceneDepthTex = sceneDepthNode->depthTex;
508
509 if (tiledDeferredSupported && viewProps.target.numSamples > 1)
510 {
511 sceneColorTexArray = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height,
512 TU_LOADSTORE, 1, false, viewProps.target.numSamples));
513 }
514 else
515 sceneColorTexArray = nullptr;
516
517 bool rebuildRT = false;
518 if (renderTarget != nullptr)
519 {
520 rebuildRT |= renderTarget->getColorTexture(0) != sceneColorTex->texture;
521 rebuildRT |= renderTarget->getDepthStencilTexture() != sceneDepthTex->texture;
522 }
523 else
524 rebuildRT = true;
525
526 if (rebuildRT)
527 {
528 RENDER_TEXTURE_DESC sceneColorDesc;
529 sceneColorDesc.colorSurfaces[0].texture = sceneColorTex->texture;
530 sceneColorDesc.colorSurfaces[0].face = 0;
531 sceneColorDesc.colorSurfaces[0].numFaces = 1;
532 sceneColorDesc.colorSurfaces[0].mipLevel = 0;
533
534 sceneColorDesc.depthStencilSurface.texture = sceneDepthTex->texture;
535 sceneColorDesc.depthStencilSurface.face = 0;
536 sceneColorDesc.depthStencilSurface.numFaces = 1;
537 sceneColorDesc.depthStencilSurface.mipLevel = 0;
538
539 renderTarget = RenderTexture::create(sceneColorDesc);
540 }
541 }
542
543 void RCNodeSceneColor::clear()
544 {
545 GpuResourcePool& resPool = GpuResourcePool::instance();
546 resPool.release(sceneColorTex);
547
548 if (sceneColorTexArray != nullptr)
549 resPool.release(sceneColorTexArray);
550 }
551
552 void RCNodeSceneColor::resolveMSAA()
553 {
554 RenderAPI& rapi = RenderAPI::instance();
555 rapi.setRenderTarget(renderTarget, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
556
557 Rect2 area(0.0f, 0.0f, 1.0f, 1.0f);
558 rapi.setViewport(area);
559
560 TextureArrayToMSAATexture* material = TextureArrayToMSAATexture::get();
561 material->execute(sceneColorTexArray->texture, sceneColorTex->texture);
562 }
563
564 SmallVector<StringID, 4> RCNodeSceneColor::getDependencies(const RendererView& view)
565 {
566 return { RCNodeSceneDepth::getNodeId() };
567 }
568
569 void RCNodeMSAACoverage::render(const RenderCompositorNodeInputs& inputs)
570 {
571 const RendererViewProperties& viewProps = inputs.view.getProperties();
572 if(viewProps.target.numSamples <= 1)
573 {
574 // No need for MSAA coverage
575 output = nullptr;
576 return;
577 }
578
579 GpuResourcePool& resPool = GpuResourcePool::instance();
580
581 UINT32 width = viewProps.target.viewRect.width;
582 UINT32 height = viewProps.target.viewRect.height;
583
584 // We just allocate the texture, while the base pass is responsible for filling it out
585 output = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width, height, TU_RENDERTARGET));
586 }
587
588 void RCNodeMSAACoverage::clear()
589 {
590 if(output)
591 {
592 GpuResourcePool& resPool = GpuResourcePool::instance();
593 resPool.release(output);
594 }
595 }
596
597 SmallVector<StringID, 4> RCNodeMSAACoverage::getDependencies(const RendererView& view)
598 {
599 return { };
600 }
601
602 void RCNodeParticleSimulate::render(const RenderCompositorNodeInputs& inputs)
603 {
604 // Only simulate particles for the first view in the main render pass
605 if(inputs.viewGroup.isMainPass() && inputs.view.getViewIdx() == 0)
606 {
607 RCNodeBasePass* gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[0]);
608 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
609
610 GBufferTextures gbuffer;
611 gbuffer.albedo = gbufferNode->albedoTex->texture;
612 gbuffer.normals = gbufferNode->normalTex->texture;
613 gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
614 gbuffer.depth = sceneDepthNode->depthTex->texture;
615
616 GpuParticleSimulation::instance().simulate(inputs.scene, inputs.frameInfo.perFrameData.particles,
617 inputs.view.getPerViewBuffer(), gbuffer, inputs.frameInfo.timeDelta);
618 }
619
620 GpuParticleSimulation::instance().sort(inputs.view);
621 }
622
623 void RCNodeParticleSimulate::clear()
624 {
625 // Do nothing
626 }
627
628 SmallVector<StringID, 4> RCNodeParticleSimulate::getDependencies(const RendererView& view)
629 {
630 return { RCNodeBasePass::getNodeId(), RCNodeSceneDepth::getNodeId() };
631 }
632
633 void RCNodeParticleSort::render(const RenderCompositorNodeInputs& inputs)
634 {
635 const ParticlePerFrameData* particleData = inputs.frameInfo.perFrameData.particles;
636 if(!particleData)
637 return;
638
639 const RendererViewProperties& viewProps = inputs.view.getProperties();
640 const VisibilityInfo& visibility = inputs.view.getVisibilityMasks();
641 const auto numParticleSystems = (UINT32)inputs.scene.particleSystems.size();
642
643 // Sort particles
644 bs_frame_mark();
645 {
646 struct SortData
647 {
648 ParticleSystem* system;
649 ParticleRenderData* renderData;
650 };
651
652 FrameVector<SortData> systemsToSort;
653 for (UINT32 i = 0; i < numParticleSystems; i++)
654 {
655 if (!visibility.particleSystems[i])
656 continue;
657
658 const RendererParticles& rendererParticles = inputs.scene.particleSystems[i];
659
660 ParticleSystem* particleSystem = rendererParticles.particleSystem;
661 const auto iterFind = particleData->cpuData.find(particleSystem->getId());
662 if (iterFind == particleData->cpuData.end())
663 continue;
664
665 ParticleRenderData* simulationData = iterFind->second;
666 if (particleSystem->getSettings().sortMode == ParticleSortMode::Distance)
667 systemsToSort.push_back({ particleSystem, simulationData });
668 }
669
670 const auto worker = [&systemsToSort, viewOrigin = viewProps.viewOrigin](UINT32 idx)
671 {
672 const SortData& data = systemsToSort[idx];
673
674 Vector3 refPoint = viewOrigin;
675
676 // Transform the view point into particle system's local space
677 const ParticleSystemSettings& settings = data.system->getSettings();
678 if (settings.simulationSpace == ParticleSimulationSpace::Local)
679 refPoint = data.system->getTransform().getInvMatrix().multiplyAffine(refPoint);
680
681 if (settings.renderMode == ParticleRenderMode::Billboard)
682 {
683 auto renderData = static_cast<ParticleBillboardRenderData*>(data.renderData);
684 ParticleRenderer::sortByDistance(refPoint, renderData->positionAndRotation,
685 renderData->numParticles, 4, renderData->indices);
686 }
687 else
688 {
689 auto renderData = static_cast<ParticleMeshRenderData*>(data.renderData);
690 ParticleRenderer::sortByDistance(refPoint, renderData->position, renderData->numParticles,
691 3, renderData->indices);
692 }
693 };
694
695 SPtr<TaskGroup> sortTask = TaskGroup::create("ParticleSort", worker, (UINT32)systemsToSort.size());
696
697 TaskScheduler::instance().addTaskGroup(sortTask);
698 sortTask->wait();
699 }
700 bs_frame_clear();
701 }
702
703 void RCNodeParticleSort::clear()
704 {
705 // Do nothing
706 }
707
708 SmallVector<StringID, 4> RCNodeParticleSort::getDependencies(const RendererView& view)
709 {
710 return { };
711 }
712
713 void RCNodeLightAccumulation::render(const RenderCompositorNodeInputs& inputs)
714 {
715 bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
716 if(!supportsTiledDeferred)
717 {
718 // If tiled deferred is not supported, we don't need a separate texture for light accumulation, instead we
719 // use scene color directly
720 RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
721 lightAccumulationTex = sceneColorNode->sceneColorTex;
722 renderTarget = sceneColorNode->renderTarget;
723
724 mOwnsTexture = false;
725 return;
726 }
727
728 GpuResourcePool& resPool = GpuResourcePool::instance();
729 const RendererViewProperties& viewProps = inputs.view.getProperties();
730
731 RCNodeSceneDepth* depthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
732
733 UINT32 width = viewProps.target.viewRect.width;
734 UINT32 height = viewProps.target.viewRect.height;
735 UINT32 numSamples = viewProps.target.numSamples;
736
737 UINT32 usage = TU_RENDERTARGET;
738 if (numSamples > 1)
739 {
740 lightAccumulationTexArray = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height,
741 TU_LOADSTORE, 1, false, numSamples));
742
743 ClearLoadStoreMat* clearMat = ClearLoadStoreMat::getVariation(ClearLoadStoreType::TextureArray,
744 ClearLoadStoreDataType::Float, 4);
745
746 for(UINT32 i = 0; i < numSamples; i++)
747 {
748 TextureSurface surface;
749 surface.face = i;
750 surface.numFaces = 1;
751 surface.mipLevel = 0;
752 surface.numMipLevels = 1;
753
754 clearMat->execute(lightAccumulationTexArray->texture, Color::ZERO, surface);
755 }
756 }
757 else
758 {
759 usage |= TU_LOADSTORE;
760 lightAccumulationTexArray = nullptr;
761 }
762
763 lightAccumulationTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width,
764 height, usage, numSamples, false));
765
766 bool rebuildRT;
767 if (renderTarget != nullptr)
768 {
769 rebuildRT = renderTarget->getColorTexture(0) != lightAccumulationTex->texture;
770 rebuildRT |= renderTarget->getDepthStencilTexture() != depthNode->depthTex->texture;
771 }
772 else
773 rebuildRT = true;
774
775 if (rebuildRT)
776 {
777 RENDER_TEXTURE_DESC lightAccumulationRTDesc;
778 lightAccumulationRTDesc.colorSurfaces[0].texture = lightAccumulationTex->texture;
779 lightAccumulationRTDesc.colorSurfaces[0].face = 0;
780 lightAccumulationRTDesc.colorSurfaces[0].numFaces = 1;
781 lightAccumulationRTDesc.colorSurfaces[0].mipLevel = 0;
782
783 lightAccumulationRTDesc.depthStencilSurface.texture = depthNode->depthTex->texture;
784 lightAccumulationRTDesc.depthStencilSurface.face = 0;
785 lightAccumulationRTDesc.depthStencilSurface.numFaces = 1;
786 lightAccumulationRTDesc.depthStencilSurface.mipLevel = 0;
787
788 renderTarget = RenderTexture::create(lightAccumulationRTDesc);
789 }
790
791 mOwnsTexture = true;
792 }
793
794 void RCNodeLightAccumulation::resolveMSAA()
795 {
796 RenderAPI& rapi = RenderAPI::instance();
797 rapi.setRenderTarget(renderTarget, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
798
799 TextureArrayToMSAATexture* material = TextureArrayToMSAATexture::get();
800 material->execute(lightAccumulationTexArray->texture, lightAccumulationTex->texture);
801 }
802
803 void RCNodeLightAccumulation::clear()
804 {
805 GpuResourcePool& resPool = GpuResourcePool::instance();
806 if(mOwnsTexture)
807 resPool.release(lightAccumulationTex);
808 else
809 {
810 lightAccumulationTex = nullptr;
811 renderTarget = nullptr;
812 }
813
814 if(lightAccumulationTexArray)
815 resPool.release(lightAccumulationTexArray);
816 }
817
818 SmallVector<StringID, 4> RCNodeLightAccumulation::getDependencies(const RendererView& view)
819 {
820 SmallVector<StringID, 4> deps;
821
822 const bool supportsTiledDeferred = gRenderBeast()->getFeatureSet() != RenderBeastFeatureSet::DesktopMacOS;
823 if(!supportsTiledDeferred)
824 deps.add(RCNodeSceneColor::getNodeId());
825 else
826 deps.add(RCNodeSceneDepth::getNodeId());
827
828 return deps;
829 }
830
831 void RCNodeDeferredDirectLighting::render(const RenderCompositorNodeInputs& inputs)
832 {
833 output = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[0]);
834
835 auto gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[1]);
836 auto sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[2]);
837 auto sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[3]);
838
839 GBufferTextures gbuffer;
840 gbuffer.albedo = gbufferNode->albedoTex->texture;
841 gbuffer.normals = gbufferNode->normalTex->texture;
842 gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
843 gbuffer.depth = sceneDepthNode->depthTex->texture;
844
845 const RendererViewProperties& viewProps = inputs.view.getProperties();
846
847 if (!inputs.view.getRenderSettings().enableShadows)
848 mLightOcclusionRT = nullptr;
849
850 bool tiledDeferredSupported = inputs.featureSet != RenderBeastFeatureSet::DesktopMacOS;
851 if(tiledDeferredSupported)
852 {
853 SPtr<Texture> msaaCoverage;
854 if(viewProps.target.numSamples > 1)
855 {
856 RCNodeMSAACoverage* coverageNode = static_cast<RCNodeMSAACoverage*>(inputs.inputNodes[4]);
857 msaaCoverage = coverageNode->output->texture;
858 }
859
860 TiledDeferredLightingMat* tiledDeferredMat =
861 TiledDeferredLightingMat::getVariation(viewProps.target.numSamples);
862
863 const VisibleLightData& lightData = inputs.viewGroup.getVisibleLightData();
864
865 SPtr<Texture> lightAccumTexArray;
866 if(output->lightAccumulationTexArray)
867 lightAccumTexArray = output->lightAccumulationTexArray->texture;
868
869 tiledDeferredMat->execute(inputs.view, lightData, gbuffer, sceneColorNode->sceneColorTex->texture,
870 output->lightAccumulationTex->texture, lightAccumTexArray, msaaCoverage);
871
872 if (viewProps.target.numSamples > 1)
873 output->resolveMSAA();
874
875 // If shadows are disabled we handle all lights through tiled deferred so we can exit immediately
876 if (!inputs.view.getRenderSettings().enableShadows)
877 return;
878 }
879
880 // Standard deferred used for shadowed lights, or when tiled deferred isn't supported
881 GpuResourcePool& resPool = GpuResourcePool::instance();
882
883 UINT32 width = viewProps.target.viewRect.width;
884 UINT32 height = viewProps.target.viewRect.height;
885 UINT32 numSamples = viewProps.target.numSamples;
886
887 const VisibleLightData& lightData = inputs.viewGroup.getVisibleLightData();
888
889 RenderAPI& rapi = RenderAPI::instance();
890
891 // Render unshadowed lights
892 if(!tiledDeferredSupported)
893 {
894 ProfileGPUBlock sampleBlock("Standard deferred unshadowed lights");
895
896 rapi.setRenderTarget(output->renderTarget, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
897
898 for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
899 {
900 LightType lightType = (LightType)i;
901
902 auto& lights = lightData.getLights(lightType);
903 UINT32 count = lightData.getNumUnshadowedLights(lightType);
904
905 for (UINT32 j = 0; j < count; j++)
906 {
907 UINT32 lightIdx = j;
908 const RendererLight& light = *lights[lightIdx];
909
910 StandardDeferred::instance().renderLight(lightType, light, inputs.view, gbuffer, Texture::BLACK);
911 }
912 }
913 }
914
915 // Allocate light occlusion
916 SPtr<PooledRenderTexture> lightOcclusionTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width,
917 height, TU_RENDERTARGET, numSamples, false));
918
919 bool rebuildRT = false;
920 if (mLightOcclusionRT != nullptr)
921 {
922 rebuildRT |= mLightOcclusionRT->getColorTexture(0) != lightOcclusionTex->texture;
923 rebuildRT |= mLightOcclusionRT->getDepthStencilTexture() != sceneDepthNode->depthTex->texture;
924 }
925 else
926 rebuildRT = true;
927
928 if (rebuildRT)
929 {
930 RENDER_TEXTURE_DESC lightOcclusionRTDesc;
931 lightOcclusionRTDesc.colorSurfaces[0].texture = lightOcclusionTex->texture;
932 lightOcclusionRTDesc.colorSurfaces[0].face = 0;
933 lightOcclusionRTDesc.colorSurfaces[0].numFaces = 1;
934 lightOcclusionRTDesc.colorSurfaces[0].mipLevel = 0;
935
936 lightOcclusionRTDesc.depthStencilSurface.texture = sceneDepthNode->depthTex->texture;
937 lightOcclusionRTDesc.depthStencilSurface.face = 0;
938 lightOcclusionRTDesc.depthStencilSurface.numFaces = 1;
939 lightOcclusionRTDesc.depthStencilSurface.mipLevel = 0;
940
941 mLightOcclusionRT = RenderTexture::create(lightOcclusionRTDesc);
942 }
943
944 // Render shadowed lights
945 {
946 ProfileGPUBlock sampleBlock("Standard deferred shadowed lights");
947
948 const ShadowRendering& shadowRenderer = inputs.viewGroup.getShadowRenderer();
949 for (UINT32 i = 0; i < (UINT32)LightType::Count; i++)
950 {
951 LightType lightType = (LightType)i;
952
953 auto& lights = lightData.getLights(lightType);
954 UINT32 count = lightData.getNumShadowedLights(lightType);
955 UINT32 offset = lightData.getNumUnshadowedLights(lightType);
956
957 for (UINT32 j = 0; j < count; j++)
958 {
959 rapi.setRenderTarget(mLightOcclusionRT, FBT_DEPTH, RT_DEPTH_STENCIL);
960
961 Rect2 area(0.0f, 0.0f, 1.0f, 1.0f);
962 rapi.setViewport(area);
963
964 rapi.clearViewport(FBT_COLOR, Color::ZERO);
965
966 UINT32 lightIdx = offset + j;
967 const RendererLight& light = *lights[lightIdx];
968 shadowRenderer.renderShadowOcclusion(inputs.view, light, gbuffer);
969
970 rapi.setRenderTarget(output->renderTarget, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
971 StandardDeferred::instance().renderLight(lightType, light, inputs.view, gbuffer,
972 lightOcclusionTex->texture);
973 }
974 }
975 }
976
977 // Makes sure light accumulation can be read by following passes
978 rapi.setRenderTarget(nullptr);
979
980 resPool.release(lightOcclusionTex);
981 }
982
983 void RCNodeDeferredDirectLighting::clear()
984 {
985 output = nullptr;
986 }
987
988 SmallVector<StringID, 4> RCNodeDeferredDirectLighting::getDependencies(const RendererView& view)
989 {
990 SmallVector<StringID, 4> deps;
991 deps.add(RCNodeLightAccumulation::getNodeId());
992 deps.add(RCNodeBasePass::getNodeId());
993 deps.add(RCNodeSceneDepth::getNodeId());
994 deps.add(RCNodeSceneColor::getNodeId());
995 deps.add(RCNodeMSAACoverage::getNodeId());
996
997 return deps;
998 }
999
1000 void RCNodeIndirectDiffuseLighting::render(const RenderCompositorNodeInputs& inputs)
1001 {
1002 if (!inputs.view.getRenderSettings().enableIndirectLighting)
1003 return;
1004
1005 RCNodeBasePass* gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[0]);
1006 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
1007 RCNodeLightAccumulation* lightAccumNode = static_cast <RCNodeLightAccumulation*>(inputs.inputNodes[2]);
1008 RCNodeSSAO* ssaoNode = static_cast<RCNodeSSAO*>(inputs.inputNodes[3]);
1009
1010 GpuResourcePool& resPool = GpuResourcePool::instance();
1011 const RendererViewProperties& viewProps = inputs.view.getProperties();
1012
1013 const LightProbes& lightProbes = inputs.scene.lightProbes;
1014 LightProbesInfo lpInfo = lightProbes.getInfo();
1015
1016 IrradianceEvaluateMat* evaluateMat;
1017 SPtr<PooledRenderTexture> volumeIndices;
1018 if(lightProbes.hasAnyProbes())
1019 {
1020 POOLED_RENDER_TEXTURE_DESC volumeIndicesDesc;
1021 POOLED_RENDER_TEXTURE_DESC depthDesc;
1022 TetrahedraRenderMat::getOutputDesc(inputs.view, volumeIndicesDesc, depthDesc);
1023
1024 volumeIndices = resPool.get(volumeIndicesDesc);
1025 SPtr<PooledRenderTexture> depthTex = resPool.get(depthDesc);
1026
1027 RENDER_TEXTURE_DESC rtDesc;
1028 rtDesc.colorSurfaces[0].texture = volumeIndices->texture;
1029 rtDesc.depthStencilSurface.texture = depthTex->texture;
1030
1031 SPtr<RenderTexture> rt = RenderTexture::create(rtDesc);
1032
1033 RenderAPI& rapi = RenderAPI::instance();
1034 rapi.setRenderTarget(rt);
1035 rapi.clearRenderTarget(FBT_DEPTH);
1036 gRendererUtility().clear(-1);
1037
1038 TetrahedraRenderMat* renderTetrahedra =
1039 TetrahedraRenderMat::getVariation(viewProps.target.numSamples > 1, true);
1040 renderTetrahedra->execute(inputs.view, sceneDepthNode->depthTex->texture, lpInfo.tetrahedraVolume, rt);
1041
1042 rt = nullptr;
1043 resPool.release(depthTex);
1044
1045 evaluateMat = IrradianceEvaluateMat::getVariation(viewProps.target.numSamples > 1, true, false);
1046 }
1047 else // Sky only
1048 {
1049 evaluateMat = IrradianceEvaluateMat::getVariation(viewProps.target.numSamples > 1, true, true);
1050 }
1051
1052 GBufferTextures gbuffer;
1053 gbuffer.albedo = gbufferNode->albedoTex->texture;
1054 gbuffer.normals = gbufferNode->normalTex->texture;
1055 gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
1056 gbuffer.depth = sceneDepthNode->depthTex->texture;
1057
1058 SPtr<Texture> volumeIndicesTex;
1059 if (volumeIndices)
1060 volumeIndicesTex = volumeIndices->texture;
1061
1062 Skybox* skybox = nullptr;
1063 if(inputs.view.getRenderSettings().enableSkybox)
1064 skybox = inputs.scene.skybox;
1065
1066 evaluateMat->execute(inputs.view, gbuffer, volumeIndicesTex, lpInfo, skybox, ssaoNode->output,
1067 lightAccumNode->renderTarget);
1068
1069 if(volumeIndices)
1070 resPool.release(volumeIndices);
1071 }
1072
1073 void RCNodeIndirectDiffuseLighting::clear()
1074 {
1075 // Do nothing
1076 }
1077
1078 SmallVector<StringID, 4> RCNodeIndirectDiffuseLighting::getDependencies(const RendererView& view)
1079 {
1080 SmallVector<StringID, 4> deps;
1081 deps.add(RCNodeBasePass::getNodeId());
1082 deps.add(RCNodeSceneDepth::getNodeId());
1083 deps.add(RCNodeLightAccumulation::getNodeId());
1084 deps.add(RCNodeSSAO::getNodeId());
1085 deps.add(RCNodeDeferredDirectLighting::getNodeId());
1086
1087 return deps;
1088 }
1089
1090 void RCNodeDeferredIndirectSpecularLighting::render(const RenderCompositorNodeInputs& inputs)
1091 {
1092 RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
1093 RCNodeBasePass* gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[1]);
1094 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[2]);
1095 RCNodeLightAccumulation* lightAccumNode = static_cast <RCNodeLightAccumulation*>(inputs.inputNodes[3]);
1096 RCNodeSSR* ssrNode = static_cast<RCNodeSSR*>(inputs.inputNodes[4]);
1097 RCNodeSSAO* ssaoNode = static_cast<RCNodeSSAO*>(inputs.inputNodes[5]);
1098
1099 GBufferTextures gbuffer;
1100 gbuffer.albedo = gbufferNode->albedoTex->texture;
1101 gbuffer.normals = gbufferNode->normalTex->texture;
1102 gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
1103 gbuffer.depth = sceneDepthNode->depthTex->texture;
1104
1105 const RendererViewProperties& viewProps = inputs.view.getProperties();
1106
1107 bool tiledDeferredSupported = inputs.featureSet != RenderBeastFeatureSet::DesktopMacOS;
1108 if(tiledDeferredSupported)
1109 {
1110 SPtr<Texture> msaaCoverage;
1111 if (viewProps.target.numSamples > 1)
1112 {
1113 RCNodeMSAACoverage* coverageNode = static_cast<RCNodeMSAACoverage*>(inputs.inputNodes[6]);
1114 msaaCoverage = coverageNode->output->texture;
1115 }
1116
1117 TiledDeferredImageBasedLightingMat* material =
1118 TiledDeferredImageBasedLightingMat::getVariation(viewProps.target.numSamples);
1119
1120 TiledDeferredImageBasedLightingMat::Inputs iblInputs;
1121 iblInputs.gbuffer = gbuffer;
1122 iblInputs.sceneColorTex = sceneColorNode->sceneColorTex->texture;
1123 iblInputs.lightAccumulation = lightAccumNode->lightAccumulationTex->texture;
1124 iblInputs.preIntegratedGF = RendererTextures::preintegratedEnvGF;
1125 iblInputs.ambientOcclusion = ssaoNode->output;
1126 iblInputs.ssr = ssrNode->output;
1127 iblInputs.msaaCoverage = msaaCoverage;
1128
1129 if (sceneColorNode->sceneColorTexArray)
1130 iblInputs.sceneColorTexArray = sceneColorNode->sceneColorTexArray->texture;
1131
1132 material->execute(inputs.view, inputs.scene, inputs.viewGroup.getVisibleReflProbeData(), iblInputs);
1133
1134 if(viewProps.target.numSamples > 1)
1135 sceneColorNode->resolveMSAA();
1136 }
1137 else // Standard deferred
1138 {
1139 SPtr<RenderTexture> outputRT = lightAccumNode->renderTarget;
1140
1141 GpuResourcePool& resPool = GpuResourcePool::instance();
1142
1143 UINT32 width = viewProps.target.viewRect.width;
1144 UINT32 height = viewProps.target.viewRect.height;
1145 UINT32 numSamples = viewProps.target.numSamples;
1146
1147 RenderAPI& rapi = RenderAPI::instance();
1148
1149 bool isMSAA = viewProps.target.numSamples > 1;
1150
1151 SPtr<PooledRenderTexture> iblRadianceTex = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width,
1152 height, TU_RENDERTARGET, numSamples, false));
1153
1154 RENDER_TEXTURE_DESC rtDesc;
1155 rtDesc.colorSurfaces[0].texture = iblRadianceTex->texture;
1156 rtDesc.depthStencilSurface.texture = sceneDepthNode->depthTex->texture;
1157
1158 SPtr<GpuParamBlockBuffer> perViewBuffer = inputs.view.getPerViewBuffer();
1159
1160 SPtr<RenderTexture> iblRadianceRT = RenderTexture::create(rtDesc);
1161 rapi.setRenderTarget(iblRadianceRT, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
1162
1163 const VisibleReflProbeData& probeData = inputs.viewGroup.getVisibleReflProbeData();
1164
1165 Skybox* skybox = nullptr;
1166 if(inputs.view.getRenderSettings().enableSkybox)
1167 skybox = inputs.scene.skybox;
1168
1169 ReflProbeParamBuffer reflProbeParams;
1170 reflProbeParams.populate(skybox, probeData.getNumProbes(), inputs.scene.reflProbeCubemapsTex,
1171 viewProps.capturingReflections);
1172
1173 // Prepare the texture for refl. probe and skybox rendering
1174 {
1175 DeferredIBLSetupMat* mat = DeferredIBLSetupMat::getVariation(isMSAA, true);
1176 mat->bind(gbuffer, perViewBuffer, ssrNode->output, ssaoNode->output, reflProbeParams.buffer);
1177
1178 gRendererUtility().drawScreenQuad();
1179
1180 // Draw pixels requiring per-sample evaluation
1181 if (isMSAA)
1182 {
1183 DeferredIBLSetupMat* msaaMat = DeferredIBLSetupMat::getVariation(true, false);
1184 msaaMat->bind(gbuffer, perViewBuffer, ssrNode->output, ssaoNode->output, reflProbeParams.buffer);
1185
1186 gRendererUtility().drawScreenQuad();
1187 }
1188 }
1189
1190 if (!viewProps.capturingReflections)
1191 {
1192 // Render refl. probes
1193 UINT32 numProbes = probeData.getNumProbes();
1194 for (UINT32 i = 0; i < numProbes; i++)
1195 {
1196 const ReflProbeData& probe = probeData.getProbeData(i);
1197
1198 StandardDeferred::instance().renderReflProbe(probe, inputs.view, gbuffer, inputs.scene,
1199 reflProbeParams.buffer);
1200 }
1201
1202 // Render sky
1203 SPtr<Texture> skyFilteredRadiance;
1204 if (skybox)
1205 skyFilteredRadiance = skybox->getFilteredRadiance();
1206
1207 if (skyFilteredRadiance)
1208 {
1209 DeferredIBLSkyMat* skymat = DeferredIBLSkyMat::getVariation(isMSAA, true);
1210 skymat->bind(gbuffer, perViewBuffer, skybox, reflProbeParams.buffer);
1211
1212 gRendererUtility().drawScreenQuad();
1213
1214 // Draw pixels requiring per-sample evaluation
1215 if (isMSAA)
1216 {
1217 DeferredIBLSkyMat* msaaMat = DeferredIBLSkyMat::getVariation(true, false);
1218 msaaMat->bind(gbuffer, perViewBuffer, skybox, reflProbeParams.buffer);
1219
1220 gRendererUtility().drawScreenQuad();
1221 }
1222 }
1223 }
1224
1225 // Finalize rendered reflections and output them to main render target
1226 {
1227 rapi.setRenderTarget(outputRT, FBT_DEPTH | FBT_STENCIL, RT_COLOR0 | RT_DEPTH_STENCIL);
1228
1229 DeferredIBLFinalizeMat* mat = DeferredIBLFinalizeMat::getVariation(isMSAA, true);
1230 mat->bind(gbuffer, perViewBuffer, iblRadianceTex->texture, RendererTextures::preintegratedEnvGF,
1231 reflProbeParams.buffer);
1232
1233 gRendererUtility().drawScreenQuad();
1234
1235 // Draw pixels requiring per-sample evaluation
1236 if (isMSAA)
1237 {
1238 DeferredIBLFinalizeMat* msaaMat = DeferredIBLFinalizeMat::getVariation(true, false);
1239 msaaMat->bind(gbuffer, perViewBuffer, iblRadianceTex->texture, RendererTextures::preintegratedEnvGF,
1240 reflProbeParams.buffer);
1241
1242 gRendererUtility().drawScreenQuad();
1243 }
1244 }
1245
1246 // Makes sure light accumulation can be read by following passes
1247 rapi.setRenderTarget(nullptr);
1248 }
1249 }
1250
1251 void RCNodeDeferredIndirectSpecularLighting::clear()
1252 {
1253 output = nullptr;
1254 }
1255
1256 SmallVector<StringID, 4> RCNodeDeferredIndirectSpecularLighting::getDependencies(const RendererView& view)
1257 {
1258 SmallVector<StringID, 4> deps;
1259 deps.add(RCNodeSceneColor::getNodeId());
1260 deps.add(RCNodeBasePass::getNodeId());
1261 deps.add(RCNodeSceneDepth::getNodeId());
1262 deps.add(RCNodeLightAccumulation::getNodeId());
1263 deps.add(RCNodeSSR::getNodeId());
1264 deps.add(RCNodeSSAO::getNodeId());
1265 deps.add(RCNodeMSAACoverage::getNodeId());
1266 deps.add(RCNodeIndirectDiffuseLighting::getNodeId());
1267
1268 return deps;
1269 }
1270
1271 void RCNodeClusteredForward::render(const RenderCompositorNodeInputs& inputs)
1272 {
1273 const SceneInfo& sceneInfo = inputs.scene;
1274 const RendererViewProperties& viewProps = inputs.view.getProperties();
1275
1276 const VisibleLightData& visibleLightData = inputs.viewGroup.getVisibleLightData();
1277 const VisibleReflProbeData& visibleReflProbeData = inputs.viewGroup.getVisibleReflProbeData();
1278
1279 LightGridOutputs lightGridOutputs;
1280
1281 struct StandardForwardBuffers
1282 {
1283 SPtr<GpuParamBlockBuffer> lightsParamBlock;
1284 SPtr<GpuParamBlockBuffer> reflProbesParamBlock;
1285 SPtr<GpuParamBlockBuffer> lightAndReflProbeParamsParamBlock;
1286 } standardForwardBuffers;
1287
1288 const bool supportsClusteredForward = gRenderBeast()->getFeatureSet() == RenderBeastFeatureSet::Desktop;
1289 if(supportsClusteredForward)
1290 {
1291 const LightGrid& lightGrid = inputs.view.getLightGrid();
1292 lightGridOutputs = lightGrid.getOutputs();
1293 }
1294 else
1295 {
1296 // Note: Store these instead of creating them every time?
1297 standardForwardBuffers.lightsParamBlock = gLightsParamDef.createBuffer();
1298 standardForwardBuffers.reflProbesParamBlock = gReflProbesParamDef.createBuffer();
1299 standardForwardBuffers.lightAndReflProbeParamsParamBlock = gLightAndReflProbeParamsParamDef.createBuffer();
1300 }
1301
1302 Skybox* skybox = nullptr;
1303 if(inputs.view.getRenderSettings().enableSkybox)
1304 skybox = sceneInfo.skybox;
1305
1306 // Prepare refl. probe param buffer
1307 ReflProbeParamBuffer reflProbeParamBuffer;
1308 reflProbeParamBuffer.populate(skybox, visibleReflProbeData.getNumProbes(), sceneInfo.reflProbeCubemapsTex,
1309 viewProps.capturingReflections);
1310
1311 SPtr<Texture> skyFilteredRadiance;
1312 if(skybox)
1313 skyFilteredRadiance = skybox->getFilteredRadiance();
1314
1315 const auto bindParamsForClustered = [&lightGridOutputs, &visibleLightData, &visibleReflProbeData]
1316 (GpuParams& gpuParams, const ForwardLightingParams& fwdParams, const ImageBasedLightingParams& iblParams)
1317 {
1318 for (UINT32 j = 0; j < GPT_COUNT; j++)
1319 {
1320 const GpuParamBinding& binding = fwdParams.gridParamsBindings[j];
1321 if (binding.slot != (UINT32)-1)
1322 gpuParams.setParamBlockBuffer(binding.set, binding.slot, lightGridOutputs.gridParams);
1323 }
1324
1325 fwdParams.gridLightOffsetsAndSizeParam.set(lightGridOutputs.gridLightOffsetsAndSize);
1326 fwdParams.gridProbeOffsetsAndSizeParam.set(lightGridOutputs.gridProbeOffsetsAndSize);
1327
1328 fwdParams.gridLightIndicesParam.set(lightGridOutputs.gridLightIndices);
1329 iblParams.reflectionProbeIndicesParam.set(lightGridOutputs.gridProbeIndices);
1330
1331 fwdParams.lightsBufferParam.set(visibleLightData.getLightBuffer());
1332 iblParams.reflectionProbesParam.set(visibleReflProbeData.getProbeBuffer());
1333 };
1334
1335 const auto bindParamsForStandardForward = [&standardForwardBuffers, &visibleLightData, &visibleReflProbeData]
1336 (GpuParams& gpuParams, const Bounds& bounds, const ForwardLightingParams& fwdParams,
1337 const ImageBasedLightingParams& iblParams)
1338 {
1339 // Populate light & probe buffers
1340 Vector3I lightCounts;
1341 const LightData* lights[STANDARD_FORWARD_MAX_NUM_LIGHTS];
1342 visibleLightData.gatherInfluencingLights(bounds, lights, lightCounts);
1343
1344 Vector4I lightOffsets;
1345 lightOffsets.x = lightCounts.x;
1346 lightOffsets.y = lightCounts.x;
1347 lightOffsets.z = lightOffsets.y + lightCounts.y;
1348 lightOffsets.w = lightOffsets.z + lightCounts.z;
1349
1350 for (INT32 j = 0; j < lightOffsets.w; j++)
1351 gLightsParamDef.gLights.set(standardForwardBuffers.lightsParamBlock, *lights[j], j);
1352
1353 INT32 numReflProbes = std::min(visibleReflProbeData.getNumProbes(), STANDARD_FORWARD_MAX_NUM_PROBES);
1354 for (INT32 j = 0; j < numReflProbes; j++)
1355 {
1356 gReflProbesParamDef.gReflectionProbes.set(standardForwardBuffers.reflProbesParamBlock,
1357 visibleReflProbeData.getProbeData(j), j);
1358 }
1359
1360 gLightAndReflProbeParamsParamDef.gLightOffsets.set(standardForwardBuffers.lightAndReflProbeParamsParamBlock,
1361 lightOffsets);
1362 gLightAndReflProbeParamsParamDef.gReflProbeCount.set(standardForwardBuffers.lightAndReflProbeParamsParamBlock,
1363 numReflProbes);
1364
1365 if (iblParams.reflProbesBinding.set != (UINT32)-1)
1366 {
1367 gpuParams.setParamBlockBuffer(
1368 iblParams.reflProbesBinding.set,
1369 iblParams.reflProbesBinding.slot,
1370 standardForwardBuffers.reflProbesParamBlock);
1371 }
1372
1373 if (fwdParams.lightsParamBlockBinding.set != (UINT32)-1)
1374 {
1375 gpuParams.setParamBlockBuffer(
1376 fwdParams.lightsParamBlockBinding.set,
1377 fwdParams.lightsParamBlockBinding.slot,
1378 standardForwardBuffers.lightsParamBlock);
1379 }
1380
1381 if (fwdParams.lightAndReflProbeParamsParamBlockBinding.set != (UINT32)-1)
1382 {
1383 gpuParams.setParamBlockBuffer(
1384 fwdParams.lightAndReflProbeParamsParamBlockBinding.set,
1385 fwdParams.lightAndReflProbeParamsParamBlockBinding.slot,
1386 standardForwardBuffers.lightAndReflProbeParamsParamBlock);
1387 }
1388 };
1389
1390 const auto bindCommonIBLParams = [&reflProbeParamBuffer, &skyFilteredRadiance, &sceneInfo]
1391 (GpuParams& gpuParams, ImageBasedLightingParams& iblParams)
1392 {
1393 // Note: Ideally these should be bound once (they are the same for all renderables)
1394 if (iblParams.reflProbeParamBindings.set != (UINT32)-1)
1395 {
1396 gpuParams.setParamBlockBuffer(
1397 iblParams.reflProbeParamBindings.set,
1398 iblParams.reflProbeParamBindings.slot,
1399 reflProbeParamBuffer.buffer);
1400 }
1401
1402 iblParams.skyReflectionsTexParam.set(skyFilteredRadiance);
1403 iblParams.ambientOcclusionTexParam.set(Texture::WHITE); // Note: Add SSAO here?
1404 iblParams.ssrTexParam.set(Texture::BLACK); // Note: Add SSR here?
1405
1406 iblParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
1407 iblParams.preintegratedEnvBRDFParam.set(RendererTextures::preintegratedEnvGF);
1408 };
1409
1410 // Prepare render target
1411 auto sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
1412 auto sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[2]);
1413 auto resolvedSceneDepthNode = static_cast<RCNodeResolvedSceneDepth*>(inputs.inputNodes[5]);
1414
1415 bool rebuildRT;
1416 if (renderTarget != nullptr)
1417 {
1418 rebuildRT = renderTarget->getColorTexture(0) != sceneColorNode->sceneColorTex->texture;
1419 rebuildRT |= renderTarget->getDepthStencilTexture() != sceneDepthNode->depthTex->texture;
1420 }
1421 else
1422 rebuildRT = true;
1423
1424 if (rebuildRT)
1425 {
1426 RENDER_TEXTURE_DESC rtDesc;
1427 rtDesc.colorSurfaces[0].texture = sceneColorNode->sceneColorTex->texture;
1428 rtDesc.colorSurfaces[0].face = 0;
1429 rtDesc.colorSurfaces[0].numFaces = 1;
1430 rtDesc.colorSurfaces[0].mipLevel = 0;
1431
1432 rtDesc.depthStencilSurface.texture = sceneDepthNode->depthTex->texture;
1433 rtDesc.depthStencilSurface.face = 0;
1434 rtDesc.depthStencilSurface.numFaces = 1;
1435 rtDesc.depthStencilSurface.mipLevel = 0;
1436
1437 renderTarget = RenderTexture::create(rtDesc);
1438 }
1439
1440 // Prepare objects for rendering by binding forward lighting data
1441 //// Normal renderables
1442 const VisibilityInfo& visibility = inputs.view.getVisibilityMasks();
1443 const auto numRenderables = (UINT32)sceneInfo.renderables.size();
1444 for (UINT32 i = 0; i < numRenderables; i++)
1445 {
1446 if (!visibility.renderables[i])
1447 continue;
1448
1449 for (auto& element : sceneInfo.renderables[i]->elements)
1450 {
1451 ShaderFlags shaderFlags = element.material->getShader()->getFlags();
1452
1453 const bool useForwardRendering = shaderFlags.isSet(ShaderFlag::Forward) || shaderFlags.isSet(ShaderFlag::Transparent);
1454 if (!useForwardRendering)
1455 continue;
1456
1457 // Note: It would be nice to be able to set this once and keep it, only updating if the buffers actually
1458 // change (e.g. when growing).
1459 const SPtr<GpuParams> gpuParams = element.params->getGpuParams();
1460 if(supportsClusteredForward)
1461 bindParamsForClustered(*gpuParams, element.forwardLightingParams, element.imageBasedParams);
1462 else
1463 {
1464 // Populate light & probe buffers
1465 const Bounds& bounds = sceneInfo.renderableCullInfos[i].bounds;
1466 bindParamsForStandardForward(*gpuParams, bounds, element.forwardLightingParams, element.imageBasedParams);
1467 }
1468
1469 bindCommonIBLParams(*gpuParams, element.imageBasedParams);
1470 }
1471 }
1472
1473 //// Particle systems
1474 const ParticlePerFrameData* particleData = inputs.frameInfo.perFrameData.particles;
1475 if(particleData)
1476 {
1477 const auto numParticleSystems = (UINT32)inputs.scene.particleSystems.size();
1478
1479 for (UINT32 i = 0; i < numParticleSystems; i++)
1480 {
1481 if (!visibility.particleSystems[i])
1482 continue;
1483
1484 const RendererParticles& rendererParticles = inputs.scene.particleSystems[i];
1485 ParticlesRenderElement& renderElement = rendererParticles.renderElement;
1486
1487 ShaderFlags shaderFlags = renderElement.material->getShader()->getFlags();
1488
1489 if(shaderFlags.isSet(ShaderFlag::Transparent))
1490 renderElement.depthInputTexture.set(resolvedSceneDepthNode->output->texture);
1491
1492 const bool requiresForwardLighting = shaderFlags.isSet(ShaderFlag::Forward);
1493 if (!requiresForwardLighting)
1494 continue;
1495
1496 if(!renderElement.isValid())
1497 continue;
1498
1499 const SPtr<GpuParams> gpuParams = renderElement.params->getGpuParams();
1500
1501 // Note: It would be nice to be able to set this once and keep it, only updating if the buffers actually
1502 // change (e.g. when growing).
1503 if(supportsClusteredForward)
1504 bindParamsForClustered(*gpuParams, renderElement.forwardLightingParams, renderElement.imageBasedParams);
1505 else
1506 {
1507 // Populate light & probe buffers
1508 const Bounds& bounds = sceneInfo.particleSystemCullInfos[i].bounds;
1509 bindParamsForStandardForward(*gpuParams, bounds, renderElement.forwardLightingParams, renderElement.imageBasedParams);
1510 }
1511
1512 bindCommonIBLParams(*gpuParams, renderElement.imageBasedParams);
1513 }
1514 }
1515
1516 // TODO: Forward pipeline rendering doesn't support shadows. In order to support this I'd have to render the light
1517 // occlusion for all lights affecting this object into a single (or a few) textures. I can likely use texture
1518 // arrays for this, or to avoid sampling many textures, perhaps just jam it all in one or few texture channels.
1519
1520 // Render everything
1521 RenderAPI& rapi = RenderAPI::instance();
1522
1523 RenderQueue* opaqueQueue = inputs.view.getOpaqueQueue(true).get();
1524 RenderQueue* transparentQueue = inputs.view.getTransparentQueue().get();
1525
1526 rapi.setRenderTarget(renderTarget, 0, RT_ALL);
1527 renderQueueElements(opaqueQueue->getSortedElements());
1528
1529 rapi.setRenderTarget(renderTarget, FBT_DEPTH, RT_ALL);
1530 renderQueueElements(transparentQueue->getSortedElements());
1531
1532 // Note: Perhaps delay clearing this one frame, so previous frame textures have a better chance of being done
1533 ParticleRenderer::instance().getTexturePool().clear();
1534
1535 // Trigger post-lighting callbacks
1536 Camera* sceneCamera = inputs.view.getSceneCamera();
1537 if (sceneCamera != nullptr)
1538 {
1539 for(auto& extension : inputs.extPostLighting)
1540 {
1541 if (extension->check(*sceneCamera))
1542 extension->render(*sceneCamera);
1543 }
1544 }
1545 }
1546
1547 void RCNodeClusteredForward::clear()
1548 {
1549 // Do nothing
1550 }
1551
1552 SmallVector<StringID, 4> RCNodeClusteredForward::getDependencies(const RendererView& view)
1553 {
1554 return {
1555 RCNodeSceneColor::getNodeId(),
1556 RCNodeSkybox::getNodeId(),
1557 RCNodeSceneDepth::getNodeId(),
1558 RCNodeParticleSimulate::getNodeId(),
1559 RCNodeParticleSort::getNodeId(),
1560 RCNodeResolvedSceneDepth::getNodeId()
1561 };
1562 }
1563
1564 void RCNodeSkybox::render(const RenderCompositorNodeInputs& inputs)
1565 {
1566 Skybox* skybox = nullptr;
1567 if(inputs.view.getRenderSettings().enableSkybox)
1568 skybox = inputs.scene.skybox;
1569
1570 SPtr<Texture> radiance = skybox ? skybox->getTexture() : nullptr;
1571
1572 if (radiance != nullptr)
1573 {
1574 SkyboxMat* material = SkyboxMat::getVariation(false);
1575 material->bind(inputs.view.getPerViewBuffer(), radiance, Color::White);
1576 }
1577 else
1578 {
1579 // Cancel out the linear->SRGB conversion
1580 Color clearColor = PixelUtil::SRGBToLinear(inputs.view.getProperties().target.clearColor);
1581
1582 SkyboxMat* material = SkyboxMat::getVariation(true);
1583 material->bind(inputs.view.getPerViewBuffer(), nullptr, clearColor);
1584 }
1585
1586 RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
1587 int readOnlyFlags = FBT_DEPTH | FBT_STENCIL;
1588
1589 RenderAPI& rapi = RenderAPI::instance();
1590 rapi.setRenderTarget(sceneColorNode->renderTarget, readOnlyFlags, RT_COLOR0 | RT_DEPTH_STENCIL);
1591
1592 Rect2 area(0.0f, 0.0f, 1.0f, 1.0f);
1593 rapi.setViewport(area);
1594
1595 SPtr<Mesh> mesh = gRendererUtility().getSkyBoxMesh();
1596 gRendererUtility().draw(mesh, mesh->getProperties().getSubMesh(0));
1597 }
1598
1599 void RCNodeSkybox::clear()
1600 { }
1601
1602 SmallVector<StringID, 4> RCNodeSkybox::getDependencies(const RendererView& view)
1603 {
1604 SmallVector<StringID, 4> deps;
1605 deps.add(RCNodeSceneColor::getNodeId());
1606 deps.add(RCNodeDeferredIndirectSpecularLighting::getNodeId());
1607
1608 return deps;
1609 }
1610
1611 void RCNodeFinalResolve::render(const RenderCompositorNodeInputs& inputs)
1612 {
1613 const RendererViewProperties& viewProps = inputs.view.getProperties();
1614
1615 SPtr<Texture> input;
1616 if(viewProps.runPostProcessing)
1617 {
1618 RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[0]);
1619
1620 // Note: Ideally the last PP effect could write directly to the final target and we could avoid this copy
1621 input = postProcessNode->getLastOutput();
1622 }
1623 else
1624 {
1625 RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
1626 input = sceneColorNode->sceneColorTex->texture;
1627 }
1628
1629 SPtr<RenderTarget> target = viewProps.target.target;
1630
1631 RenderAPI& rapi = RenderAPI::instance();
1632 rapi.setRenderTarget(target);
1633 rapi.setViewport(viewProps.target.nrmViewRect);
1634
1635 gRendererUtility().blit(input, Rect2I::EMPTY, viewProps.flipView);
1636
1637 if(viewProps.encodeDepth)
1638 {
1639 RCNodeResolvedSceneDepth* resolvedSceneDepthNode = static_cast<RCNodeResolvedSceneDepth*>(inputs.inputNodes[0]);
1640
1641 EncodeDepthMat* encodeDepthMat = EncodeDepthMat::get();
1642 encodeDepthMat->execute(resolvedSceneDepthNode->output->texture, viewProps.depthEncodeNear,
1643 viewProps.depthEncodeFar, target);
1644 }
1645
1646 // Trigger overlay callbacks
1647 Camera* sceneCamera = inputs.view.getSceneCamera();
1648 if (sceneCamera != nullptr)
1649 {
1650 for(auto& extension : inputs.extOverlay)
1651 {
1652 if (extension->check(*sceneCamera))
1653 extension->render(*sceneCamera);
1654 }
1655 }
1656 }
1657
1658 void RCNodeFinalResolve::clear()
1659 { }
1660
1661 SmallVector<StringID, 4> RCNodeFinalResolve::getDependencies(const RendererView& view)
1662 {
1663 const RendererViewProperties& viewProps = view.getProperties();
1664
1665 SmallVector<StringID, 4> deps;
1666 if(viewProps.runPostProcessing)
1667 {
1668 deps.add(RCNodePostProcess::getNodeId());
1669 deps.add(RCNodeFXAA::getNodeId());
1670 }
1671 else
1672 {
1673 deps.add(RCNodeSceneColor::getNodeId());
1674 deps.add(RCNodeClusteredForward::getNodeId());
1675 }
1676
1677 if(viewProps.encodeDepth)
1678 deps.add(RCNodeResolvedSceneDepth::getNodeId());
1679
1680 return deps;
1681 }
1682
1683 RCNodePostProcess::RCNodePostProcess()
1684 :mOutput(), mAllocated()
1685 { }
1686
1687 void RCNodePostProcess::getAndSwitch(const RendererView& view, SPtr<RenderTexture>& output, SPtr<Texture>& lastFrame) const
1688 {
1689 GpuResourcePool& resPool = GpuResourcePool::instance();
1690
1691 const RendererViewProperties& viewProps = view.getProperties();
1692 UINT32 width = viewProps.target.viewRect.width;
1693 UINT32 height = viewProps.target.viewRect.height;
1694
1695 if(!mAllocated[mCurrentIdx])
1696 {
1697 mOutput[mCurrentIdx] = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA8, width, height,
1698 TU_RENDERTARGET, 1, false));
1699
1700 mAllocated[mCurrentIdx] = true;
1701 }
1702
1703 output = mOutput[mCurrentIdx]->renderTexture;
1704
1705 UINT32 otherIdx = (mCurrentIdx + 1) % 2;
1706 if (mAllocated[otherIdx])
1707 lastFrame = mOutput[otherIdx]->texture;
1708
1709 mCurrentIdx = otherIdx;
1710 }
1711
1712 SPtr<Texture> RCNodePostProcess::getLastOutput() const
1713 {
1714 UINT32 otherIdx = (mCurrentIdx + 1) % 2;
1715 if (mAllocated[otherIdx])
1716 return mOutput[otherIdx]->texture;
1717
1718 return nullptr;
1719 }
1720
1721 void RCNodePostProcess::render(const RenderCompositorNodeInputs& inputs)
1722 {
1723 // Do nothing, this is just a helper node
1724 }
1725
1726 void RCNodePostProcess::clear()
1727 {
1728 GpuResourcePool& resPool = GpuResourcePool::instance();
1729
1730 if (mAllocated[0])
1731 resPool.release(mOutput[0]);
1732
1733 if (mAllocated[1])
1734 resPool.release(mOutput[1]);
1735
1736 mAllocated[0] = false;
1737 mAllocated[1] = false;
1738 mCurrentIdx = 0;
1739 }
1740
1741 SmallVector<StringID, 4> RCNodePostProcess::getDependencies(const RendererView& view)
1742 {
1743 return {};
1744 }
1745
1746 RCNodeEyeAdaptation::~RCNodeEyeAdaptation()
1747 {
1748 GpuResourcePool& resPool = GpuResourcePool::instance();
1749
1750 if (previous)
1751 resPool.release(previous);
1752 }
1753
1754 void RCNodeEyeAdaptation::render(const RenderCompositorNodeInputs& inputs)
1755 {
1756 GpuResourcePool& resPool = GpuResourcePool::instance();
1757
1758 const RenderSettings& settings = inputs.view.getRenderSettings();
1759
1760 const bool hdr = settings.enableHDR;
1761
1762 if(hdr && settings.enableAutoExposure)
1763 {
1764 // Get downsample scene
1765 auto* halfSceneColorNode = static_cast<RCNodeHalfSceneColor*>(inputs.inputNodes[1]);
1766 const SPtr<PooledRenderTexture>& downsampledScene = halfSceneColorNode->output;
1767
1768 if(useHistogramEyeAdapatation(inputs))
1769 {
1770 // Generate histogram
1771 SPtr<PooledRenderTexture> eyeAdaptHistogram =
1772 resPool.get(EyeAdaptHistogramMat::getOutputDesc(downsampledScene->texture));
1773 EyeAdaptHistogramMat* eyeAdaptHistogramMat = EyeAdaptHistogramMat::get();
1774 eyeAdaptHistogramMat->execute(downsampledScene->texture, eyeAdaptHistogram->texture, settings.autoExposure);
1775
1776 // Reduce histogram
1777 SPtr<PooledRenderTexture> reducedHistogram = resPool.get(EyeAdaptHistogramReduceMat::getOutputDesc());
1778
1779 SPtr<Texture> prevFrameEyeAdaptation;
1780 if (previous != nullptr)
1781 prevFrameEyeAdaptation = previous->texture;
1782
1783 EyeAdaptHistogramReduceMat* eyeAdaptHistogramReduce = EyeAdaptHistogramReduceMat::get();
1784 eyeAdaptHistogramReduce->execute(
1785 downsampledScene->texture,
1786 eyeAdaptHistogram->texture,
1787 prevFrameEyeAdaptation,
1788 reducedHistogram->renderTexture);
1789
1790 resPool.release(eyeAdaptHistogram);
1791 eyeAdaptHistogram = nullptr;
1792
1793 // Generate eye adaptation value
1794 output = resPool.get(EyeAdaptationMat::getOutputDesc());
1795 EyeAdaptationMat* eyeAdaptationMat = EyeAdaptationMat::get();
1796 eyeAdaptationMat->execute(
1797 reducedHistogram->texture,
1798 output->renderTexture,
1799 inputs.frameInfo.timeDelta,
1800 settings.autoExposure,
1801 settings.exposureScale);
1802
1803 resPool.release(reducedHistogram);
1804 }
1805 else
1806 {
1807 // Populate alpha values of the downsampled texture with luminance
1808 SPtr<PooledRenderTexture> luminanceTex =
1809 resPool.get(EyeAdaptationBasicSetupMat::getOutputDesc(downsampledScene->texture));
1810
1811 EyeAdaptationBasicSetupMat* setupMat = EyeAdaptationBasicSetupMat::get();
1812 setupMat->execute(
1813 downsampledScene->texture,
1814 luminanceTex->renderTexture,
1815 inputs.frameInfo.timeDelta,
1816 settings.autoExposure,
1817 settings.exposureScale);
1818
1819 SPtr<Texture> downsampleInput = luminanceTex->texture;
1820 luminanceTex = nullptr;
1821
1822 // Downsample some more
1823 for(UINT32 i = 0; i < 5; i++)
1824 {
1825 DownsampleMat* downsampleMat = DownsampleMat::getVariation(1, false);
1826 SPtr<PooledRenderTexture> downsampledLuminance =
1827 resPool.get(DownsampleMat::getOutputDesc(downsampleInput));
1828
1829 downsampleMat->execute(downsampleInput, downsampledLuminance->renderTexture);
1830 downsampleInput = downsampledLuminance->texture;
1831 }
1832
1833 // Generate eye adaptation value
1834 EyeAdaptationBasicMat* eyeAdaptationMat = EyeAdaptationBasicMat::get();
1835
1836 SPtr<Texture> prevFrameEyeAdaptation;
1837 if (previous != nullptr)
1838 prevFrameEyeAdaptation = previous->texture;
1839
1840 output = resPool.get(EyeAdaptationBasicMat::getOutputDesc());
1841 eyeAdaptationMat->execute(
1842 downsampleInput,
1843 prevFrameEyeAdaptation,
1844 output->renderTexture,
1845 inputs.frameInfo.timeDelta,
1846 settings.autoExposure,
1847 settings.exposureScale);
1848 }
1849 }
1850 else
1851 {
1852 if(previous)
1853 resPool.release(previous);
1854
1855 previous = nullptr;
1856 output = nullptr;
1857 }
1858 }
1859
1860 void RCNodeEyeAdaptation::clear()
1861 {
1862 GpuResourcePool& resPool = GpuResourcePool::instance();
1863
1864 // Save eye adaptation for next frame
1865 if(previous)
1866 resPool.release(previous);
1867
1868 std::swap(output, previous);
1869 }
1870
1871 bool RCNodeEyeAdaptation::useHistogramEyeAdapatation(const RenderCompositorNodeInputs& inputs)
1872 {
1873 return inputs.featureSet == RenderBeastFeatureSet::Desktop;
1874 }
1875
1876 SmallVector<StringID, 4> RCNodeEyeAdaptation::getDependencies(const RendererView& view)
1877 {
1878 SmallVector<StringID, 4> deps;
1879 deps.add(RCNodeClusteredForward::getNodeId());
1880
1881 const RenderSettings& settings = view.getRenderSettings();
1882 if(settings.enableHDR && settings.enableAutoExposure)
1883 deps.add(RCNodeHalfSceneColor::getNodeId());
1884
1885 return deps;
1886 }
1887
1888 RCNodeTonemapping::~RCNodeTonemapping()
1889 {
1890 GpuResourcePool& resPool = GpuResourcePool::instance();
1891
1892 if (mTonemapLUT)
1893 resPool.release(mTonemapLUT);
1894 }
1895
1896 void RCNodeTonemapping::render(const RenderCompositorNodeInputs& inputs)
1897 {
1898 GpuResourcePool& resPool = GpuResourcePool::instance();
1899
1900 const RendererViewProperties& viewProps = inputs.view.getProperties();
1901 const RenderSettings& settings = inputs.view.getRenderSettings();
1902
1903 auto* eyeAdaptationNode = static_cast<RCNodeEyeAdaptation*>(inputs.inputNodes[0]);
1904 auto* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[1]);
1905 auto* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[3]);
1906 const SPtr<Texture>& sceneColor = sceneColorNode->sceneColorTex->texture;
1907
1908 const bool hdr = settings.enableHDR;
1909 const bool msaa = viewProps.target.numSamples > 1;
1910
1911 const bool volumeLUT = inputs.featureSet == RenderBeastFeatureSet::Desktop;
1912 bool gammaOnly;
1913 bool autoExposure;
1914 if (hdr)
1915 {
1916 if (settings.enableTonemapping)
1917 {
1918 const UINT64 latestHash = inputs.view.getRenderSettingsHash();
1919 const bool tonemapLUTDirty = mTonemapLastUpdateHash != latestHash;
1920
1921 if (tonemapLUTDirty) // Rebuild LUT if PP settings changed
1922 {
1923 CreateTonemapLUTMat* createLUT = CreateTonemapLUTMat::getVariation(volumeLUT);
1924 if(mTonemapLUT == nullptr)
1925 mTonemapLUT = resPool.get(createLUT->getOutputDesc());
1926
1927 if(volumeLUT)
1928 createLUT->execute3D(mTonemapLUT->texture, settings);
1929 else
1930 createLUT->execute2D(mTonemapLUT->renderTexture, settings);
1931
1932 mTonemapLastUpdateHash = latestHash;
1933 }
1934
1935 gammaOnly = false;
1936 }
1937 else
1938 gammaOnly = true;
1939
1940 autoExposure = settings.enableAutoExposure;
1941 }
1942 else
1943 {
1944 gammaOnly = true;
1945 autoExposure = false;
1946 }
1947
1948 if(gammaOnly)
1949 {
1950 if(mTonemapLUT)
1951 {
1952 resPool.release(mTonemapLUT);
1953 mTonemapLUT = nullptr;
1954 }
1955 }
1956
1957 TonemappingMat* tonemapping = TonemappingMat::getVariation(volumeLUT, gammaOnly, autoExposure, msaa);
1958
1959 SPtr<RenderTexture> ppOutput;
1960 SPtr<Texture> ppLastFrame;
1961 postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
1962
1963 SPtr<Texture> eyeAdaptationTex;
1964 if (eyeAdaptationNode->output)
1965 eyeAdaptationTex = eyeAdaptationNode->output->texture;
1966
1967 SPtr<Texture> tonemapLUTTex;
1968 if (mTonemapLUT)
1969 tonemapLUTTex = mTonemapLUT->texture;
1970
1971 SPtr<Texture> bloomTex;
1972 if(settings.bloom.enabled)
1973 {
1974 auto* bloomNode = static_cast<RCNodeBloom*>(inputs.inputNodes[5]);
1975 bloomTex = bloomNode->output;
1976 }
1977
1978 tonemapping->execute(sceneColor, eyeAdaptationTex, bloomTex, tonemapLUTTex, ppOutput, settings);
1979 }
1980
1981 void RCNodeTonemapping::clear()
1982 {
1983 // Do nothing
1984 }
1985
1986 SmallVector<StringID, 4> RCNodeTonemapping::getDependencies(const RendererView& view)
1987 {
1988 SmallVector<StringID, 4> deps = {
1989 RCNodeEyeAdaptation::getNodeId(),
1990 RCNodeSceneColor::getNodeId(),
1991 RCNodeClusteredForward::getNodeId(),
1992 RCNodePostProcess::getNodeId(),
1993 RCNodeHalfSceneColor::getNodeId()
1994 };
1995
1996 if(view.getRenderSettings().bloom.enabled)
1997 deps.add(RCNodeBloom::getNodeId());
1998
1999 return deps;
2000 }
2001
2002 void RCNodeGaussianDOF::render(const RenderCompositorNodeInputs& inputs)
2003 {
2004 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[1]);
2005 RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[2]);
2006
2007 const DepthOfFieldSettings& settings = inputs.view.getRenderSettings().depthOfField;
2008 bool near = settings.nearBlurAmount > 0.0f;
2009 bool far = settings.farBlurAmount > 0.0f;
2010
2011 bool enabled = settings.enabled && (near || far);
2012 if(!enabled)
2013 return;
2014
2015 GaussianDOFSeparateMat* separateMat = GaussianDOFSeparateMat::getVariation(near, far);
2016 GaussianDOFCombineMat* combineMat = GaussianDOFCombineMat::getVariation(near, far);
2017 GaussianBlurMat* blurMat = GaussianBlurMat::get();
2018
2019 SPtr<RenderTexture> ppOutput;
2020 SPtr<Texture> ppLastFrame;
2021 postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
2022
2023 separateMat->execute(ppLastFrame, sceneDepthNode->depthTex->texture, inputs.view, settings);
2024
2025 SPtr<PooledRenderTexture> nearTex, farTex;
2026 if(near && far)
2027 {
2028 nearTex = separateMat->getOutput(0);
2029 farTex = separateMat->getOutput(1);
2030 }
2031 else
2032 {
2033 if (near)
2034 nearTex = separateMat->getOutput(0);
2035 else
2036 farTex = separateMat->getOutput(0);
2037 }
2038
2039 // Blur the out of focus pixels
2040 // Note: Perhaps set up stencil so I can avoid performing blur on unused parts of the textures?
2041 const TextureProperties& texProps = nearTex ? nearTex->texture->getProperties() : farTex->texture->getProperties();
2042 POOLED_RENDER_TEXTURE_DESC tempTexDesc = POOLED_RENDER_TEXTURE_DESC::create2D(texProps.getFormat(),
2043 texProps.getWidth(), texProps.getHeight(), TU_RENDERTARGET);
2044 SPtr<PooledRenderTexture> tempTexture = GpuResourcePool::instance().get(tempTexDesc);
2045
2046 SPtr<Texture> blurredNearTex;
2047 if(nearTex)
2048 {
2049 blurMat->execute(nearTex->texture, settings.nearBlurAmount, tempTexture->renderTexture);
2050 blurredNearTex = tempTexture->texture;
2051 }
2052
2053 SPtr<Texture> blurredFarTex;
2054 if(farTex)
2055 {
2056 // If temporary texture is used up, re-use the original near texture for the blurred result
2057 if(blurredNearTex)
2058 {
2059 blurMat->execute(farTex->texture, settings.farBlurAmount, nearTex->renderTexture);
2060 blurredFarTex = nearTex->texture;
2061 }
2062 else // Otherwise just use the temporary
2063 {
2064 blurMat->execute(farTex->texture, settings.farBlurAmount, tempTexture->renderTexture);
2065 blurredFarTex = tempTexture->texture;
2066 }
2067 }
2068
2069 combineMat->execute(ppLastFrame, blurredNearTex, blurredFarTex,
2070 sceneDepthNode->depthTex->texture, ppOutput, inputs.view, settings);
2071
2072 separateMat->release();
2073 GpuResourcePool::instance().release(tempTexture);
2074 }
2075
2076 void RCNodeGaussianDOF::clear()
2077 {
2078 // Do nothing
2079 }
2080
2081 SmallVector<StringID, 4> RCNodeGaussianDOF::getDependencies(const RendererView& view)
2082 {
2083 return { RCNodeTonemapping::getNodeId(), RCNodeSceneDepth::getNodeId(), RCNodePostProcess::getNodeId() };
2084 }
2085
2086 void RCNodeFXAA::render(const RenderCompositorNodeInputs& inputs)
2087 {
2088 const RenderSettings& settings = inputs.view.getRenderSettings();
2089 if (!settings.enableFXAA)
2090 return;
2091
2092 RCNodePostProcess* postProcessNode = static_cast<RCNodePostProcess*>(inputs.inputNodes[1]);
2093
2094 SPtr<RenderTexture> ppOutput;
2095 SPtr<Texture> ppLastFrame;
2096 postProcessNode->getAndSwitch(inputs.view, ppOutput, ppLastFrame);
2097
2098 // Note: I could skip executing FXAA over DOF and motion blurred pixels
2099 FXAAMat* fxaa = FXAAMat::get();
2100 fxaa->execute(ppLastFrame, ppOutput);
2101 }
2102
2103 void RCNodeFXAA::clear()
2104 {
2105 // Do nothing
2106 }
2107
2108 SmallVector<StringID, 4> RCNodeFXAA::getDependencies(const RendererView& view)
2109 {
2110 return { RCNodeGaussianDOF::getNodeId(), RCNodePostProcess::getNodeId() };
2111 }
2112
2113 void RCNodeHalfSceneColor::render(const RenderCompositorNodeInputs& inputs)
2114 {
2115 const RendererViewProperties& viewProps = inputs.view.getProperties();
2116
2117 auto* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
2118 const SPtr<Texture>& input = sceneColorNode->sceneColorTex->texture;
2119
2120 // Downsample scene
2121 const bool msaa = viewProps.target.numSamples > 1;
2122 DownsampleMat* downsampleMat = DownsampleMat::getVariation(1, msaa);
2123
2124 GpuResourcePool& resPool = GpuResourcePool::instance();
2125 output = resPool.get(DownsampleMat::getOutputDesc(input));
2126
2127 downsampleMat->execute(input, output->renderTexture);
2128 }
2129
2130 void RCNodeHalfSceneColor::clear()
2131 {
2132 GpuResourcePool& resPool = GpuResourcePool::instance();
2133 resPool.release(output);
2134 }
2135
2136 SmallVector<StringID, 4> RCNodeHalfSceneColor::getDependencies(const RendererView& view)
2137 {
2138 return { RCNodeSceneColor::getNodeId() };
2139 }
2140
2141 void RCNodeResolvedSceneDepth::render(const RenderCompositorNodeInputs& inputs)
2142 {
2143 GpuResourcePool& resPool = GpuResourcePool::instance();
2144 const RendererViewProperties& viewProps = inputs.view.getProperties();
2145 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
2146
2147 if (viewProps.target.numSamples > 1)
2148 {
2149 UINT32 width = viewProps.target.viewRect.width;
2150 UINT32 height = viewProps.target.viewRect.height;
2151
2152 output = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_D32_S8X24, width, height,
2153 TU_DEPTHSTENCIL, 1, false));
2154
2155 RenderAPI& rapi = RenderAPI::instance();
2156 rapi.setRenderTarget(output->renderTexture);
2157 rapi.clearRenderTarget(FBT_STENCIL);
2158 gRendererUtility().blit(sceneDepthNode->depthTex->texture, Rect2I::EMPTY, false, true);
2159
2160 mPassThrough = false;
2161 }
2162 else
2163 {
2164 output = sceneDepthNode->depthTex;
2165 mPassThrough = true;
2166 }
2167 }
2168
2169 void RCNodeResolvedSceneDepth::clear()
2170 {
2171 GpuResourcePool& resPool = GpuResourcePool::instance();
2172
2173 if (!mPassThrough)
2174 resPool.release(output);
2175 else
2176 output = nullptr;
2177
2178 mPassThrough = false;
2179 }
2180
2181 SmallVector<StringID, 4> RCNodeResolvedSceneDepth::getDependencies(const RendererView& view)
2182 {
2183 // GBuffer require because it renders the base pass (populates the depth buffer)
2184 return { RCNodeSceneDepth::getNodeId(), RCNodeBasePass::getNodeId() };
2185 }
2186
2187 void RCNodeHiZ::render(const RenderCompositorNodeInputs& inputs)
2188 {
2189 GpuResourcePool& resPool = GpuResourcePool::instance();
2190 const RendererViewProperties& viewProps = inputs.view.getProperties();
2191
2192 RCNodeResolvedSceneDepth* resolvedSceneDepth = static_cast<RCNodeResolvedSceneDepth*>(inputs.inputNodes[0]);
2193
2194 UINT32 width = viewProps.target.viewRect.width;
2195 UINT32 height = viewProps.target.viewRect.height;
2196
2197 UINT32 size = Bitwise::nextPow2(std::max(width, height));
2198 UINT32 numMips = PixelUtil::getMaxMipmaps(size, size, 1, PF_R32F);
2199 size = 1 << numMips;
2200
2201 // Note: Use the 32-bit buffer here as 16-bit causes too much banding (most of the scene gets assigned 4-5 different
2202 // depth values).
2203 // - When I add UNORM 16-bit format I should be able to switch to that
2204 output = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R32F, size, size, TU_RENDERTARGET, 1, false, 1,
2205 numMips));
2206
2207 Rect2 srcRect = viewProps.target.nrmViewRect;
2208
2209 // If viewport size is odd, adjust UV
2210 srcRect.width += (viewProps.target.viewRect.width % 2) * (1.0f / viewProps.target.viewRect.width);
2211 srcRect.height += (viewProps.target.viewRect.height % 2) * (1.0f / viewProps.target.viewRect.height);
2212
2213 bool noTextureViews = !gCaps().hasCapability(RSC_TEXTURE_VIEWS);
2214
2215 BuildHiZMat* material = BuildHiZMat::getVariation(noTextureViews);
2216
2217 // Generate first mip
2218 RENDER_TEXTURE_DESC rtDesc;
2219 rtDesc.colorSurfaces[0].texture = output->texture;
2220 rtDesc.colorSurfaces[0].mipLevel = 0;
2221
2222 SPtr<RenderTexture> rt = RenderTexture::create(rtDesc);
2223
2224 Rect2 destRect;
2225 bool downsampledFirstMip = false; // Not used currently
2226 if (downsampledFirstMip)
2227 {
2228 // Make sure that 1 pixel in HiZ maps to a 2x2 block in source
2229 destRect = Rect2(0, 0,
2230 Math::ceilToInt(viewProps.target.viewRect.width / 2.0f) / (float)size,
2231 Math::ceilToInt(viewProps.target.viewRect.height / 2.0f) / (float)size);
2232
2233 material->execute(resolvedSceneDepth->output->texture, 0, srcRect, destRect, rt);
2234 }
2235 else // First level is just a copy of the depth buffer
2236 {
2237 destRect = Rect2(0, 0,
2238 viewProps.target.viewRect.width / (float)size,
2239 viewProps.target.viewRect.height / (float)size);
2240
2241 RenderAPI& rapi = RenderAPI::instance();
2242 rapi.setRenderTarget(rt);
2243 rapi.setViewport(destRect);
2244
2245 Rect2I srcAreaInt;
2246 srcAreaInt.x = (INT32)(srcRect.x * viewProps.target.viewRect.width);
2247 srcAreaInt.y = (INT32)(srcRect.y * viewProps.target.viewRect.height);
2248 srcAreaInt.width = (UINT32)(srcRect.width * viewProps.target.viewRect.width);
2249 srcAreaInt.height = (UINT32)(srcRect.height * viewProps.target.viewRect.height);
2250
2251 gRendererUtility().blit(resolvedSceneDepth->output->texture, srcAreaInt);
2252 rapi.setViewport(Rect2(0, 0, 1, 1));
2253 }
2254
2255 // Generate remaining mip levels
2256 for(UINT32 i = 1; i <= numMips; i++)
2257 {
2258 rtDesc.colorSurfaces[0].mipLevel = i;
2259 rt = RenderTexture::create(rtDesc);
2260
2261 material->execute(output->texture, i - 1, destRect, destRect, rt);
2262 }
2263 }
2264
2265 void RCNodeHiZ::clear()
2266 {
2267 GpuResourcePool& resPool = GpuResourcePool::instance();
2268 resPool.release(output);
2269 }
2270
2271 SmallVector<StringID, 4> RCNodeHiZ::getDependencies(const RendererView& view)
2272 {
2273 // Note: This doesn't actually use any gbuffer textures, but node is a dependency because it renders to the depth
2274 // buffer. In order to avoid keeping gbuffer textures alive I could separate out the base pass into its own node
2275 // perhaps. But at the moment it doesn't matter, as anything using HiZ also needs gbuffer.
2276 return { RCNodeResolvedSceneDepth::getNodeId(), RCNodeBasePass::getNodeId() };
2277 }
2278
2279 void RCNodeSSAO::render(const RenderCompositorNodeInputs& inputs)
2280 {
2281 /** Maximum valid depth range within samples in a sample set. In meters. */
2282 static const float DEPTH_RANGE = 1.0f;
2283
2284 const AmbientOcclusionSettings& settings = inputs.view.getRenderSettings().ambientOcclusion;
2285 if(!settings.enabled)
2286 {
2287 output = Texture::WHITE;
2288 mPooledOutput = nullptr;
2289 return;
2290 }
2291
2292 GpuResourcePool& resPool = GpuResourcePool::instance();
2293 const RendererViewProperties& viewProps = inputs.view.getProperties();
2294
2295 RCNodeResolvedSceneDepth* resolvedDepthNode = static_cast<RCNodeResolvedSceneDepth*>(inputs.inputNodes[0]);
2296 RCNodeBasePass* gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[1]);
2297
2298 SPtr<Texture> sceneDepth = resolvedDepthNode->output->texture;
2299 SPtr<Texture> sceneNormals = gbufferNode->normalTex->texture;
2300
2301 const TextureProperties& normalsProps = sceneNormals->getProperties();
2302 SPtr<PooledRenderTexture> resolvedNormals;
2303
2304 RenderAPI& rapi = RenderAPI::instance();
2305 if(sceneNormals->getProperties().getNumSamples() > 1)
2306 {
2307 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(normalsProps.getFormat(),
2308 normalsProps.getWidth(), normalsProps.getHeight(), TU_RENDERTARGET);
2309 resolvedNormals = resPool.get(desc);
2310
2311 rapi.setRenderTarget(resolvedNormals->renderTexture);
2312 gRendererUtility().blit(sceneNormals);
2313
2314 sceneNormals = resolvedNormals->texture;
2315 }
2316
2317 // Multiple downsampled AO levels are used to minimize cache trashing. Downsampled AO targets use larger radius,
2318 // whose contents are then blended with the higher level.
2319 UINT32 quality = settings.quality;
2320 UINT32 numDownsampleLevels = 0;
2321 if (quality == 2)
2322 numDownsampleLevels = 1;
2323 else if (quality > 2)
2324 numDownsampleLevels = 2;
2325
2326 SSAODownsampleMat* downsample = SSAODownsampleMat::get();
2327
2328 SPtr<PooledRenderTexture> setupTex0;
2329 if(numDownsampleLevels > 0)
2330 {
2331 Vector2I downsampledSize(
2332 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.width, 2)),
2333 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.height, 2))
2334 );
2335
2336 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, downsampledSize.x,
2337 downsampledSize.y, TU_RENDERTARGET);
2338 setupTex0 = GpuResourcePool::instance().get(desc);
2339
2340 downsample->execute(inputs.view, sceneDepth, sceneNormals, setupTex0->renderTexture, DEPTH_RANGE);
2341 }
2342
2343 SPtr<PooledRenderTexture> setupTex1;
2344 if(numDownsampleLevels > 1)
2345 {
2346 Vector2I downsampledSize(
2347 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.width, 4)),
2348 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.height, 4))
2349 );
2350
2351 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, downsampledSize.x,
2352 downsampledSize.y, TU_RENDERTARGET);
2353 setupTex1 = GpuResourcePool::instance().get(desc);
2354
2355 downsample->execute(inputs.view, sceneDepth, sceneNormals, setupTex1->renderTexture, DEPTH_RANGE);
2356 }
2357
2358 SSAOTextureInputs textures;
2359 textures.sceneDepth = sceneDepth;
2360 textures.sceneNormals = sceneNormals;
2361 textures.randomRotations = RendererTextures::ssaoRandomization4x4;
2362
2363 SPtr<PooledRenderTexture> downAOTex1;
2364 if(numDownsampleLevels > 1)
2365 {
2366 textures.aoSetup = setupTex1->texture;
2367
2368 Vector2I downsampledSize(
2369 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.width, 4)),
2370 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.height, 4))
2371 );
2372
2373 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, downsampledSize.x,
2374 downsampledSize.y, TU_RENDERTARGET);
2375 downAOTex1 = GpuResourcePool::instance().get(desc);
2376
2377 SSAOMat* ssaoMat = SSAOMat::getVariation(false, false, quality);
2378 ssaoMat->execute(inputs.view, textures, downAOTex1->renderTexture, settings);
2379
2380 GpuResourcePool::instance().release(setupTex1);
2381 setupTex1 = nullptr;
2382 }
2383
2384 SPtr<PooledRenderTexture> downAOTex0;
2385 if(numDownsampleLevels > 0)
2386 {
2387 textures.aoSetup = setupTex0->texture;
2388
2389 if(downAOTex1)
2390 textures.aoDownsampled = downAOTex1->texture;
2391
2392 Vector2I downsampledSize(
2393 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.width, 2)),
2394 std::max(1, Math::divideAndRoundUp((INT32)viewProps.target.viewRect.height, 2))
2395 );
2396
2397 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, downsampledSize.x,
2398 downsampledSize.y, TU_RENDERTARGET);
2399 downAOTex0 = GpuResourcePool::instance().get(desc);
2400
2401 bool upsample = numDownsampleLevels > 1;
2402 SSAOMat* ssaoMat = SSAOMat::getVariation(upsample, false, quality);
2403 ssaoMat->execute(inputs.view, textures, downAOTex0->renderTexture, settings);
2404
2405 if(upsample)
2406 {
2407 GpuResourcePool::instance().release(downAOTex1);
2408 downAOTex1 = nullptr;
2409 }
2410 }
2411
2412 UINT32 width = viewProps.target.viewRect.width;
2413 UINT32 height = viewProps.target.viewRect.height;
2414 mPooledOutput = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, width, height, TU_RENDERTARGET));
2415
2416 {
2417 if(setupTex0)
2418 textures.aoSetup = setupTex0->texture;
2419
2420 if(downAOTex0)
2421 textures.aoDownsampled = downAOTex0->texture;
2422
2423 bool upsample = numDownsampleLevels > 0;
2424 SSAOMat* ssaoMat = SSAOMat::getVariation(upsample, true, quality);
2425 ssaoMat->execute(inputs.view, textures, mPooledOutput->renderTexture, settings);
2426 }
2427
2428 if(resolvedNormals)
2429 {
2430 GpuResourcePool::instance().release(resolvedNormals);
2431 resolvedNormals = nullptr;
2432 }
2433
2434 if(numDownsampleLevels > 0)
2435 {
2436 GpuResourcePool::instance().release(setupTex0);
2437 GpuResourcePool::instance().release(downAOTex0);
2438 }
2439
2440 // Blur the output
2441 // Note: If I implement temporal AA then this can probably be avoided. I can instead jitter the sample offsets
2442 // each frame, and averaging them out should yield blurred AO.
2443 if(quality > 1) // On level 0 we don't blur at all, on level 1 we use the ad-hoc blur in shader
2444 {
2445 const RenderTargetProperties& rtProps = mPooledOutput->renderTexture->getProperties();
2446
2447 POOLED_RENDER_TEXTURE_DESC desc = POOLED_RENDER_TEXTURE_DESC::create2D(PF_R8, rtProps.width,
2448 rtProps.height, TU_RENDERTARGET);
2449 SPtr<PooledRenderTexture> blurIntermediateTex = GpuResourcePool::instance().get(desc);
2450
2451 SSAOBlurMat* blurHorz = SSAOBlurMat::getVariation(true);
2452 SSAOBlurMat* blurVert = SSAOBlurMat::getVariation(false);
2453
2454 blurHorz->execute(inputs.view, mPooledOutput->texture, sceneDepth, blurIntermediateTex->renderTexture, DEPTH_RANGE);
2455 blurVert->execute(inputs.view, blurIntermediateTex->texture, sceneDepth, mPooledOutput->renderTexture, DEPTH_RANGE);
2456
2457 GpuResourcePool::instance().release(blurIntermediateTex);
2458 }
2459
2460 RenderAPI::instance().setRenderTarget(nullptr);
2461 output = mPooledOutput->texture;
2462 }
2463
2464 void RCNodeSSAO::clear()
2465 {
2466 if(mPooledOutput)
2467 {
2468 GpuResourcePool& resPool = GpuResourcePool::instance();
2469 resPool.release(mPooledOutput);
2470 }
2471
2472 output = nullptr;
2473 }
2474
2475 SmallVector<StringID, 4> RCNodeSSAO::getDependencies(const RendererView& view)
2476 {
2477 return { RCNodeResolvedSceneDepth::getNodeId(), RCNodeBasePass::getNodeId() };
2478 }
2479
2480 RCNodeSSR::~RCNodeSSR()
2481 {
2482 deallocOutputs();
2483 }
2484
2485 void RCNodeSSR::render(const RenderCompositorNodeInputs& inputs)
2486 {
2487 const ScreenSpaceReflectionsSettings& settings = inputs.view.getRenderSettings().screenSpaceReflections;
2488 if (!settings.enabled)
2489 {
2490 deallocOutputs();
2491
2492 mPooledOutput = nullptr;
2493 output = Texture::BLACK;
2494 return;
2495 }
2496
2497 RenderAPI& rapi = RenderAPI::instance();
2498
2499 RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
2500 RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[1]);
2501 RCNodeBasePass* gbufferNode = static_cast<RCNodeBasePass*>(inputs.inputNodes[2]);
2502 RCNodeHiZ* hiZNode = static_cast<RCNodeHiZ*>(inputs.inputNodes[3]);
2503 RCNodeResolvedSceneDepth* resolvedSceneDepthNode = static_cast<RCNodeResolvedSceneDepth*>(inputs.inputNodes[4]);
2504
2505 GpuResourcePool& resPool = GpuResourcePool::instance();
2506 const RendererViewProperties& viewProps = inputs.view.getProperties();
2507
2508 UINT32 width = viewProps.target.viewRect.width;
2509 UINT32 height = viewProps.target.viewRect.height;
2510
2511 SPtr<Texture> hiZ = hiZNode->output->texture;
2512
2513 // This will be executing before scene color is resolved, so get the light accum buffer instead
2514 SPtr<Texture> sceneColor = lightAccumNode->lightAccumulationTex->texture;
2515
2516 // Resolve multiple samples if MSAA is used
2517 SPtr<PooledRenderTexture> resolvedSceneColor;
2518 if (viewProps.target.numSamples > 1)
2519 {
2520 resolvedSceneColor = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height,
2521 TU_RENDERTARGET));
2522
2523 rapi.setRenderTarget(resolvedSceneColor->renderTexture);
2524 gRendererUtility().blit(sceneColor);
2525
2526 sceneColor = resolvedSceneColor->texture;
2527 }
2528
2529 GBufferTextures gbuffer;
2530 gbuffer.albedo = gbufferNode->albedoTex->texture;
2531 gbuffer.normals = gbufferNode->normalTex->texture;
2532 gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
2533 gbuffer.depth = sceneDepthNode->depthTex->texture;
2534
2535 SSRStencilMat* stencilMat = SSRStencilMat::getVariation(viewProps.target.numSamples > 1, true);
2536
2537 // Note: Making the assumption that the stencil buffer is clear at this point
2538 rapi.setRenderTarget(resolvedSceneDepthNode->output->renderTexture, FBT_DEPTH, RT_DEPTH_STENCIL);
2539 stencilMat->execute(inputs.view, gbuffer, settings);
2540
2541 SPtr<PooledRenderTexture> traceOutput = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width,
2542 height, TU_RENDERTARGET));
2543
2544 RENDER_TEXTURE_DESC traceRtDesc;
2545 traceRtDesc.colorSurfaces[0].texture = traceOutput->texture;
2546 traceRtDesc.depthStencilSurface.texture = resolvedSceneDepthNode->output->texture;
2547
2548 SPtr<RenderTexture> traceRt = RenderTexture::create(traceRtDesc);
2549
2550 rapi.setRenderTarget(traceRt, FBT_DEPTH | FBT_STENCIL, RT_DEPTH_STENCIL);
2551 rapi.clearRenderTarget(FBT_COLOR, Color::ZERO);
2552
2553 SSRTraceMat* traceMat = SSRTraceMat::getVariation(settings.quality, viewProps.target.numSamples > 1, true);
2554 traceMat->execute(inputs.view, gbuffer, sceneColor, hiZ, settings, traceRt);
2555
2556 if (resolvedSceneColor)
2557 {
2558 resPool.release(resolvedSceneColor);
2559 resolvedSceneColor = nullptr;
2560 }
2561
2562 if (mPrevFrame)
2563 {
2564 mPooledOutput = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height, TU_RENDERTARGET));
2565
2566 rapi.setRenderTarget(mPooledOutput->renderTexture);
2567 rapi.clearRenderTarget(FBT_COLOR);
2568
2569 SSRResolveMat* resolveMat = SSRResolveMat::getVariation(viewProps.target.numSamples > 1);
2570 resolveMat->execute(inputs.view, mPrevFrame->texture, traceOutput->texture, sceneDepthNode->depthTex->texture,
2571 mPooledOutput->renderTexture);
2572
2573 resPool.release(traceOutput);
2574 }
2575 else
2576 mPooledOutput = traceOutput;
2577
2578 RenderAPI::instance().setRenderTarget(nullptr);
2579 output = mPooledOutput->texture;
2580 }
2581
2582 void RCNodeSSR::clear()
2583 {
2584 GpuResourcePool& resPool = GpuResourcePool::instance();
2585
2586 if(mPrevFrame)
2587 resPool.release(mPrevFrame);
2588
2589 mPrevFrame = mPooledOutput;
2590 mPooledOutput = nullptr;
2591 output = nullptr;
2592 }
2593
2594 void RCNodeSSR::deallocOutputs()
2595 {
2596 GpuResourcePool& resPool = GpuResourcePool::instance();
2597
2598 if(mPrevFrame)
2599 {
2600 resPool.release(mPrevFrame);
2601 mPrevFrame = nullptr;
2602 }
2603
2604 output = nullptr;
2605 }
2606
2607 SmallVector<StringID, 4> RCNodeSSR::getDependencies(const RendererView& view)
2608 {
2609 SmallVector<StringID, 4> deps;
2610 if (view.getRenderSettings().screenSpaceReflections.enabled)
2611 {
2612 deps.add(RCNodeSceneDepth::getNodeId());
2613 deps.add(RCNodeLightAccumulation::getNodeId());
2614 deps.add(RCNodeBasePass::getNodeId());
2615 deps.add(RCNodeHiZ::getNodeId());
2616 deps.add(RCNodeResolvedSceneDepth::getNodeId());
2617 deps.add(RCNodeIndirectDiffuseLighting::getNodeId());
2618 }
2619
2620 return deps;
2621 }
2622
2623 void RCNodeBloom::render(const RenderCompositorNodeInputs& inputs)
2624 {
2625 GpuResourcePool& resPool = GpuResourcePool::instance();
2626 const RenderSettings& settings = inputs.view.getRenderSettings();
2627
2628 // Grab 1/2 scene color to use as input
2629 auto* halfSceneColorNode = static_cast<RCNodeHalfSceneColor*>(inputs.inputNodes[1]);
2630 const SPtr<Texture>& halfSceneColor = halfSceneColorNode->output->texture;
2631
2632 // Clip color values based on intensity (if enabled)
2633 SPtr<PooledRenderTexture> clipOutput;
2634 SPtr<PooledRenderTexture> downsampleInput;
2635 if(settings.bloom.threshold > 0.0f)
2636 {
2637 const bool autoExposure = settings.enableHDR && settings.enableAutoExposure;
2638 BloomClipMat* clipMat = BloomClipMat::getVariation(autoExposure);
2639
2640 SPtr<Texture> eyeAdaptationTex = nullptr;
2641
2642 if(autoExposure)
2643 {
2644 auto* eyeAdapatationNode = static_cast<RCNodeEyeAdaptation*>(inputs.inputNodes[2]);
2645
2646 if(eyeAdapatationNode->output)
2647 eyeAdaptationTex = eyeAdapatationNode->output->texture;
2648 }
2649
2650 const TextureProperties& halfSceneColorProps = halfSceneColor->getProperties();
2651 clipOutput = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(
2652 halfSceneColorProps.getFormat(),
2653 halfSceneColorProps.getWidth(),
2654 halfSceneColorProps.getHeight(),
2655 TU_RENDERTARGET));
2656
2657 clipMat->execute(halfSceneColor, settings.bloom.threshold, eyeAdaptationTex, settings,
2658 clipOutput->renderTexture);
2659
2660 downsampleInput = clipOutput;
2661 }
2662 else
2663 downsampleInput = halfSceneColorNode->output;
2664
2665 // Generate the downsample pyramid
2666 constexpr UINT32 NUM_DOWNSAMPLE_LEVELS = 6;
2667 SPtr<PooledRenderTexture> downsamplePyramid[NUM_DOWNSAMPLE_LEVELS];
2668 downsamplePyramid[0] = downsampleInput;
2669
2670 DownsampleMat* downsampleMat = DownsampleMat::getVariation(1, false);
2671 for(UINT32 i = 1; i < NUM_DOWNSAMPLE_LEVELS; i++)
2672 {
2673 downsamplePyramid[i] = resPool.get(DownsampleMat::getOutputDesc(downsamplePyramid[i - 1]->texture));
2674 downsampleMat->execute(downsamplePyramid[i - 1]->texture, downsamplePyramid[i]->renderTexture);
2675 }
2676
2677 // Blur the downsampled entries and add them together
2678 const UINT32 quality = Math::clamp(settings.bloom.quality, 0U, 3U);
2679 constexpr UINT32 NUM_STEPS_PER_QUALITY[] = { 3, 4, 5, 6 };
2680 constexpr float FILTER_SIZE_PER_STEP[] = { 4.0f, 16.0f, 64.0f, 128.0f, 256.0f, 256.0f };
2681
2682 GaussianBlurMat* filterMat = GaussianBlurMat::getVariation(true);
2683 const UINT32 numSteps = NUM_STEPS_PER_QUALITY[quality];
2684 SPtr<PooledRenderTexture> prevOutput;
2685 for(UINT32 i = 0; i < numSteps; i++)
2686 {
2687 const UINT32 srcIdx = NUM_DOWNSAMPLE_LEVELS - i - 1;
2688 const TextureProperties& inputProps = downsamplePyramid[srcIdx]->texture->getProperties();
2689
2690 SPtr<PooledRenderTexture> filterOutput = resPool.get(
2691 POOLED_RENDER_TEXTURE_DESC::create2D(inputProps.getFormat(), inputProps.getWidth(),
2692 inputProps.getHeight(), TU_RENDERTARGET));
2693
2694 SPtr<Texture> additiveInput;
2695 if(prevOutput)
2696 additiveInput = prevOutput->texture;
2697
2698 const Color tint = Color::White * (settings.bloom.intensity / (float)numSteps);
2699 filterMat->execute(downsamplePyramid[srcIdx]->texture, FILTER_SIZE_PER_STEP[i], filterOutput->renderTexture,
2700 tint, additiveInput);
2701 prevOutput = filterOutput;
2702 }
2703
2704 mPooledOutput = prevOutput;
2705 output = mPooledOutput->texture;
2706 }
2707
2708 void RCNodeBloom::clear()
2709 {
2710 GpuResourcePool& resPool = GpuResourcePool::instance();
2711 resPool.release(mPooledOutput);
2712
2713 output = nullptr;
2714 }
2715
2716 SmallVector<StringID, 4> RCNodeBloom::getDependencies(const RendererView& view)
2717 {
2718 return { RCNodeClusteredForward::getNodeId(), RCNodeHalfSceneColor::getNodeId(), RCNodeEyeAdaptation::getNodeId() };
2719 }
2720
2721}}
2722