mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-12-02 08:52:28 +00:00
a76dba5cfb
- brightness-corrected ImGUI drawing - upgraded shader code to HLSL 2021 - vertex normals drawing
872 lines
27 KiB
C++
872 lines
27 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2023-2024 Gian 'myT' Schellenbaum
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Challenge Quake 3 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
// Cinematic Rendering Pipeline - main interface
|
|
|
|
|
|
#include "crp_local.h"
|
|
#include "../client/cl_imgui.h"
|
|
#include "shaders/crp/oit.h.hlsli"
|
|
#include "shaders/crp/scene_view.h.hlsli"
|
|
#include "compshaders/crp/fullscreen.h"
|
|
#include "compshaders/crp/blit.h"
|
|
#include "compshaders/crp/ui.h"
|
|
#include "compshaders/crp/imgui.h"
|
|
#include "compshaders/crp/nuklear.h"
|
|
#include "compshaders/crp/mip_1.h"
|
|
#include "compshaders/crp/mip_2.h"
|
|
#include "compshaders/crp/mip_3.h"
|
|
|
|
|
|
struct SceneViewConst
|
|
{
|
|
enum Constants
|
|
{
|
|
MaxViews = 1024,
|
|
LightBytes = sizeof(DynamicLight),
|
|
MaxLights = SCENE_VIEW_MAX_LIGHTS,
|
|
StructBytes = sizeof(SceneView),
|
|
BufferBytes = MaxViews * StructBytes
|
|
};
|
|
};
|
|
|
|
|
|
CRP crp;
|
|
IRenderPipeline* crpp = &crp;
|
|
|
|
cvar_t* crp_dof;
|
|
cvar_t* crp_dof_overlay;
|
|
cvar_t* crp_dof_blades;
|
|
cvar_t* crp_dof_angle;
|
|
cvar_t* crp_gatherDof_focusNearDist;
|
|
cvar_t* crp_gatherDof_focusNearRange;
|
|
cvar_t* crp_gatherDof_focusFarDist;
|
|
cvar_t* crp_gatherDof_focusFarRange;
|
|
cvar_t* crp_gatherDof_brightness;
|
|
cvar_t* crp_accumDof_focusDist;
|
|
cvar_t* crp_accumDof_radius;
|
|
cvar_t* crp_accumDof_samples;
|
|
cvar_t* crp_accumDof_preview;
|
|
cvar_t* crp_drawNormals;
|
|
cvar_t* crp_updateRTAS;
|
|
cvar_t* crp_debug0;
|
|
cvar_t* crp_debug1;
|
|
cvar_t* crp_debug2;
|
|
cvar_t* crp_debug3;
|
|
|
|
static const cvarTableItem_t crp_cvars[] =
|
|
{
|
|
{
|
|
&crp_dof, "crp_dof", "1", CVAR_ARCHIVE, CVART_INTEGER, "0", "2",
|
|
"enables depth of field\n"
|
|
S_COLOR_VAL " 0 " S_COLOR_HELP "= Disabled\n"
|
|
S_COLOR_VAL " 1 " S_COLOR_HELP "= Gather (fast, more flexible, issues with transparency)\n"
|
|
S_COLOR_VAL " 2 " S_COLOR_HELP "= Accumulation (slow, less flexible, great IQ)\n",
|
|
"DoF mode", CVARCAT_GRAPHICS, "Depth of field mode", "",
|
|
CVAR_GUI_VALUE("0", "Disabled", "")
|
|
CVAR_GUI_VALUE("1", "Gather", "Fast, lower IQ")
|
|
CVAR_GUI_VALUE("2", "Accumulation", "Very slow, great IQ")
|
|
},
|
|
{
|
|
&crp_dof_overlay, "crp_dof_overlay", "0", CVAR_ARCHIVE, CVART_INTEGER, "0", "2",
|
|
"debug overlay mode\n"
|
|
S_COLOR_VAL " 0 " S_COLOR_HELP "= Disabled\n"
|
|
S_COLOR_VAL " 1 " S_COLOR_HELP "= Colorized Blur\n"
|
|
S_COLOR_VAL " 2 " S_COLOR_HELP "= Focus Plane",
|
|
"DoF overlay mode", CVARCAT_GRAPHICS, "Debug overlay mode", "",
|
|
CVAR_GUI_VALUE("0", "Disabled", "")
|
|
CVAR_GUI_VALUE("1", "Colorized Blur", "")
|
|
CVAR_GUI_VALUE("2", "Focus Plane", "")
|
|
},
|
|
{
|
|
&crp_dof_blades, "crp_dof_blades", "6", CVAR_ARCHIVE, CVART_FLOAT, "0", "16",
|
|
"aperture blade count\n"
|
|
"Set to less than 3 for a disk shape.",
|
|
"DoF blade count", CVARCAT_GRAPHICS, "Aperture blade count", "Set to less than 3 for a disk shape."
|
|
},
|
|
{
|
|
&crp_dof_angle, "crp_dof_angle", "20", CVAR_ARCHIVE, CVART_FLOAT, "0", "360", "aperture angle, in degrees",
|
|
"DoF aperture angle", CVARCAT_GRAPHICS, "Aperture angle, in degrees", ""
|
|
},
|
|
{
|
|
&crp_accumDof_focusDist, "crp_accumDof_focusDist", "256", CVAR_ARCHIVE, CVART_FLOAT, "2", "2048", "focus distance",
|
|
"Accum DoF focus distance", CVARCAT_GRAPHICS, "Focus distance", ""
|
|
},
|
|
{
|
|
&crp_accumDof_radius, "crp_accumDof_blurRadius", "0.1", CVAR_ARCHIVE, CVART_FLOAT, "0.001", "20", "aperture radius in world units",
|
|
"Accum DoF aperture radius", CVARCAT_GRAPHICS, "Aperture radius in world units", ""
|
|
},
|
|
{
|
|
&crp_accumDof_samples, "crp_accumDof_samples", "2", CVAR_ARCHIVE, CVART_INTEGER, "1", "12",
|
|
"per-axis sampling density\n"
|
|
"Density N means (2N + 1)(2N + 1) scene renders in total.",
|
|
"Accum DoF sample count", CVARCAT_GRAPHICS, "Per-axis sampling density", "Density N means (2N + 1)^2 scene renders in total."
|
|
},
|
|
{
|
|
&crp_accumDof_preview, "crp_accumDof_preview", "0", CVAR_ARCHIVE, CVART_INTEGER, "0", "2",
|
|
"low-res preview mode\n"
|
|
S_COLOR_VAL " 0 " S_COLOR_HELP "= Disabled\n"
|
|
S_COLOR_VAL " 1 " S_COLOR_HELP "= 1/4 pixel count, 9 samples total\n"
|
|
S_COLOR_VAL " 2 " S_COLOR_HELP "= 1/16 pixel count, 25 samples total",
|
|
"Accum DoF preview mode", CVARCAT_GRAPHICS, "Low-resolution preview modes", "",
|
|
CVAR_GUI_VALUE("0", "Disabled", "")
|
|
CVAR_GUI_VALUE("1", "1/4 pixel count", "9 samples total")
|
|
CVAR_GUI_VALUE("2", "1/16 pixel count", "25 samples total")
|
|
},
|
|
{
|
|
&crp_gatherDof_focusNearDist, "crp_gatherDof_focusNearDist", "192", CVAR_ARCHIVE, CVART_FLOAT, "1", "2048", "near focus distance",
|
|
"Gather DoF near focus distance", CVARCAT_GRAPHICS, "Near focus distance", ""
|
|
},
|
|
{
|
|
&crp_gatherDof_focusNearRange, "crp_gatherDof_focusNearRange", "256", CVAR_ARCHIVE, CVART_FLOAT, "1", "2048", "near focus range",
|
|
"Gather DoF near focus range", CVARCAT_GRAPHICS, "Near focus range", ""
|
|
},
|
|
{
|
|
&crp_gatherDof_focusFarDist, "crp_gatherDof_focusFarDist", "512", CVAR_ARCHIVE, CVART_FLOAT, "1", "2048", "far focus distance",
|
|
"Gather DoF far focus distance", CVARCAT_GRAPHICS, "Far focus distance", ""
|
|
},
|
|
{
|
|
&crp_gatherDof_focusFarRange, "crp_gatherDof_focusFarRange", "384", CVAR_ARCHIVE, CVART_FLOAT, "1", "2048", "far focus range",
|
|
"Gather DoF far focus range", CVARCAT_GRAPHICS, "Far focus range", ""
|
|
},
|
|
{
|
|
&crp_gatherDof_brightness, "crp_gatherDof_brightness", "2", CVAR_ARCHIVE, CVART_FLOAT, "0", "8", "blur brightness weight",
|
|
"Gather DoF bokeh brightness", CVARCAT_GRAPHICS, "Blur brightness weight", ""
|
|
},
|
|
{
|
|
&crp_drawNormals, "crp_drawNormals", "0", CVAR_TEMP, CVART_BOOL, NULL, NULL, "draws vertex normals",
|
|
"Draw vertex normals", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", ""
|
|
},
|
|
{
|
|
&crp_updateRTAS, "crp_updateRTAS", "1", CVAR_TEMP, CVART_BOOL, NULL, NULL, "enables RTAS builds every frame",
|
|
"Enable RTAS builds", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "Allows raytracing acceleration structure updates", ""
|
|
}
|
|
#if defined(_DEBUG)
|
|
,
|
|
{
|
|
&crp_debug0, "crp_debug0", "0", CVAR_TEMP, CVART_FLOAT, "0", "1", "debug value 0",
|
|
"Debug value 0", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", ""
|
|
},
|
|
{
|
|
&crp_debug1, "crp_debug1", "0", CVAR_TEMP, CVART_FLOAT, "0", "1", "debug value 1",
|
|
"Debug value 1", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", ""
|
|
},
|
|
{
|
|
&crp_debug2, "crp_debug2", "0", CVAR_TEMP, CVART_FLOAT, "0", "1", "debug value 2",
|
|
"Debug value 2", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", ""
|
|
},
|
|
{
|
|
&crp_debug3, "crp_debug3", "0", CVAR_TEMP, CVART_FLOAT, "0", "1", "debug value 3",
|
|
"Debug value 3", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", ""
|
|
}
|
|
#endif
|
|
};
|
|
|
|
|
|
void PSOCache::Init(Entry* entries_, uint32_t maxEntryCount_)
|
|
{
|
|
entries = entries_;
|
|
maxEntryCount = maxEntryCount_;
|
|
entryCount = 1; // we treat index 0 as invalid
|
|
}
|
|
|
|
int PSOCache::AddPipeline(const GraphicsPipelineDesc& desc, const char* name)
|
|
{
|
|
// we treat index 0 as invalid, so start at 1
|
|
for(uint32_t i = 1; i < entryCount; ++i)
|
|
{
|
|
Entry& entry = entries[i];
|
|
if(memcmp(&entry.desc, &desc, sizeof(desc)) == 0)
|
|
{
|
|
return (int)i;
|
|
}
|
|
}
|
|
|
|
ASSERT_OR_DIE(entryCount < maxEntryCount, "Not enough entries in the PSO cache");
|
|
|
|
GraphicsPipelineDesc namedDesc = desc;
|
|
namedDesc.name = name;
|
|
|
|
// @NOTE: we keep the original desc and its padding bytes for proper comparison results
|
|
const uint32_t index = entryCount++;
|
|
Entry& entry = entries[index];
|
|
memcpy(&entry.desc, &desc, sizeof(entry.desc));
|
|
entry.handle = CreateGraphicsPipeline(namedDesc);
|
|
|
|
return (int)index;
|
|
}
|
|
|
|
void CRP::Init()
|
|
{
|
|
static bool veryFirstInit = true;
|
|
if(veryFirstInit)
|
|
{
|
|
ri.Cvar_RegisterTable(crp_cvars, ARRAY_LEN(crp_cvars));
|
|
veryFirstInit = false;
|
|
}
|
|
|
|
InitDesc initDesc;
|
|
initDesc.directDescriptorHeapIndexing = true;
|
|
initDesc.inlineRaytracing = true;
|
|
srp.firstInit = RHI::Init(initDesc);
|
|
srp.psoStatsValid = false;
|
|
|
|
if(srp.firstInit)
|
|
{
|
|
srp.CreateShaderTraceBuffers();
|
|
|
|
for(uint32_t f = 0; f < FrameCount; ++f)
|
|
{
|
|
// the doubled index count is for the depth pre-pass
|
|
const int MaxDynamicVertexCount = 16 << 20;
|
|
const int MaxDynamicIndexCount = MaxDynamicVertexCount * 4;
|
|
GeoBuffers& db = dynBuffers[f];
|
|
db.Create(va("world #%d", f + 1), MaxDynamicVertexCount, MaxDynamicIndexCount);
|
|
}
|
|
}
|
|
|
|
// we recreate the samplers on every vid_restart to create the right level
|
|
// of anisotropy based on the latched CVar
|
|
for(uint32_t w = 0; w < TW_COUNT; ++w)
|
|
{
|
|
for(uint32_t f = 0; f < TextureFilter::Count; ++f)
|
|
{
|
|
for(uint32_t m = 0; m < MaxTextureMips; ++m)
|
|
{
|
|
const textureWrap_t wrap = (textureWrap_t)w;
|
|
const TextureFilter::Id filter = (TextureFilter::Id)f;
|
|
const uint32_t s = GetBaseSamplerIndex(wrap, filter, m);
|
|
SamplerDesc desc(wrap, filter, (float)m);
|
|
desc.shortLifeTime = true;
|
|
samplers[s] = CreateSampler(desc);
|
|
samplerIndices[s] = RHI::GetSamplerIndex(samplers[s]);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
renderTargetFormat = TextureFormat::RGBA64_Float;
|
|
|
|
TextureDesc desc("render target #1", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit;
|
|
Vector4Clear(desc.clearColor);
|
|
desc.usePreferredClearValue = true;
|
|
desc.committedResource = true;
|
|
desc.format = renderTargetFormat;
|
|
desc.shortLifeTime = true;
|
|
renderTargets[0] = RHI::CreateTexture(desc);
|
|
desc.name = "render target #2";
|
|
renderTargets[1] = RHI::CreateTexture(desc);
|
|
renderTargetIndex = 0;
|
|
renderTarget = renderTargets[0];
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("readback render target", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit;
|
|
Vector4Clear(desc.clearColor);
|
|
desc.usePreferredClearValue = true;
|
|
desc.committedResource = true;
|
|
desc.format = TextureFormat::RGBA32_UNorm;
|
|
desc.shortLifeTime = true;
|
|
readbackRenderTarget = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("OIT index", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.initialState = ResourceStates::UnorderedAccessBit;
|
|
desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit;
|
|
desc.committedResource = true;
|
|
desc.format = TextureFormat::R32_UInt;
|
|
desc.shortLifeTime = true;
|
|
oitIndexTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
uint32_t oitMaxFragmentCount = 0;
|
|
{
|
|
const int byteCountPerFragment = sizeof(OIT_Fragment);
|
|
const int fragmentCount = glConfig.vidWidth * glConfig.vidHeight * OIT_AVG_FRAGMENTS_PER_PIXEL;
|
|
const int byteCount = byteCountPerFragment * fragmentCount;
|
|
oitMaxFragmentCount = fragmentCount;
|
|
|
|
BufferDesc desc("OIT fragment", byteCount, ResourceStates::UnorderedAccessBit);
|
|
desc.committedResource = true;
|
|
desc.memoryUsage = MemoryUsage::GPU;
|
|
desc.structureByteCount = byteCountPerFragment;
|
|
desc.shortLifeTime = true;
|
|
oitFragmentBuffer = CreateBuffer(desc);
|
|
}
|
|
|
|
{
|
|
const int byteCount = sizeof(OIT_Counter);
|
|
|
|
{
|
|
BufferDesc desc("OIT counter", byteCount, ResourceStates::UnorderedAccessBit);
|
|
desc.committedResource = true;
|
|
desc.memoryUsage = MemoryUsage::GPU;
|
|
desc.structureByteCount = byteCount;
|
|
desc.shortLifeTime = true;
|
|
oitCounterBuffer = CreateBuffer(desc);
|
|
}
|
|
{
|
|
BufferDesc desc("OIT counter staging", byteCount, ResourceStates::Common);
|
|
desc.committedResource = false;
|
|
desc.memoryUsage = MemoryUsage::Upload;
|
|
desc.structureByteCount = byteCount;
|
|
desc.shortLifeTime = true;
|
|
oitCounterStagingBuffer = CreateBuffer(desc);
|
|
|
|
uint32_t* dst = (uint32_t*)MapBuffer(oitCounterStagingBuffer);
|
|
dst[0] = 1; // fragment index 0 is the end-of-list value
|
|
dst[1] = oitMaxFragmentCount;
|
|
dst[2] = 0;
|
|
UnmapBuffer(oitCounterStagingBuffer);
|
|
}
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("depth buffer", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.committedResource = true;
|
|
desc.shortLifeTime = true;
|
|
desc.initialState = ResourceStates::DepthWriteBit;
|
|
desc.allowedState = ResourceStates::DepthAccessBits | ResourceStates::PixelShaderAccessBit;
|
|
desc.format = TextureFormat::Depth32_Float;
|
|
desc.SetClearDepthStencil(0.0f, 0);
|
|
depthTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("GBuffer normals", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.committedResource = true;
|
|
desc.shortLifeTime = true;
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit;
|
|
desc.format = TextureFormat::RG32_SNorm;
|
|
desc.SetClearColor(vec4_zero);
|
|
normalTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("GBuffer motion vectors", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.committedResource = true;
|
|
desc.shortLifeTime = true;
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit;
|
|
desc.format = TextureFormat::RG32_Float;
|
|
desc.SetClearColor(vec4_zero);
|
|
motionVectorTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("GBuffer direct light", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.committedResource = true;
|
|
desc.shortLifeTime = true;
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit;
|
|
desc.format = TextureFormat::RGBA64_Float;
|
|
desc.SetClearColor(colorBlack);
|
|
lightTexture = RHI::CreateTexture(desc);
|
|
desc.name = "GBuffer raw direct light";
|
|
noisyLightTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
TextureDesc desc("GBuffer shading position", glConfig.vidWidth, glConfig.vidHeight);
|
|
desc.committedResource = true;
|
|
desc.shortLifeTime = true;
|
|
desc.initialState = ResourceStates::RenderTargetBit;
|
|
desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit;
|
|
desc.format = TextureFormat::RGBA128_Float;
|
|
desc.SetClearColor(vec4_zero);
|
|
shadingPositionTexture = RHI::CreateTexture(desc);
|
|
}
|
|
|
|
{
|
|
GraphicsPipelineDesc desc("blit LDR");
|
|
desc.shortLifeTime = true;
|
|
desc.vertexShader = ShaderByteCode(g_fullscreen_vs);
|
|
desc.pixelShader = ShaderByteCode(g_blit_ps);
|
|
desc.depthStencil.DisableDepth();
|
|
desc.rasterizer.cullMode = CT_TWO_SIDED;
|
|
desc.AddRenderTarget(0, TextureFormat::RGBA32_UNorm);
|
|
blitPipelineLDR = CreateGraphicsPipeline(desc);
|
|
desc.name = "blit HDR";
|
|
desc.renderTargets[0].format = TextureFormat::RGBA64_Float;
|
|
blitPipelineHDR = CreateGraphicsPipeline(desc);
|
|
}
|
|
|
|
{
|
|
BufferDesc desc("scene view upload #1", SceneViewConst::BufferBytes, ResourceStates::ShaderAccessBits);
|
|
desc.shortLifeTime = true;
|
|
desc.memoryUsage = MemoryUsage::Upload;
|
|
desc.structureByteCount = SceneViewConst::StructBytes;
|
|
sceneViewUploadBuffers[0] = CreateBuffer(desc);
|
|
desc.name = "scene view upload #2";
|
|
sceneViewUploadBuffers[1] = CreateBuffer(desc);
|
|
}
|
|
|
|
{
|
|
BufferDesc desc("scene view", SceneViewConst::StructBytes, ResourceStates::ShaderAccessBits);
|
|
desc.shortLifeTime = true;
|
|
desc.structureByteCount = SceneViewConst::StructBytes;
|
|
desc.useSrvIndex0 = true; // the one and only buffer allowed to be there
|
|
sceneViewBuffer = CreateBuffer(desc);
|
|
}
|
|
|
|
ui.Init(true, ShaderByteCode(g_ui_vs), ShaderByteCode(g_ui_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL);
|
|
imgui.Init(true, ShaderByteCode(g_imgui_vs), ShaderByteCode(g_imgui_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL);
|
|
nuklear.Init(true, ShaderByteCode(g_nuklear_vs), ShaderByteCode(g_nuklear_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL);
|
|
mipMapGen.Init(true, ShaderByteCode(g_mip_1_cs), ShaderByteCode(g_mip_2_cs), ShaderByteCode(g_mip_3_cs));
|
|
prepass.Init();
|
|
opaque.Init();
|
|
transp.Init();
|
|
transpResolve.Init();
|
|
toneMap.Init();
|
|
gatherDof.Init();
|
|
accumDof.Init();
|
|
fog.Init();
|
|
magnifier.Init();
|
|
dynamicLights.Init();
|
|
raytracing.Init();
|
|
gbufferViz.Init();
|
|
|
|
srp.firstInit = false;
|
|
}
|
|
|
|
void CRP::LoadResources()
|
|
{
|
|
const int flags = IMG_NOPICMIP | IMG_NOMIPMAP | IMG_NOIMANIP | IMG_NOAF;
|
|
blueNoise2D = R_FindImageFile("textures/stbn_2d.tga", flags, TW_REPEAT)->texture;
|
|
}
|
|
|
|
void CRP::ShutDown(bool fullShutDown)
|
|
{
|
|
RHI::ShutDown(fullShutDown);
|
|
}
|
|
|
|
void CRP::BeginFrame()
|
|
{
|
|
renderTargetIndex = 0;
|
|
renderTarget = renderTargets[0];
|
|
sceneViewIndex = 0;
|
|
|
|
srp.BeginFrame();
|
|
|
|
// have it be first to we can use ImGUI in the other components too
|
|
imgui.BeginFrame();
|
|
|
|
// must be run outside of the RHI::BeginFrame/RHI::EndFrame pair
|
|
raytracing.BeginFrame();
|
|
|
|
RHI::BeginFrame();
|
|
ui.BeginFrame();
|
|
nuklear.BeginFrame();
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
const float clearColor[4] = { 0.0f, 0.5f, 0.0f, 0.0f };
|
|
CmdClearColorTarget(renderTarget, clearColor);
|
|
|
|
frameSeed = (float)rand() / (float)RAND_MAX;
|
|
|
|
dynBuffers[GetFrameIndex()].Rewind();
|
|
}
|
|
|
|
void CRP::EndFrame()
|
|
{
|
|
srp.DrawGUI();
|
|
gbufferViz.DrawGUI();
|
|
magnifier.DrawGUI();
|
|
imgui.Draw(renderTarget);
|
|
toneMap.DrawToneMap();
|
|
magnifier.Draw();
|
|
BlitRenderTarget(GetSwapChainTexture(), "Blit to Swap Chain");
|
|
BlitRenderTarget(readbackRenderTarget, "Blit to Readback Texture");
|
|
srp.EndFrame();
|
|
}
|
|
|
|
void CRP::Blit(HTexture destination, HTexture source, const char* passName, bool hdr, const vec2_t tcScale, const vec2_t tcBias)
|
|
{
|
|
SCOPED_RENDER_PASS(passName, 0.125f, 0.125f, 0.5f);
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(source, ResourceStates::PixelShaderAccessBit);
|
|
CmdTextureBarrier(destination, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
|
|
#pragma pack(push, 4)
|
|
struct BlitRC
|
|
{
|
|
uint32_t textureIndex;
|
|
uint32_t samplerIndex;
|
|
float tcScale[2];
|
|
float tcBias[2];
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
BlitRC rc;
|
|
rc.textureIndex = GetTextureIndexSRV(source);
|
|
rc.samplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear);
|
|
rc.tcScale[0] = tcScale[0];
|
|
rc.tcScale[1] = tcScale[1];
|
|
rc.tcBias[0] = tcBias[0];
|
|
rc.tcBias[1] = tcBias[0];
|
|
CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight);
|
|
CmdBindRenderTargets(1, &destination, NULL);
|
|
CmdBindPipeline(hdr ? blitPipelineHDR : blitPipelineLDR);
|
|
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
|
|
CmdDraw(3, 0);
|
|
}
|
|
|
|
void CRP::BlitRenderTarget(HTexture destination, const char* passName)
|
|
{
|
|
Blit(destination, crp.renderTarget, passName, false, vec2_one, vec2_zero);
|
|
}
|
|
|
|
void CRP::CreateTexture(image_t* image, int mipCount, int width, int height)
|
|
{
|
|
TextureDesc desc(image->name, width, height, mipCount);
|
|
desc.committedResource = width * height >= (1 << 20);
|
|
desc.shortLifeTime = true;
|
|
if(mipCount > 1)
|
|
{
|
|
desc.allowedState |= ResourceStates::UnorderedAccessBit; // for mip-map generation
|
|
}
|
|
|
|
image->texture = ::RHI::CreateTexture(desc);
|
|
image->textureIndex = GetTextureIndexSRV(image->texture);
|
|
}
|
|
|
|
void CRP::UpoadTextureAndGenerateMipMaps(image_t* image, const byte* data)
|
|
{
|
|
MappedTexture texture;
|
|
RHI::BeginTextureUpload(texture, image->texture);
|
|
for(uint32_t r = 0; r < texture.rowCount; ++r)
|
|
{
|
|
memcpy(texture.mappedData + r * texture.dstRowByteCount, data + r * texture.srcRowByteCount, texture.srcRowByteCount);
|
|
}
|
|
RHI::EndTextureUpload();
|
|
|
|
mipMapGen.GenerateMipMaps(image->texture);
|
|
}
|
|
|
|
void CRP::BeginTextureUpload(MappedTexture& mappedTexture, image_t* image)
|
|
{
|
|
RHI::BeginTextureUpload(mappedTexture, image->texture);
|
|
}
|
|
|
|
void CRP::EndTextureUpload()
|
|
{
|
|
RHI::EndTextureUpload();
|
|
}
|
|
|
|
void CRP::ProcessWorld(world_t& world)
|
|
{
|
|
raytracing.ProcessWorld(world);
|
|
}
|
|
|
|
void CRP::ProcessModel(model_t&)
|
|
{
|
|
}
|
|
|
|
void CRP::ProcessShader(shader_t& shader)
|
|
{
|
|
if(shader.isOpaque)
|
|
{
|
|
prepass.ProcessShader(shader);
|
|
opaque.ProcessShader(shader);
|
|
}
|
|
else
|
|
{
|
|
transp.ProcessShader(shader);
|
|
}
|
|
}
|
|
|
|
void CRP::ExecuteRenderCommands(const byte* data, bool /*readbackRequested*/)
|
|
{
|
|
// @NOTE: the CRP always blits the final result to the readback texture
|
|
|
|
for(;;)
|
|
{
|
|
const int commandId = ((const renderCommandBase_t*)data)->commandId;
|
|
|
|
if(commandId < 0 || commandId >= RC_COUNT)
|
|
{
|
|
assert(!"Invalid render command type");
|
|
return;
|
|
}
|
|
|
|
if(commandId == RC_END_OF_LIST)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch(commandId)
|
|
{
|
|
case RC_UI_SET_COLOR:
|
|
ui.CmdSetColor(*(const uiSetColorCommand_t*)data);
|
|
break;
|
|
case RC_UI_DRAW_QUAD:
|
|
ui.CmdDrawQuad(*(const uiDrawQuadCommand_t*)data);
|
|
break;
|
|
case RC_UI_DRAW_TRIANGLE:
|
|
ui.CmdDrawTriangle(*(const uiDrawTriangleCommand_t*)data);
|
|
break;
|
|
case RC_DRAW_SCENE_VIEW:
|
|
DrawSceneView(*(const drawSceneViewCommand_t*)data);
|
|
break;
|
|
case RC_BEGIN_FRAME:
|
|
BeginFrame();
|
|
break;
|
|
case RC_SWAP_BUFFERS:
|
|
EndFrame();
|
|
break;
|
|
case RC_BEGIN_UI:
|
|
ui.Begin(renderTarget);
|
|
break;
|
|
case RC_END_UI:
|
|
ui.End();
|
|
break;
|
|
case RC_BEGIN_3D:
|
|
// @TODO:
|
|
srp.renderMode = RenderMode::None;
|
|
break;
|
|
case RC_END_3D:
|
|
// @TODO:
|
|
srp.renderMode = RenderMode::None;
|
|
break;
|
|
case RC_END_SCENE:
|
|
// @TODO: post-processing
|
|
break;
|
|
case RC_BEGIN_NK:
|
|
nuklear.Begin(renderTarget);
|
|
break;
|
|
case RC_END_NK:
|
|
nuklear.End();
|
|
break;
|
|
case RC_NK_UPLOAD:
|
|
nuklear.Upload(*(const nuklearUploadCommand_t*)data);
|
|
break;
|
|
case RC_NK_DRAW:
|
|
nuklear.Draw(*(const nuklearDrawCommand_t*)data);
|
|
break;
|
|
default:
|
|
Q_assert(!"Unsupported render command type");
|
|
return;
|
|
}
|
|
|
|
data += renderCommandSizes[commandId];
|
|
}
|
|
}
|
|
|
|
void CRP::TessellationOverflow()
|
|
{
|
|
switch(tess.tessellator)
|
|
{
|
|
case Tessellator::Prepass: prepass.TessellationOverflow(); break;
|
|
case Tessellator::Opaque: opaque.TessellationOverflow(); break;
|
|
case Tessellator::Transp: transp.TessellationOverflow(); break;
|
|
default: break;
|
|
}
|
|
tess.numIndexes = 0;
|
|
tess.numVertexes = 0;
|
|
}
|
|
|
|
void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd)
|
|
{
|
|
const viewParms_t& vp = cmd.viewParms;
|
|
if(cmd.shouldClearColor)
|
|
{
|
|
const Rect rect(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight);
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
CmdClearColorTarget(renderTarget, cmd.clearColor, &rect);
|
|
}
|
|
|
|
if(cmd.numDrawSurfs <= 0 || !cmd.shouldDrawScene)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(crp_dof->integer == DOFMethod::Accumulation &&
|
|
IsViewportFullscreen(vp))
|
|
{
|
|
const Rect rect(0, 0, glConfig.vidWidth, glConfig.vidHeight);
|
|
accumDof.Begin(cmd);
|
|
const uint32_t sampleCount = accumDof.GetSampleCount();
|
|
for(uint32_t y = 0; y < sampleCount; y++)
|
|
{
|
|
for(uint32_t x = 0; x < sampleCount; x++)
|
|
{
|
|
srp.enableRenderPassQueries = x == 0 && y == 0;
|
|
drawSceneViewCommand_t newCmd;
|
|
accumDof.FixCommand(newCmd, cmd, x, y);
|
|
|
|
backEnd.refdef = newCmd.refdef;
|
|
backEnd.viewParms = newCmd.viewParms;
|
|
UploadSceneViewData();
|
|
|
|
CmdBeginBarrier();
|
|
CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit);
|
|
CmdEndBarrier();
|
|
CmdClearColorTarget(renderTarget, cmd.clearColor, &rect);
|
|
prepass.Draw(newCmd);
|
|
dynamicLights.Draw();
|
|
opaque.Draw(newCmd);
|
|
fog.Draw();
|
|
transp.Draw(newCmd);
|
|
transpResolve.Draw(newCmd);
|
|
accumDof.Accumulate();
|
|
|
|
// geometry allocation is a linear allocation instead of a ring buffer
|
|
// we force a CPU-GPU sync point after every full scene render
|
|
// that way, we can keep the buffer sizes at least somewhat reasonable
|
|
SubmitAndContinue();
|
|
dynBuffers[GetFrameIndex()].Rewind();
|
|
}
|
|
}
|
|
CmdSetViewportAndScissor(backEnd.viewParms);
|
|
srp.enableRenderPassQueries = true;
|
|
accumDof.Normalize();
|
|
backEnd.viewParms = cmd.viewParms;
|
|
backEnd.refdef = cmd.refdef;
|
|
accumDof.DrawDebug();
|
|
}
|
|
else
|
|
{
|
|
backEnd.refdef = cmd.refdef;
|
|
backEnd.viewParms = cmd.viewParms;
|
|
UploadSceneViewData();
|
|
|
|
prepass.Draw(cmd);
|
|
dynamicLights.Draw();
|
|
opaque.Draw(cmd);
|
|
fog.Draw();
|
|
transp.Draw(cmd);
|
|
transpResolve.Draw(cmd);
|
|
CmdSetViewportAndScissor(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight);
|
|
gatherDof.Draw();
|
|
}
|
|
}
|
|
|
|
void CRP::UploadSceneViewData()
|
|
{
|
|
Q_assert(sceneViewIndex < SceneViewConst::MaxViews);
|
|
if(sceneViewIndex >= SceneViewConst::MaxViews)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPED_RENDER_PASS("Scene View Upload", 1.0f, 1.0f, 1.0f);
|
|
|
|
const trRefdef_t& refdef = backEnd.refdef;
|
|
const viewParms_t& vp = backEnd.viewParms;
|
|
const HBuffer uploadBuffer = sceneViewUploadBuffers[GetFrameIndex()];
|
|
const uint32_t uploadByteOffset = sceneViewIndex * SceneViewConst::StructBytes;
|
|
const HBuffer tlasBuffer = raytracing.GetTLAS();
|
|
const HBuffer tlasInstanceBuffer = raytracing.GetInstanceBuffer();
|
|
|
|
SceneView& dest = *(SceneView*)(MapBuffer(uploadBuffer) + uploadByteOffset);
|
|
|
|
// @NOTE: yes, world.modelMatrix is actually the view matrix
|
|
// it's the model-view matrix for the world entity, thus the view matrix
|
|
memcpy(dest.projectionMatrix, vp.projectionMatrix, sizeof(dest.projectionMatrix));
|
|
R_InvMatrix(dest.projectionMatrix, dest.invProjectionMatrix);
|
|
memcpy(dest.viewMatrix, vp.world.modelMatrix, sizeof(dest.viewMatrix));
|
|
R_InvMatrix(dest.viewMatrix, dest.invViewMatrix);
|
|
RB_CreateClipPlane(dest.clipPlane);
|
|
#if defined(_DEBUG)
|
|
dest.debug[0] = crp_debug0->value;
|
|
dest.debug[1] = crp_debug1->value;
|
|
dest.debug[2] = crp_debug2->value;
|
|
dest.debug[3] = crp_debug3->value;
|
|
#else
|
|
const uint32_t deadBeef = 0xDEADBEEF;
|
|
dest.debug[0] = *(const float*)&deadBeef;
|
|
dest.debug[1] = *(const float*)&deadBeef;
|
|
dest.debug[2] = *(const float*)&deadBeef;
|
|
dest.debug[3] = *(const float*)&deadBeef;
|
|
#endif
|
|
dest.sceneViewIndex = sceneViewIndex;
|
|
dest.frameIndex = tr.frameCount;
|
|
dest.depthTextureIndex = GetTextureIndexSRV(depthTexture);
|
|
dest.normalTextureIndex = GetTextureIndexSRV(normalTexture);
|
|
dest.shadingPositionTextureIndex = GetTextureIndexSRV(shadingPositionTexture);
|
|
dest.lightTextureIndex = GetTextureIndexSRV(lightTexture);
|
|
dest.tlasBufferIndex = IsNullHandle(tlasBuffer) ? 0 : GetBufferIndexSRV(tlasBuffer);
|
|
dest.tlasInstanceBufferIndex = IsNullHandle(tlasInstanceBuffer) ? 0 : GetBufferIndexSRV(tlasInstanceBuffer);
|
|
dest.lightCount = refdef.num_dlights;
|
|
|
|
for(int i = 0; i < refdef.num_dlights; i++)
|
|
{
|
|
const dlight_t& srcLight = refdef.dlights[i];
|
|
DynamicLight& destLight = dest.lights[i];
|
|
VectorCopy(srcLight.origin, destLight.position);
|
|
VectorCopy(srcLight.color, destLight.color);
|
|
destLight.radius = srcLight.radius;
|
|
destLight.padding = 0.0f;
|
|
}
|
|
|
|
UnmapBuffer(uploadBuffer);
|
|
|
|
CmdBeginBarrier();
|
|
CmdBufferBarrier(uploadBuffer, ResourceStates::CopySourceBit);
|
|
CmdBufferBarrier(sceneViewBuffer, ResourceStates::CopyDestinationBit);
|
|
CmdEndBarrier();
|
|
CmdCopyBuffer(sceneViewBuffer, 0, uploadBuffer, uploadByteOffset, SceneViewConst::StructBytes);
|
|
CmdBeginBarrier();
|
|
CmdBufferBarrier(sceneViewBuffer, ResourceStates::ShaderAccessBits);
|
|
CmdEndBarrier();
|
|
|
|
sceneViewIndex++;
|
|
}
|
|
|
|
void CRP::ReadPixels(int w, int h, int alignment, colorSpace_t colorSpace, void* outPixels)
|
|
{
|
|
ReadTextureImage(outPixels, readbackRenderTarget, w, h, alignment, colorSpace);
|
|
}
|
|
|
|
uint32_t CRP::GetSamplerDescriptorIndexFromBaseIndex(uint32_t baseIndex)
|
|
{
|
|
Q_assert(baseIndex < ARRAY_LEN(samplerIndices));
|
|
|
|
return samplerIndices[baseIndex];
|
|
}
|
|
|
|
HTexture CRP::GetReadRenderTarget()
|
|
{
|
|
return renderTargets[renderTargetIndex ^ 1];
|
|
}
|
|
|
|
HTexture CRP::GetWriteRenderTarget()
|
|
{
|
|
return renderTargets[renderTargetIndex];
|
|
}
|
|
|
|
void CRP::SwapRenderTargets()
|
|
{
|
|
renderTargetIndex ^= 1;
|
|
renderTarget = GetWriteRenderTarget();
|
|
}
|