/* =========================================================================== 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 . =========================================================================== */ // 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(); }