517 lines
18 KiB
C++
517 lines
18 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "GBufferPass.h"
|
|
#include "RenderList.h"
|
|
#if USE_EDITOR
|
|
#include "Engine/Renderer/Editor/VertexColors.h"
|
|
#include "Engine/Renderer/Editor/LightmapUVsDensity.h"
|
|
#include "Engine/Renderer/Editor/LODPreview.h"
|
|
#include "Engine/Renderer/Editor/MaterialComplexity.h"
|
|
#endif
|
|
#include "Engine/Core/Collections/Sorting.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/GPUContext.h"
|
|
#include "Engine/Graphics/Shaders/GPUShader.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Graphics/RenderBuffers.h"
|
|
#include "Engine/Graphics/RenderTargetPool.h"
|
|
#include "Engine/Content/Assets/Shader.h"
|
|
#include "Engine/Content/Content.h"
|
|
#include "Engine/Content/Assets/Model.h"
|
|
#include "Engine/Level/Actors/Decal.h"
|
|
#include "Engine/Engine/Engine.h"
|
|
|
|
PACK_STRUCT(struct alignas(GPU_SHADER_DATA_ALIGNMENT) GBufferPassData{
|
|
ShaderGBufferData GBuffer;
|
|
Float3 Dummy0;
|
|
int32 ViewMode;
|
|
});
|
|
|
|
#if USE_EDITOR
|
|
Dictionary<GPUBuffer*, const ModelLOD*> GBufferPass::IndexBufferToModelLOD;
|
|
CriticalSection GBufferPass::Locker;
|
|
#endif
|
|
|
|
String GBufferPass::ToString() const
|
|
{
|
|
return TEXT("GBufferPass");
|
|
}
|
|
|
|
bool GBufferPass::Init()
|
|
{
|
|
// Create pipeline state
|
|
_psDebug = GPUDevice::Instance->CreatePipelineState();
|
|
|
|
// Load assets
|
|
_gBufferShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/GBuffer"));
|
|
_skyModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Sphere"));
|
|
_boxModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/SimpleBox"));
|
|
if (_gBufferShader == nullptr || _skyModel == nullptr || _boxModel == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
#if COMPILE_WITH_DEV_ENV
|
|
_gBufferShader.Get()->OnReloading.Bind<GBufferPass, &GBufferPass::OnShaderReloading>(this);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GBufferPass::setupResources()
|
|
{
|
|
if (!_gBufferShader || !_gBufferShader->IsLoaded())
|
|
return true;
|
|
auto gbuffer = _gBufferShader->GetShader();
|
|
|
|
// Validate shader constant buffers sizes
|
|
if (gbuffer->GetCB(0)->GetSize() != sizeof(GBufferPassData))
|
|
{
|
|
LOG(Warning, "GBuffer shader has incorrct constant buffers sizes.");
|
|
return true;
|
|
}
|
|
|
|
// Create pipeline stages
|
|
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
|
if (!_psDebug->IsValid())
|
|
{
|
|
psDesc.PS = gbuffer->GetPS("PS_DebugView");
|
|
if (_psDebug->Init(psDesc))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GBufferPass::Dispose()
|
|
{
|
|
// Base
|
|
RendererPass::Dispose();
|
|
|
|
// Cleanup
|
|
SAFE_DELETE_GPU_RESOURCE(_psDebug);
|
|
_gBufferShader = nullptr;
|
|
_skyModel = nullptr;
|
|
_boxModel = nullptr;
|
|
#if USE_EDITOR
|
|
SAFE_DELETE(_lightmapUVsDensity);
|
|
SAFE_DELETE(_vertexColors);
|
|
SAFE_DELETE(_lodPreview);
|
|
SAFE_DELETE(_materialComplexity);
|
|
IndexBufferToModelLOD.SetCapacity(0);
|
|
#endif
|
|
}
|
|
|
|
#if USE_EDITOR
|
|
|
|
void DebugOverrideDrawCallsMaterial(const RenderContext& renderContext, IMaterial* material)
|
|
{
|
|
if (!material->IsReady())
|
|
return;
|
|
PROFILE_CPU();
|
|
IMaterial::InstancingHandler handler;
|
|
const bool canUseInstancing = material->CanUseInstancing(handler);
|
|
const auto drawModes = material->GetDrawModes();
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::GBuffer))
|
|
{
|
|
auto& drawCallsList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer];
|
|
for (int32 i : drawCallsList.Indices)
|
|
{
|
|
auto& drawCall = renderContext.List->DrawCalls.Get()[i];
|
|
if (drawCall.Material->IsSurface())
|
|
{
|
|
drawCall.Material = material;
|
|
}
|
|
}
|
|
drawCallsList.CanUseInstancing &= canUseInstancing;
|
|
}
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::GBuffer))
|
|
{
|
|
auto& drawCallsList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals];
|
|
for (int32 i : drawCallsList.Indices)
|
|
{
|
|
auto& drawCall = renderContext.List->DrawCalls.Get()[i];
|
|
if (drawCall.Material->IsSurface())
|
|
{
|
|
drawCall.Material = material;
|
|
}
|
|
}
|
|
drawCallsList.CanUseInstancing &= canUseInstancing;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void GBufferPass::Fill(RenderContext& renderContext, GPUTexture* lightBuffer)
|
|
{
|
|
PROFILE_GPU_CPU("GBuffer");
|
|
|
|
// Cache data
|
|
auto device = GPUDevice::Instance;
|
|
auto context = device->GetMainContext();
|
|
GPUTextureView* targetBuffers[5] =
|
|
{
|
|
lightBuffer->View(),
|
|
renderContext.Buffers->GBuffer0->View(),
|
|
renderContext.Buffers->GBuffer1->View(),
|
|
renderContext.Buffers->GBuffer2->View(),
|
|
renderContext.Buffers->GBuffer3->View(),
|
|
};
|
|
renderContext.View.Pass = DrawPass::GBuffer;
|
|
|
|
// Clear GBuffer
|
|
{
|
|
PROFILE_GPU_CPU_NAMED("Clear");
|
|
|
|
context->ClearDepth(*renderContext.Buffers->DepthBuffer);
|
|
context->Clear(lightBuffer->View(), Color::Transparent);
|
|
context->Clear(renderContext.Buffers->GBuffer0->View(), Color::Transparent);
|
|
context->Clear(renderContext.Buffers->GBuffer1->View(), Color::Transparent);
|
|
context->Clear(renderContext.Buffers->GBuffer2->View(), Color(1, 0, 0, 0));
|
|
context->Clear(renderContext.Buffers->GBuffer3->View(), Color::Transparent);
|
|
}
|
|
|
|
// Ensure to have valid data
|
|
if (checkIfSkipPass())
|
|
{
|
|
// Resources are missing. Do not perform rendering.
|
|
return;
|
|
}
|
|
|
|
#if USE_EDITOR
|
|
// Special debug drawing
|
|
if (renderContext.View.Mode == ViewMode::MaterialComplexity)
|
|
{
|
|
// Initialize background with complexity of the sky (uniform)
|
|
if (renderContext.List->Sky)
|
|
{
|
|
renderContext.List->Sky->ApplySky(context, renderContext, Matrix::Identity);
|
|
GPUPipelineState* materialPs = context->GetState();
|
|
const float complexity = (float)Math::Min(materialPs->Complexity, MATERIAL_COMPLEXITY_LIMIT) / MATERIAL_COMPLEXITY_LIMIT;
|
|
context->Clear(lightBuffer->View(), Color(complexity, complexity, complexity, 1.0f));
|
|
renderContext.List->Sky = nullptr;
|
|
}
|
|
}
|
|
else if (renderContext.View.Mode == ViewMode::PhysicsColliders)
|
|
{
|
|
context->ResetRenderTarget();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Draw objects that can get decals
|
|
context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
|
|
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::GBuffer);
|
|
|
|
// Draw decals
|
|
DrawDecals(renderContext, lightBuffer->View());
|
|
|
|
// Draw objects that cannot get decals
|
|
context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
|
|
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::GBufferNoDecals);
|
|
|
|
GPUTexture* nullTexture = nullptr;
|
|
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::AfterGBufferPass, lightBuffer, nullTexture);
|
|
|
|
// Draw sky
|
|
if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered() && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Sky))
|
|
{
|
|
PROFILE_GPU_CPU_NAMED("Sky");
|
|
context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
|
|
DrawSky(renderContext, context);
|
|
}
|
|
|
|
context->ResetRenderTarget();
|
|
}
|
|
|
|
bool SortDecal(RenderDecalData const& a, RenderDecalData const& b)
|
|
{
|
|
if (a.SortOrder == b.SortOrder)
|
|
{
|
|
return (uintptr)a.Material < (uintptr)b.Material;
|
|
}
|
|
return a.SortOrder < b.SortOrder;
|
|
}
|
|
|
|
void GBufferPass::RenderDebug(RenderContext& renderContext)
|
|
{
|
|
// Check if has resources loaded
|
|
if (checkIfSkipPass())
|
|
return;
|
|
|
|
// Cache data
|
|
auto device = GPUDevice::Instance;
|
|
auto context = device->GetMainContext();
|
|
auto lights = _gBufferShader->GetShader();
|
|
GBufferPassData data;
|
|
|
|
// Set constants buffer
|
|
SetInputs(renderContext.View, data.GBuffer);
|
|
data.ViewMode = static_cast<int32>(renderContext.View.Mode);
|
|
auto cb = lights->GetCB(0);
|
|
context->UpdateCB(cb, &data);
|
|
context->BindCB(0, cb);
|
|
|
|
// Bind inputs
|
|
context->BindSR(0, renderContext.Buffers->GBuffer0);
|
|
context->BindSR(1, renderContext.Buffers->GBuffer1);
|
|
context->BindSR(2, renderContext.Buffers->GBuffer2);
|
|
context->BindSR(3, renderContext.Buffers->DepthBuffer);
|
|
context->BindSR(4, renderContext.Buffers->GBuffer3);
|
|
|
|
// Combine frame
|
|
context->SetState(_psDebug);
|
|
context->DrawFullscreenTriangle();
|
|
|
|
// Cleanup
|
|
context->ResetSR();
|
|
}
|
|
|
|
// Custom render buffer for realtime skybox capturing (eg. used by GI).
|
|
class SkyboxCustomBuffer : public RenderBuffers::CustomBuffer
|
|
{
|
|
public:
|
|
uint64 LastCaptureFrame = 0;
|
|
GPUTexture* Skybox = nullptr;
|
|
|
|
~SkyboxCustomBuffer()
|
|
{
|
|
RenderTargetPool::Release(Skybox);
|
|
}
|
|
};
|
|
|
|
GPUTextureView* GBufferPass::RenderSkybox(RenderContext& renderContext, GPUContext* context)
|
|
{
|
|
GPUTextureView* result = nullptr;
|
|
if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered() && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Sky))
|
|
{
|
|
// Initialize skybox texture
|
|
auto& skyboxData = *renderContext.Buffers->GetCustomBuffer<SkyboxCustomBuffer>(TEXT("Skybox"));
|
|
skyboxData.LastFrameUsed = Engine::FrameCount;
|
|
bool dirty = false;
|
|
const int32 resolution = 16;
|
|
if (!skyboxData.Skybox)
|
|
{
|
|
const auto desc = GPUTextureDescription::NewCube(resolution, PixelFormat::R11G11B10_Float);
|
|
skyboxData.Skybox = RenderTargetPool::Get(desc);
|
|
if (!skyboxData.Skybox)
|
|
return nullptr;
|
|
RENDER_TARGET_POOL_SET_NAME(skyboxData.Skybox, "GBuffer.Skybox");
|
|
dirty = true;
|
|
}
|
|
|
|
// Redraw sky from time-to-time (dynamic skies can be animated, static skies can have textures streamed)
|
|
const uint32 redrawFramesCount = renderContext.List->Sky->IsDynamicSky() ? 4 : 240;
|
|
if (Engine::FrameCount - skyboxData.LastCaptureFrame >= redrawFramesCount)
|
|
dirty = true;
|
|
|
|
if (dirty)
|
|
{
|
|
PROFILE_GPU_CPU("Skybox");
|
|
skyboxData.LastCaptureFrame = Engine::FrameCount;
|
|
const RenderView originalView = renderContext.View;
|
|
renderContext.View.Pass = DrawPass::GBuffer;
|
|
renderContext.View.SetUpCube(10.0f, 10000.0f, originalView.Position);
|
|
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++)
|
|
{
|
|
renderContext.View.SetFace(faceIndex);
|
|
context->SetRenderTarget(skyboxData.Skybox->View(faceIndex));
|
|
context->SetViewportAndScissors(resolution, resolution);
|
|
DrawSky(renderContext, context);
|
|
}
|
|
renderContext.View = originalView;
|
|
context->ResetRenderTarget();
|
|
}
|
|
|
|
result = skyboxData.Skybox->ViewArray();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if USE_EDITOR
|
|
|
|
void GBufferPass::PreOverrideDrawCalls(RenderContext& renderContext)
|
|
{
|
|
// Clear cache before scene drawing
|
|
IndexBufferToModelLOD.Clear();
|
|
}
|
|
|
|
void GBufferPass::OverrideDrawCalls(RenderContext& renderContext)
|
|
{
|
|
// Override draw calls material to use material debug shader
|
|
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
|
|
{
|
|
if (!_lightmapUVsDensity)
|
|
_lightmapUVsDensity = New<LightmapUVsDensityMaterialShader>();
|
|
DebugOverrideDrawCallsMaterial(renderContext, _lightmapUVsDensity);
|
|
}
|
|
else if (renderContext.View.Mode == ViewMode::VertexColors)
|
|
{
|
|
if (!_vertexColors)
|
|
_vertexColors = New<VertexColorsMaterialShader>();
|
|
DebugOverrideDrawCallsMaterial(renderContext, _vertexColors);
|
|
}
|
|
else if (renderContext.View.Mode == ViewMode::LODPreview)
|
|
{
|
|
if (!_lodPreview)
|
|
_lodPreview = New<LODPreviewMaterialShader>();
|
|
DebugOverrideDrawCallsMaterial(renderContext, _lodPreview);
|
|
}
|
|
else if (renderContext.View.Mode == ViewMode::MaterialComplexity)
|
|
{
|
|
if (!_materialComplexity)
|
|
_materialComplexity = New<MaterialComplexityMaterialShader>();
|
|
_materialComplexity->DebugOverrideDrawCallsMaterial(renderContext);
|
|
}
|
|
}
|
|
|
|
void GBufferPass::DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer)
|
|
{
|
|
_materialComplexity->Draw(renderContext, context, lightBuffer);
|
|
}
|
|
|
|
#endif
|
|
|
|
bool GBufferPass::IsDebugView(ViewMode mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case ViewMode::Unlit:
|
|
case ViewMode::Diffuse:
|
|
case ViewMode::Normals:
|
|
case ViewMode::Depth:
|
|
case ViewMode::AmbientOcclusion:
|
|
case ViewMode::Metalness:
|
|
case ViewMode::Roughness:
|
|
case ViewMode::Specular:
|
|
case ViewMode::SpecularColor:
|
|
case ViewMode::SubsurfaceColor:
|
|
case ViewMode::ShadingModel:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void GBufferPass::SetInputs(const RenderView& view, ShaderGBufferData& gBuffer)
|
|
{
|
|
// GBuffer params:
|
|
// ViewInfo : x-1/Projection[0,0] y-1/Projection[1,1] z-(Far / (Far - Near) w-(-Far * Near) / (Far - Near) / Far)
|
|
// ScreenSize : x-Width y-Height z-1 / Width w-1 / Height
|
|
// ViewPos,ViewFar : x,y,z - world space view position w-Far
|
|
// InvViewMatrix : inverse view matrix (4 rows by 4 columns)
|
|
// InvProjectionMatrix : inverse projection matrix (4 rows by 4 columns)
|
|
gBuffer.ViewInfo = view.ViewInfo;
|
|
gBuffer.ScreenSize = view.ScreenSize;
|
|
gBuffer.ViewPos = view.Position;
|
|
gBuffer.ViewFar = view.Far;
|
|
Matrix::Transpose(view.IV, gBuffer.InvViewMatrix);
|
|
Matrix::Transpose(view.IP, gBuffer.InvProjectionMatrix);
|
|
}
|
|
|
|
void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context)
|
|
{
|
|
// Cache data
|
|
auto model = _skyModel.Get();
|
|
auto box = model->GetBox();
|
|
|
|
// Calculate sphere model transform to cover far plane
|
|
Matrix m1, m2;
|
|
Matrix::Scaling(renderContext.View.Far / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum
|
|
Matrix::CreateWorld(renderContext.View.Position, Float3::Up, Float3::Backward, m2); // Rotate sphere model
|
|
m1 *= m2;
|
|
|
|
// Draw sky
|
|
renderContext.List->Sky->ApplySky(context, renderContext, m1);
|
|
model->Render(context);
|
|
}
|
|
|
|
void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer)
|
|
{
|
|
auto& decals = renderContext.List->Decals;
|
|
const auto boxModel = _boxModel.Get();
|
|
if (decals.IsEmpty() || boxModel == nullptr || !boxModel->CanBeRendered() || EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::Decals))
|
|
return;
|
|
PROFILE_GPU_CPU("Decals");
|
|
auto context = GPUDevice::Instance->GetMainContext();
|
|
auto buffers = renderContext.Buffers;
|
|
|
|
// Sort decals from the lowest order to the highest order
|
|
Sorting::QuickSort(decals.Get(), decals.Count(), &SortDecal);
|
|
|
|
// Prepare
|
|
DrawCall drawCall;
|
|
MaterialBase::BindParameters bindParams(context, renderContext, drawCall);
|
|
bindParams.BindViewData();
|
|
MaterialDecalBlendingMode decalBlendingMode = (MaterialDecalBlendingMode)-1;
|
|
MaterialUsageFlags usageFlags = (MaterialUsageFlags)-1;
|
|
boxModel->LODs.Get()->Meshes.Get()->GetDrawCallGeometry(drawCall);
|
|
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3));
|
|
context->BindIB(drawCall.Geometry.IndexBuffer);
|
|
context->ResetRenderTarget();
|
|
|
|
// Draw all decals
|
|
for (int32 i = 0; i < decals.Count(); i++)
|
|
{
|
|
const RenderDecalData& decal = decals.Get()[i];
|
|
|
|
// Bind output (skip if won't change in-between decals)
|
|
const MaterialInfo& info = decal.Material->GetInfo();
|
|
const MaterialUsageFlags infoUsageFlags = info.UsageFlags & (MaterialUsageFlags::UseEmissive | MaterialUsageFlags::UseNormal);
|
|
if (decalBlendingMode != info.DecalBlendingMode || usageFlags != infoUsageFlags)
|
|
{
|
|
decalBlendingMode = info.DecalBlendingMode;
|
|
usageFlags = infoUsageFlags;
|
|
switch (decalBlendingMode)
|
|
{
|
|
case MaterialDecalBlendingMode::Translucent:
|
|
{
|
|
GPUTextureView* targetBuffers[4];
|
|
int32 count = 2;
|
|
targetBuffers[0] = buffers->GBuffer0->View();
|
|
targetBuffers[1] = buffers->GBuffer2->View();
|
|
if (EnumHasAnyFlags(usageFlags, MaterialUsageFlags::UseEmissive))
|
|
{
|
|
count++;
|
|
targetBuffers[2] = lightBuffer;
|
|
if (EnumHasAnyFlags(usageFlags, MaterialUsageFlags::UseNormal))
|
|
{
|
|
count++;
|
|
targetBuffers[3] = buffers->GBuffer1->View();
|
|
}
|
|
}
|
|
else if (EnumHasAnyFlags(usageFlags, MaterialUsageFlags::UseNormal))
|
|
{
|
|
count++;
|
|
targetBuffers[2] = buffers->GBuffer1->View();
|
|
}
|
|
context->SetRenderTarget(nullptr, ToSpan(targetBuffers, count));
|
|
break;
|
|
}
|
|
case MaterialDecalBlendingMode::Stain:
|
|
{
|
|
context->SetRenderTarget(buffers->GBuffer0->View());
|
|
break;
|
|
}
|
|
case MaterialDecalBlendingMode::Normal:
|
|
{
|
|
context->SetRenderTarget(buffers->GBuffer1->View());
|
|
break;
|
|
}
|
|
case MaterialDecalBlendingMode::Emissive:
|
|
{
|
|
context->SetRenderTarget(lightBuffer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw decal
|
|
drawCall.World = decal.World;
|
|
decal.Material->Bind(bindParams);
|
|
// TODO: use hardware instancing
|
|
context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, 1, 0, 0, 0);
|
|
}
|
|
|
|
context->ResetSR();
|
|
}
|