From 0cae0a95456243c9712236cf4a74556430c0dbb5 Mon Sep 17 00:00:00 2001
From: myT <>
Date: Fri, 19 Jan 2024 23:57:40 +0100
Subject: [PATCH] added pre-pass w/ normals, unified clip plane generation
motion vectors to be done later
---
code/renderer/crp_local.h | 25 +-
code/renderer/crp_main.cpp | 27 ++
code/renderer/crp_opaque.cpp | 47 +--
code/renderer/crp_prepass.cpp | 294 ++++++++++++++++++
code/renderer/crp_tone_map.cpp | 2 +-
code/renderer/crp_transp_draw.cpp | 34 +-
code/renderer/crp_transp_resolve.cpp | 2 +-
code/renderer/grp_main.cpp | 2 +-
code/renderer/grp_world.cpp | 29 +-
code/renderer/rhi_d3d12.cpp | 2 +
code/renderer/rhi_local.h | 2 +
code/renderer/shaders/crp/common.hlsli | 42 ++-
code/renderer/shaders/crp/opaque.hlsl | 6 +-
code/renderer/shaders/crp/prepass.hlsl | 98 ++++++
code/renderer/srp_main.cpp | 3 +-
code/renderer/tr_backend.cpp | 33 +-
code/renderer/tr_local.h | 7 +-
code/renderer/tr_main.cpp | 6 +-
code/shadercomp/shadercomp.cpp | 1 +
makefiles/windows_vs2019/renderer.vcxproj | 4 +
.../windows_vs2019/renderer.vcxproj.filters | 4 +
makefiles/windows_vs2022/renderer.vcxproj | 4 +
.../windows_vs2022/renderer.vcxproj.filters | 4 +
23 files changed, 563 insertions(+), 115 deletions(-)
create mode 100644 code/renderer/crp_prepass.cpp
create mode 100644 code/renderer/shaders/crp/prepass.hlsl
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