diff --git a/code/renderer/crp_local.h b/code/renderer/crp_local.h index 7f526af..f77a20b 100644 --- a/code/renderer/crp_local.h +++ b/code/renderer/crp_local.h @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -57,6 +57,7 @@ struct Tessellator enum Id { None, + Prepass, Opaque, Transp, Count @@ -90,6 +91,25 @@ struct PSOCache uint32_t entryCount = 1; // we treat index 0 as invalid }; +struct Prepass +{ + void Init(); + void Draw(const drawSceneViewCommand_t& cmd); + void ProcessShader(shader_t& shader); + void TessellationOverflow(); + +private: + void BeginBatch(const shader_t* shader); + void EndBatch(); + + PSOCache::Entry psoCacheEntries[128]; + PSOCache psoCache; + + float clipPlane[4]; + bool batchOldDepthHack; + bool batchDepthHack; +}; + struct WorldOpaque { void Init(); @@ -304,6 +324,8 @@ struct CRP : IRenderPipeline float frameSeed; HTexture readbackRenderTarget; HTexture depthTexture; + HTexture normalTexture; + HTexture motionVectorTexture; HTexture renderTarget; TextureFormat::Id renderTargetFormat; HTexture renderTargets[2]; @@ -328,6 +350,7 @@ struct CRP : IRenderPipeline MipMapGenerator mipMapGen; ImGUI imgui; Nuklear nuklear; + Prepass prepass; WorldOpaque opaque; WorldTransp transp; TranspResolve transpResolve; diff --git a/code/renderer/crp_main.cpp b/code/renderer/crp_main.cpp index 7d79648..babace5 100644 --- a/code/renderer/crp_main.cpp +++ b/code/renderer/crp_main.cpp @@ -305,6 +305,28 @@ void CRP::Init() 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); + } + { GraphicsPipelineDesc desc("blit LDR"); desc.vertexShader = ShaderByteCode(g_fullscreen_vs); @@ -322,6 +344,7 @@ void CRP::Init() 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(); @@ -464,6 +487,7 @@ void CRP::ProcessShader(shader_t& shader) { if(shader.isOpaque) { + prepass.ProcessShader(shader); opaque.ProcessShader(shader); } else @@ -553,6 +577,7 @@ 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; @@ -598,6 +623,7 @@ void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit); CmdEndBarrier(); CmdClearColorTarget(renderTarget, cmd.clearColor, &rect); + prepass.Draw(newCmd); opaque.Draw(newCmd); fog.Draw(); transp.Draw(newCmd); @@ -620,6 +646,7 @@ void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) } else { + prepass.Draw(cmd); opaque.Draw(cmd); fog.Draw(); transp.Draw(cmd); diff --git a/code/renderer/crp_opaque.cpp b/code/renderer/crp_opaque.cpp index de19b40..e345cfd 100644 --- a/code/renderer/crp_opaque.cpp +++ b/code/renderer/crp_opaque.cpp @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -65,47 +65,17 @@ void WorldOpaque::Draw(const drawSceneViewCommand_t& cmd) backEnd.refdef = cmd.refdef; backEnd.viewParms = cmd.viewParms; - - if(backEnd.viewParms.isPortal) - { - float plane[4]; - plane[0] = backEnd.viewParms.portalPlane.normal[0]; - plane[1] = backEnd.viewParms.portalPlane.normal[1]; - plane[2] = backEnd.viewParms.portalPlane.normal[2]; - plane[3] = backEnd.viewParms.portalPlane.dist; - - float plane2[4]; - plane2[0] = DotProduct(backEnd.viewParms.orient.axis[0], plane); - plane2[1] = DotProduct(backEnd.viewParms.orient.axis[1], plane); - plane2[2] = DotProduct(backEnd.viewParms.orient.axis[2], plane); - plane2[3] = DotProduct(plane, backEnd.viewParms.orient.origin) - plane[3]; - - float* o = plane; - const float* m = s_flipMatrix; - const float* v = plane2; - o[0] = m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3]; - o[1] = m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3]; - o[2] = m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3]; - o[3] = m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3]; - - memcpy(clipPlane, plane, sizeof(clipPlane)); - } - else - { - memset(clipPlane, 0, sizeof(clipPlane)); - } + RB_CreateClipPlane(clipPlane); CmdSetViewportAndScissor(backEnd.viewParms); batchOldDepthHack = false; batchDepthHack = false; CmdBeginBarrier(); - CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); + CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthReadBit); CmdBufferBarrier(srp.traceRenderBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); - CmdClearDepthStencilTarget(crp.depthTexture, true, 0.0f); - GeoBuffers& db = crp.dynBuffers[GetFrameIndex()]; db.BeginUpload(); @@ -207,16 +177,13 @@ void WorldOpaque::ProcessShader(shader_t& shader) desc.vertexShader = g_opaque_vs; desc.pixelShader = g_opaque_ps; desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Position, DataType::Float32, 3, 0); - desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 2, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 3, 0); desc.vertexLayout.AddAttribute(a++, ShaderSemantic::TexCoord, DataType::Float32, 2, 0); desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Color, DataType::UNorm8, 4, 0); desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; - desc.depthStencil.depthComparison = - (stateBits & GLS_DEPTHFUNC_EQUAL) != 0 ? - ComparisonFunction::Equal : - ComparisonFunction::GreaterEqual; - desc.depthStencil.enableDepthTest = (stateBits & GLS_DEPTHTEST_DISABLE) == 0; - desc.depthStencil.enableDepthWrites = (stateBits & GLS_DEPTHMASK_TRUE) != 0; + desc.depthStencil.depthComparison = shader.isSky ? ComparisonFunction::GreaterEqual : ComparisonFunction::Equal; + desc.depthStencil.enableDepthTest = true; + desc.depthStencil.enableDepthWrites = false; desc.rasterizer.cullMode = shader.cullType; desc.rasterizer.polygonOffset = shader.polygonOffset != 0; desc.rasterizer.clampDepth = clampDepth; diff --git a/code/renderer/crp_prepass.cpp b/code/renderer/crp_prepass.cpp new file mode 100644 index 0000000..e249e47 --- /dev/null +++ b/code/renderer/crp_prepass.cpp @@ -0,0 +1,294 @@ +/* +=========================================================================== +Copyright (C) 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 - full opaque pre-pass + + +#include "crp_local.h" +#include "compshaders/crp/prepass.h" + + +#pragma pack(push, 4) + +struct PrepassVertexRC +{ + float modelViewMatrix[16]; + float projectionMatrix[16]; + float normalMatrix[16]; + float clipPlane[4]; +}; + +struct PrepassPixelRC +{ + uint32_t textureIndex; + uint32_t samplerIndex; + uint32_t alphaTest; +}; + +#pragma pack(pop) + + +void Prepass::Init() +{ + psoCache.Init(psoCacheEntries, ARRAY_LEN(psoCacheEntries)); +} + +void Prepass::Draw(const drawSceneViewCommand_t& cmd) +{ + if(cmd.numDrawSurfs - cmd.numTranspSurfs <= 0) + { + return; + } + + srp.renderMode = RenderMode::World; + + backEnd.refdef = cmd.refdef; + backEnd.viewParms = cmd.viewParms; + RB_CreateClipPlane(clipPlane); + + CmdSetViewportAndScissor(backEnd.viewParms); + batchOldDepthHack = false; + batchDepthHack = false; + + CmdBeginBarrier(); + CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); + CmdTextureBarrier(crp.normalTexture, ResourceStates::RenderTargetBit); + CmdTextureBarrier(crp.motionVectorTexture, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + + CmdClearDepthStencilTarget(crp.depthTexture, true, 0.0f); + CmdClearColorTarget(crp.normalTexture, vec4_zero, NULL); + CmdClearColorTarget(crp.motionVectorTexture, vec4_zero, NULL); + + GeoBuffers& db = crp.dynBuffers[GetFrameIndex()]; + db.BeginUpload(); + + SCOPED_RENDER_PASS("Pre-pass", 1.0f, 0.5f, 0.5f); + + const HTexture renderTargets[] = { crp.normalTexture, crp.motionVectorTexture }; + CmdBindRenderTargets(ARRAY_LEN(renderTargets), renderTargets, &crp.depthTexture); + CmdBindVertexBuffers(ARRAY_LEN(db.vertexBuffers), db.vertexBuffers, db.vertexBufferStrides, NULL); + CmdBindIndexBuffer(db.indexBuffer.buffer, IndexType::UInt32, 0); + + const drawSurf_t* drawSurfs = cmd.drawSurfs; + const int surfCount = cmd.numDrawSurfs - cmd.numTranspSurfs; + const double originalTime = backEnd.refdef.floatTime; + + const shader_t* shader = NULL; + const shader_t* oldShader = NULL; + int oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + + tess.numVertexes = 0; + tess.numIndexes = 0; + + int ds; + const drawSurf_t* drawSurf; + for(ds = 0, drawSurf = drawSurfs; ds < surfCount; ++ds, ++drawSurf) + { + int entityNum; + R_DecomposeSort(drawSurf->sort, &entityNum, &shader); + Q_assert(shader != NULL); + Q_assert(shader->isOpaque); + + if(shader->isSky || shader->numStages <= 0) + { + continue; + } + + const bool shaderChanged = shader != oldShader; + const bool entityChanged = entityNum != oldEntityNum; + if(shaderChanged || entityChanged) + { + oldShader = shader; + oldEntityNum = entityNum; + EndBatch(); + BeginBatch(shader); + tess.greyscale = drawSurf->greyscale; + } + + if(entityChanged) + { + UpdateEntityData(batchDepthHack, entityNum, originalTime); + } + + R_TessellateSurface(drawSurf->surface); + } + + backEnd.refdef.floatTime = originalTime; + + EndBatch(); + + db.EndUpload(); + + // restores the potentially "hacked" depth range as well + CmdSetViewportAndScissor(backEnd.viewParms); + batchOldDepthHack = false; + batchDepthHack = false; +} + +void Prepass::ProcessShader(shader_t& shader) +{ + if(shader.isSky) + { + return; + } + + Q_assert(shader.isOpaque); + + if(shader.numStages < 1) + { + return; + } + + const bool clampDepth = r_depthClamp->integer != 0 || shader.isSky; + + const shaderStage_t& stage = *shader.stages[0]; + const unsigned int stateBits = stage.stateBits & (~GLS_POLYMODE_LINE); + int a = 0; + + Q_assert((stateBits & GLS_DEPTHTEST_DISABLE) == 0); // depth test enabled + Q_assert((stateBits & GLS_DEPTHMASK_TRUE) != 0); // depth write enabled + Q_assert((stateBits & GLS_DEPTHFUNC_EQUAL) == 0); // depth comparison GE + + // @NOTE: we are not using any CTOR because we deliberately want to 0-init the struct + // this is necessary for padding bytes not to mess up comparisons in the PSO cache + GraphicsPipelineDesc desc = {}; + desc.name = "pre-pass"; + desc.rootSignature = RHI_MAKE_NULL_HANDLE(); + desc.shortLifeTime = true; // the PSO cache is only valid for this map! + desc.vertexShader = g_prepass_vs; + desc.pixelShader = g_prepass_ps; + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Position, DataType::Float32, 3, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 3, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::TexCoord, DataType::Float32, 2, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Color, DataType::UNorm8, 4, 0); + desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; + desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; + desc.depthStencil.enableDepthTest = true; + desc.depthStencil.enableDepthWrites = true; + desc.rasterizer.cullMode = shader.cullType; + desc.rasterizer.polygonOffset = shader.polygonOffset != 0; + desc.rasterizer.clampDepth = clampDepth; + desc.AddRenderTarget(0, TextureFormat::RG32_SNorm); + desc.AddRenderTarget(0, TextureFormat::RG32_Float); + + pipeline_t& p = shader.prepassPipeline; + p.firstStage = 0; + p.numStages = 1; + p.pipeline = psoCache.AddPipeline(desc, va("pre-pass %d", psoCache.entryCount)); + desc.rasterizer.cullMode = GetMirrorredCullType(desc.rasterizer.cullMode); + p.mirrorPipeline = psoCache.AddPipeline(desc, va("pre-pass %d mirrored", psoCache.entryCount)); +} + +void Prepass::TessellationOverflow() +{ + EndBatch(); + BeginBatch(tess.shader); +} + +void Prepass::BeginBatch(const shader_t* shader) +{ + tess.tessellator = Tessellator::Prepass; + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.depthFade = DFT_NONE; + tess.deformsPreApplied = qfalse; + tess.xstages = (const shaderStage_t**)shader->stages; + tess.shader = shader; + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if(tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) + { + tess.shaderTime = tess.shader->clampTime; + } +} + +void Prepass::EndBatch() +{ + PrepassVertexRC vertexRC = {}; + PrepassPixelRC pixelRC = {}; + float tempMatrix[16]; + + const int vertexCount = tess.numVertexes; + const int indexCount = tess.numIndexes; + if(vertexCount <= 0 || + indexCount <= 0 || + tess.shader->numStages <= 0) + { + goto clean_up; + } + + const shader_t* const shader = tess.shader; + + GeoBuffers& db = crp.dynBuffers[GetFrameIndex()]; + if(!db.CanAdd(vertexCount, indexCount, shader->numStages)) + { + Q_assert(!"World surface geometry buffer too small!"); + goto clean_up; + } + + RB_DeformTessGeometry(0, vertexCount, 0, indexCount); + db.UploadBase(); + + if(batchDepthHack != batchOldDepthHack) + { + const viewParms_t& vp = backEnd.viewParms; + CmdSetViewport(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight, batchDepthHack ? 0.7f : 0.0f, 1.0f); + batchOldDepthHack = batchDepthHack; + } + + memcpy(vertexRC.modelViewMatrix, backEnd.orient.modelMatrix, sizeof(vertexRC.modelViewMatrix)); + memcpy(vertexRC.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(vertexRC.projectionMatrix)); + memcpy(vertexRC.clipPlane, clipPlane, sizeof(vertexRC.clipPlane)); + R_InvMatrix(backEnd.modelMatrix, tempMatrix); + R_TransposeMatrix(tempMatrix, vertexRC.normalMatrix); + CmdSetGraphicsRootConstants(0, sizeof(vertexRC), &vertexRC); + + const shaderStage_t* const stage = shader->stages[0]; + + R_ComputeColors(stage, tess.svars[0], 0, vertexCount); + R_ComputeTexCoords(stage, tess.svars[0], 0, vertexCount, qfalse); + db.UploadStage(0); + + const pipeline_t& pipeline = shader->prepassPipeline; + const int psoIndex = backEnd.viewParms.isMirror ? pipeline.mirrorPipeline : pipeline.pipeline; + Q_assert(psoIndex > 0); + CmdBindPipeline(psoCache.entries[psoIndex].handle); + + const image_t* image = GetBundleImage(stage->bundle); + const uint32_t texIdx = image->textureIndex; + const uint32_t sampIdx = GetSamplerIndex(image); + const uint32_t alphaTest = AlphaTestShaderConstFromStateBits(stage->stateBits); + Q_assert(sampIdx < ARRAY_LEN(crp.samplers)); + + pixelRC.textureIndex = texIdx; + pixelRC.samplerIndex = sampIdx; + pixelRC.alphaTest = alphaTest; + CmdSetGraphicsRootConstants(sizeof(vertexRC), sizeof(pixelRC), &pixelRC); + + db.DrawStage(vertexCount, indexCount); + + db.EndBaseBatch(vertexCount); + +clean_up: + tess.tessellator = Tessellator::None; + tess.numVertexes = 0; + tess.numIndexes = 0; +} diff --git a/code/renderer/crp_tone_map.cpp b/code/renderer/crp_tone_map.cpp index 9e72f43..acf68f2 100644 --- a/code/renderer/crp_tone_map.cpp +++ b/code/renderer/crp_tone_map.cpp @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). diff --git a/code/renderer/crp_transp_draw.cpp b/code/renderer/crp_transp_draw.cpp index 91ac17c..30ec33a 100644 --- a/code/renderer/crp_transp_draw.cpp +++ b/code/renderer/crp_transp_draw.cpp @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -83,35 +83,7 @@ void WorldTransp::Draw(const drawSceneViewCommand_t& cmd) backEnd.refdef = cmd.refdef; backEnd.viewParms = cmd.viewParms; - - if(backEnd.viewParms.isPortal) - { - float plane[4]; - plane[0] = backEnd.viewParms.portalPlane.normal[0]; - plane[1] = backEnd.viewParms.portalPlane.normal[1]; - plane[2] = backEnd.viewParms.portalPlane.normal[2]; - plane[3] = backEnd.viewParms.portalPlane.dist; - - float plane2[4]; - plane2[0] = DotProduct(backEnd.viewParms.orient.axis[0], plane); - plane2[1] = DotProduct(backEnd.viewParms.orient.axis[1], plane); - plane2[2] = DotProduct(backEnd.viewParms.orient.axis[2], plane); - plane2[3] = DotProduct(plane, backEnd.viewParms.orient.origin) - plane[3]; - - float* o = plane; - const float* m = s_flipMatrix; - const float* v = plane2; - o[0] = m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3]; - o[1] = m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3]; - o[2] = m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3]; - o[3] = m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3]; - - memcpy(clipPlane, plane, sizeof(clipPlane)); - } - else - { - memset(clipPlane, 0, sizeof(clipPlane)); - } + RB_CreateClipPlane(clipPlane); SCOPED_RENDER_PASS("Transparent", 1.0f, 0.5f, 0.5f); @@ -222,7 +194,7 @@ void WorldTransp::ProcessShader(shader_t& shader) desc.vertexShader = g_transp_draw_vs; desc.pixelShader = g_transp_draw_ps; desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Position, DataType::Float32, 3, 0); - desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 2, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 3, 0); desc.vertexLayout.AddAttribute(a++, ShaderSemantic::TexCoord, DataType::Float32, 2, 0); desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Color, DataType::UNorm8, 4, 0); desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; diff --git a/code/renderer/crp_transp_resolve.cpp b/code/renderer/crp_transp_resolve.cpp index 8279094..5fad79c 100644 --- a/code/renderer/crp_transp_resolve.cpp +++ b/code/renderer/crp_transp_resolve.cpp @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). diff --git a/code/renderer/grp_main.cpp b/code/renderer/grp_main.cpp index 042e16e..ca26e31 100644 --- a/code/renderer/grp_main.cpp +++ b/code/renderer/grp_main.cpp @@ -552,7 +552,7 @@ uint32_t GRP::CreatePSO(CachedPSO& cache, const char* name) desc.vertexShader = vertexShaderByteCodes[cache.stageCount - 1]; desc.pixelShader = pixelShaderByteCode; desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Position, DataType::Float32, 3, 0); - desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 2, 0); + desc.vertexLayout.AddAttribute(a++, ShaderSemantic::Normal, DataType::Float32, 3, 0); for(int s = 0; s < cache.stageCount; ++s) { desc.vertexLayout.AddAttribute(a++, ShaderSemantic::TexCoord, DataType::Float32, 2, 0); diff --git a/code/renderer/grp_world.cpp b/code/renderer/grp_world.cpp index 0672803..2143d21 100644 --- a/code/renderer/grp_world.cpp +++ b/code/renderer/grp_world.cpp @@ -363,34 +363,7 @@ void World::Begin() { srp.renderMode = RenderMode::World; - if(backEnd.viewParms.isPortal) - { - float plane[4]; - plane[0] = backEnd.viewParms.portalPlane.normal[0]; - plane[1] = backEnd.viewParms.portalPlane.normal[1]; - plane[2] = backEnd.viewParms.portalPlane.normal[2]; - plane[3] = backEnd.viewParms.portalPlane.dist; - - float plane2[4]; - plane2[0] = DotProduct(backEnd.viewParms.orient.axis[0], plane); - plane2[1] = DotProduct(backEnd.viewParms.orient.axis[1], plane); - plane2[2] = DotProduct(backEnd.viewParms.orient.axis[2], plane); - plane2[3] = DotProduct(plane, backEnd.viewParms.orient.origin) - plane[3]; - - float* o = plane; - const float* m = s_flipMatrix; - const float* v = plane2; - o[0] = m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3]; - o[1] = m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3]; - o[2] = m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3]; - o[3] = m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3]; - - memcpy(clipPlane, plane, sizeof(clipPlane)); - } - else - { - memset(clipPlane, 0, sizeof(clipPlane)); - } + RB_CreateClipPlane(clipPlane); CmdSetViewportAndScissor(backEnd.viewParms); batchOldDepthHack = false; diff --git a/code/renderer/rhi_d3d12.cpp b/code/renderer/rhi_d3d12.cpp index 98e7857..653ad27 100644 --- a/code/renderer/rhi_d3d12.cpp +++ b/code/renderer/rhi_d3d12.cpp @@ -1715,6 +1715,8 @@ namespace RHI case TextureFormat::R8_UNorm: return DXGI_FORMAT_R8_UNORM; case TextureFormat::R10G10B10A2_UNorm: return DXGI_FORMAT_R10G10B10A2_UNORM; case TextureFormat::R32_UInt: return DXGI_FORMAT_R32_UINT; + case TextureFormat::RG32_SNorm: return DXGI_FORMAT_R16G16_SNORM; + case TextureFormat::RG32_Float: return DXGI_FORMAT_R16G16_FLOAT; default: Q_assert(!"Unsupported texture format"); return DXGI_FORMAT_R8G8B8A8_UNORM; } } diff --git a/code/renderer/rhi_local.h b/code/renderer/rhi_local.h index 4f56fa9..302d708 100644 --- a/code/renderer/rhi_local.h +++ b/code/renderer/rhi_local.h @@ -170,6 +170,8 @@ namespace RHI Depth24_Stencil8, R10G10B10A2_UNorm, R32_UInt, + RG32_SNorm, + RG32_Float, Count }; }; diff --git a/code/renderer/shaders/crp/common.hlsli b/code/renderer/shaders/crp/common.hlsli index dd7a53c..e035b7f 100644 --- a/code/renderer/shaders/crp/common.hlsli +++ b/code/renderer/shaders/crp/common.hlsli @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -138,7 +138,45 @@ float EaseInQuad(float x) return x * x; } -float SmoothStep(float x) +float smoothstep01(float x) { return smoothstep(0.0, 1.0, x); } + +// Oct*: octahedron normal vector encoding +// original code from "A Survey of Efficient Representations for Independent Unit Vectors" +// further improved by Krzysztof Narkowicz and Rune Stubbe + +float2 OctWrap(float2 v) +{ + return (1.0 - abs(v.yx)) * (v.xy >= 0.0 ? 1.0 : -1.0); +} + +float2 OctEncode(float3 n) +{ + n /= (abs(n.x) + abs(n.y) + abs(n.z)); + n.xy = n.z >= 0.0 ? n.xy : OctWrap(n.xy); + n.xy = n.xy * 0.5 + 0.5; + + return n.xy; +} + +float3 OctDecode(float2 f) +{ + f = f * 2.0 - 1.0; + float3 n = float3(f.x, f.y, 1.0 - abs(f.x) - abs(f.y)); + float t = saturate(-n.z); + n.xy += n.xy >= 0.0 ? -t : t; + + return normalize(n); +} + +float3 GetPositionFromDepth(float2 tc01, float depthZW, float4x4 invMatrix) +{ + float x = tc01.x * 2.0 - 1.0; + float y = (1.0 - tc01.y) * 2.0 - 1.0; + float4 position = mul(float4(x, y, depthZW, 1.0), invMatrix); + float3 result = position.xyz / position.w; + + return result; +} diff --git a/code/renderer/shaders/crp/opaque.hlsl b/code/renderer/shaders/crp/opaque.hlsl index 624bb21..d9c7cb1 100644 --- a/code/renderer/shaders/crp/opaque.hlsl +++ b/code/renderer/shaders/crp/opaque.hlsl @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -76,9 +76,7 @@ VOut vs(VIn input) return output; } -// @TODO: enable early-Z here once the full pre-pass is implemented -// this will prevent fragments failing the depth test from writing to the shader ID buffer -//[earlydepthstencil] +[earlydepthstencil] float4 ps(VOut input) : SV_Target { // @TODO: Voronoi tiling diff --git a/code/renderer/shaders/crp/prepass.hlsl b/code/renderer/shaders/crp/prepass.hlsl new file mode 100644 index 0000000..b2673ab --- /dev/null +++ b/code/renderer/shaders/crp/prepass.hlsl @@ -0,0 +1,98 @@ +/* +=========================================================================== +Copyright (C) 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 . +=========================================================================== +*/ +// generic shader for the prepass of opaque surfaces + + +#include "common.hlsli" +#include "world.h.hlsli" +#include "world.hlsli" + + +cbuffer RootConstants +{ + // geometry + matrix modelViewMatrix; + matrix projectionMatrix; + matrix normalMatrix; + float4 clipPlane; + + // general + uint textureIndex; + uint samplerIndex; + uint alphaTest; +}; + +struct VIn +{ + float3 position : POSITION; + float3 normal : NORMAL; + float2 texCoords : TEXCOORD0; + float4 color : COLOR0; +}; + +struct VOut +{ + float4 position : SV_Position; + float3 normal : NORMAL; + float2 texCoords : TEXCOORD0; + float4 color : COLOR0; + float clipDist : SV_ClipDistance0; +}; + +VOut vs(VIn input) +{ + float4 positionVS = mul(modelViewMatrix, float4(input.position.xyz, 1)); + + VOut output; + output.position = mul(projectionMatrix, positionVS); + output.normal = mul(normalMatrix, float4(input.normal, 0)).xyz; + output.texCoords = input.texCoords; + output.color = input.color; + output.clipDist = dot(positionVS, clipPlane); + + return output; +} + +struct POut +{ + float2 normal : SV_Target0; + float2 motionVector : SV_Target1; +}; + +POut ps(VOut input) +{ + if(alphaTest != ATEST_NONE) + { + Texture2D texture0 = ResourceDescriptorHeap[textureIndex]; + SamplerState sampler0 = ResourceDescriptorHeap[samplerIndex]; + float4 dst = texture0.Sample(sampler0, input.texCoords) * input.color; + if(FailsAlphaTest(dst.a, alphaTest)) + { + discard; + } + } + + POut output; + output.normal = OctEncode(normalize(input.normal)); + output.motionVector = float2(0, 0); // @TODO: + + return output; +} diff --git a/code/renderer/srp_main.cpp b/code/renderer/srp_main.cpp index 57f8d17..661dd1a 100644 --- a/code/renderer/srp_main.cpp +++ b/code/renderer/srp_main.cpp @@ -181,7 +181,7 @@ void UpdateEntityData(bool& depthHack, int entityNum, double originalTime) tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; // set up the transformation matrix - R_RotateForEntity(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orient); + R_RotateForEntity(backEnd.currentEntity, &backEnd.viewParms, &backEnd.orient, backEnd.modelMatrix); if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) { @@ -193,6 +193,7 @@ void UpdateEntityData(bool& depthHack, int entityNum, double originalTime) backEnd.currentEntity = &tr.worldEntity; backEnd.refdef.floatTime = originalTime; backEnd.orient = backEnd.viewParms.world; + R_MakeIdentityMatrix(backEnd.modelMatrix); // we have to reset the shaderTime as well otherwise image animations on // the world (like water) continue with the wrong frame tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; diff --git a/code/renderer/tr_backend.cpp b/code/renderer/tr_backend.cpp index 107a33f..7fbb2ec 100644 --- a/code/renderer/tr_backend.cpp +++ b/code/renderer/tr_backend.cpp @@ -64,8 +64,7 @@ void RB_PopShader() // used when a player has predicted a teleport, but hasn't arrived yet float RB_HyperspaceColor() { - if ( r_teleporterFlash->integer == 0 ) - { + if (r_teleporterFlash->integer == 0) { return 0.0f; } @@ -73,3 +72,33 @@ float RB_HyperspaceColor() return c; } + + +void RB_CreateClipPlane( float* clipPlane ) +{ + if (backEnd.viewParms.isPortal) { + float plane[4]; + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + float plane2[4]; + plane2[0] = DotProduct(backEnd.viewParms.orient.axis[0], plane); + plane2[1] = DotProduct(backEnd.viewParms.orient.axis[1], plane); + plane2[2] = DotProduct(backEnd.viewParms.orient.axis[2], plane); + plane2[3] = DotProduct(plane, backEnd.viewParms.orient.origin) - plane[3]; + + float* o = plane; + const float* m = s_flipMatrix; + const float* v = plane2; + o[0] = m[0] * v[0] + m[4] * v[1] + m[8] * v[2] + m[12] * v[3]; + o[1] = m[1] * v[0] + m[5] * v[1] + m[9] * v[2] + m[13] * v[3]; + o[2] = m[2] * v[0] + m[6] * v[1] + m[10] * v[2] + m[14] * v[3]; + o[3] = m[3] * v[0] + m[7] * v[1] + m[11] * v[2] + m[15] * v[3]; + + memcpy(clipPlane, plane, 4 * sizeof(float)); + } else { + memset(clipPlane, 0, 4 * sizeof(float)); + } +} diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h index d866050..33fb694 100644 --- a/code/renderer/tr_local.h +++ b/code/renderer/tr_local.h @@ -435,6 +435,8 @@ struct shader_t { pipeline_t transpPipelines[MAX_SHADER_STAGES]; int numTranspPipelines; + pipeline_t prepassPipeline; + shader_t* next; }; @@ -857,6 +859,7 @@ typedef struct { viewParms_t viewParms; orientationr_t orient; trRefEntity_t* currentEntity; + float modelMatrix[16]; // real model matrix, not model-view qbool projection2D; // if qtrue, drawstretchpic doesn't need to change modes byte color2D[4]; @@ -1121,7 +1124,7 @@ int R_CullLocalBox( const vec3_t bounds[2] ); int R_CullPointAndRadius( const vec3_t origin, float radius ); int R_CullLocalPointAndRadius( const vec3_t origin, float radius ); -void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, orientationr_t* orient ); +void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, orientationr_t* orient, float* modelMatrix = NULL ); void R_CreateWorldModelMatrix( const vec3_t origin, const vec3_t axis[3], float* viewMatrix ); typedef void (*updateAnimatedImage_t)( image_t* image, int w, int h, const byte* data, qbool dirty ); @@ -1572,8 +1575,8 @@ void RB_TakeVideoFrameCmd( const videoFrameCommand_t* cmd ); void RB_PushSingleStageShader( int stateBits, cullType_t cullType ); void RB_PopShader(); - float RB_HyperspaceColor(); +void RB_CreateClipPlane( float* clipPlane ); void RB_DrawSky(); void R_BuildCloudData(); diff --git a/code/renderer/tr_main.cpp b/code/renderer/tr_main.cpp index 5419397..6c92cfe 100644 --- a/code/renderer/tr_main.cpp +++ b/code/renderer/tr_main.cpp @@ -493,7 +493,7 @@ Does NOT produce any GL calls Called by both the front end and the back end ================= */ -void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, orientationr_t* orient ) +void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, orientationr_t* orient, float* modelMatrix ) { float glMatrix[16]; vec3_t delta; @@ -530,6 +530,10 @@ void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, glMatrix[11] = 0; glMatrix[15] = 1; + if ( modelMatrix != NULL ) { + Com_Memcpy( modelMatrix, glMatrix, sizeof( glMatrix ) ); + } + R_MultMatrix( glMatrix, viewParms->world.modelMatrix, orient->modelMatrix ); // calculate the viewer origin in the model's space diff --git a/code/shadercomp/shadercomp.cpp b/code/shadercomp/shadercomp.cpp index 723a6d8..1cf23bc 100644 --- a/code/shadercomp/shadercomp.cpp +++ b/code/shadercomp/shadercomp.cpp @@ -388,6 +388,7 @@ void ProcessCRP() CompileCompute("mip_1.h", "mip_1.hlsl", "mip_1"); CompileCompute("mip_2.h", "mip_2.hlsl", "mip_2"); CompileCompute("mip_3.h", "mip_3.hlsl", "mip_3"); + CompileGraphics("prepass.h", "prepass.hlsl", "prepass"); CompileGraphics("opaque.h", "opaque.hlsl", "opaque"); CompileGraphics("transp_draw.h", "transp_draw.hlsl", "transp_draw"); CompilePixelShader("transp_resolve.h", "transp_resolve.hlsl", "transp_resolve"); diff --git a/makefiles/windows_vs2019/renderer.vcxproj b/makefiles/windows_vs2019/renderer.vcxproj index bc7d208..0c62fc3 100644 --- a/makefiles/windows_vs2019/renderer.vcxproj +++ b/makefiles/windows_vs2019/renderer.vcxproj @@ -134,6 +134,7 @@ + @@ -236,6 +237,9 @@ true + + true + true diff --git a/makefiles/windows_vs2019/renderer.vcxproj.filters b/makefiles/windows_vs2019/renderer.vcxproj.filters index c429822..c11dcc9 100644 --- a/makefiles/windows_vs2019/renderer.vcxproj.filters +++ b/makefiles/windows_vs2019/renderer.vcxproj.filters @@ -38,6 +38,7 @@ + @@ -140,6 +141,9 @@ shaders\crp + + shaders\crp + shaders\crp diff --git a/makefiles/windows_vs2022/renderer.vcxproj b/makefiles/windows_vs2022/renderer.vcxproj index a8a5a61..d82fb8c 100644 --- a/makefiles/windows_vs2022/renderer.vcxproj +++ b/makefiles/windows_vs2022/renderer.vcxproj @@ -136,6 +136,7 @@ + @@ -238,6 +239,9 @@ true + + true + true diff --git a/makefiles/windows_vs2022/renderer.vcxproj.filters b/makefiles/windows_vs2022/renderer.vcxproj.filters index c429822..c11dcc9 100644 --- a/makefiles/windows_vs2022/renderer.vcxproj.filters +++ b/makefiles/windows_vs2022/renderer.vcxproj.filters @@ -38,6 +38,7 @@ + @@ -140,6 +141,9 @@ shaders\crp + + shaders\crp + shaders\crp