cnq3/code/renderer/crp_prepass.cpp

467 lines
13 KiB
C++
Raw Normal View History

/*
===========================================================================
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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Cinematic Rendering Pipeline - full opaque pre-pass
#include "crp_local.h"
#include "compshaders/crp/prepass.h"
#include "compshaders/crp/prepass_bary.h"
#include "compshaders/crp/fullscreen.h"
#include "compshaders/crp/skybox_motion.h"
#pragma pack(push, 4)
struct PrepassRC
{
float modelViewMatrix[16];
float modelMatrix[16];
float prevModelMatrix[16];
float normalMatrix[9];
float motionBlurScale;
uint32_t textureIndex;
uint32_t samplerIndex;
uint32_t alphaTest;
uint32_t motionBlurMode;
};
#pragma pack(pop)
struct EntityData
{
float modelMatrix[16];
int uniqueId;
};
struct EntityTracker
{
void BeginFrame()
{
Q_assert(!begun);
Q_assert(!enabled);
const viewParms_t& vp = backEnd.viewParms;
enabled = IsViewportFullscreen(vp) && !vp.isPortal;
begun = true;
}
void EndFrame()
{
Q_assert(begun);
begun = false;
if(enabled)
{
enabled = false;
EntityArray* prevEntsCopy = prevEnts;
prevEnts = currEnts;
currEnts = prevEntsCopy;
currEnts->Clear();
}
}
void CompareEntity(float* prevModelMatrix, int uniqueId, const float* modelMatrix)
{
Q_assert(begun);
if(!enabled || uniqueId == 0)
{
memcpy(prevModelMatrix, modelMatrix, 16 * sizeof(float));
return;
}
EntityData* prevEnt = NULL;
for(uint32_t i = 0, end = prevEnts->count; i < end; i++)
{
EntityData& ent = (*prevEnts)[i];
if(ent.uniqueId == uniqueId)
{
prevEnt = &ent;
break;
}
}
if(prevEnt != NULL)
{
memcpy(prevModelMatrix, prevEnt->modelMatrix, 16 * sizeof(float));
}
else
{
memcpy(prevModelMatrix, modelMatrix, 16 * sizeof(float));
}
EntityData* currEnt = NULL;
for(uint32_t i = 0, end = currEnts->count; i < end; i++)
{
EntityData& ent = (*currEnts)[i];
if(ent.uniqueId == uniqueId)
{
currEnt = &ent;
break;
}
}
if(currEnt != NULL)
{
memcpy(currEnt->modelMatrix, modelMatrix, 16 * sizeof(float));
}
else
{
EntityData ent;
memcpy(ent.modelMatrix, modelMatrix, 16 * sizeof(float));
ent.uniqueId = uniqueId;
currEnts->Add(ent);
}
}
private:
typedef RHI::StaticArray<EntityData, 16384> EntityArray;
EntityArray entities[2];
EntityArray* currEnts = &entities[0];
EntityArray* prevEnts = &entities[1];
bool enabled;
bool begun;
};
static EntityTracker et;
void Prepass::Init()
{
psoCache.Init(psoCacheEntries, ARRAY_LEN(psoCacheEntries));
{
GraphicsPipelineDesc desc("Skybox Motion Vectors");
MakeFullScreenPipeline(desc, ShaderByteCode(g_skybox_motion_ps));
desc.AddRenderTarget(0, TextureFormat::R16G16_Float);
skyboxMotionPipeline = CreateGraphicsPipeline(desc);
}
}
void Prepass::Draw(const drawSceneViewCommand_t& cmd)
{
if(cmd.numDrawSurfs - cmd.numTranspSurfs <= 0)
{
return;
}
srp.renderMode = RenderMode::World;
SCOPED_RENDER_PASS("Pre-pass", 1.0f, 0.5f, 0.5f);
backEnd.refdef = cmd.refdef;
backEnd.viewParms = cmd.viewParms;
et.BeginFrame();
CmdSetViewportAndScissor(backEnd.viewParms);
batchOldDepthHack = false;
batchDepthHack = false;
CmdBeginBarrier();
CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit);
CmdTextureBarrier(crp.normalTexture, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.motionVectorTexture, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.motionVectorMBTexture, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::RenderTargetBit);
CmdEndBarrier();
CmdClearDepthStencilTarget(crp.depthTexture, true, 0.0f);
CmdClearColorTarget(crp.normalTexture, vec4_zero, NULL);
CmdClearColorTarget(crp.shadingPositionTexture, vec4_zero, NULL);
// clear the motion vectors with skybox values
CmdBeginDebugLabel("Skybox Motion Vectors");
{
CmdBindRenderTargets(1, &crp.motionVectorTexture, NULL);
CmdBindPipeline(skyboxMotionPipeline);
CmdDraw(3, 0);
if(crp_mblur->integer & MotionBlurModes::CameraBit)
{
CmdBindRenderTargets(1, &crp.motionVectorMBTexture, NULL);
CmdBindPipeline(skyboxMotionPipeline);
CmdDraw(3, 0);
}
else
{
CmdClearColorTarget(crp.motionVectorMBTexture, vec4_zero, NULL);
}
}
CmdEndDebugLabel();
GeoBuffers& db = crp.dynBuffers[GetFrameIndex()];
db.BeginUpload();
const HTexture renderTargets[] =
{
crp.normalTexture,
crp.motionVectorTexture,
crp.motionVectorMBTexture,
crp.shadingPositionTexture
};
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;
batchEntityId = -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;
const bool idChanged = drawSurf->uniqueEntityId != batchEntityId;
if(shaderChanged || entityChanged || idChanged)
{
oldShader = shader;
oldEntityNum = entityNum;
EndBatch();
BeginBatch(shader);
tess.greyscale = drawSurf->greyscale;
batchEntityId = drawSurf->uniqueEntityId;
batchMotionScale = drawSurf->motionBlurScale;
}
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;
et.EndFrame();
}
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;
int a = 0;
#if defined(DEBUG)
const shaderStage_t& stage = *shader.stages[0];
const unsigned int stateBits = stage.stateBits & (~GLS_POLYMODE_LINE);
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
#endif
// @NOTE: we 0-init the struct so that padding bytes don't 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!
if(rhiInfo.hasBarycentrics)
{
desc.vertexShader.Set(g_prepass_bary_vs);
desc.pixelShader.Set(g_prepass_bary_ps);
}
else
{
desc.vertexShader.Set(g_prepass_vs);
desc.pixelShader.Set(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::R16G16_SNorm);
desc.AddRenderTarget(0, TextureFormat::R16G16_Float);
desc.AddRenderTarget(0, TextureFormat::R16G16_Float);
desc.AddRenderTarget(0, TextureFormat::R32G32B32A32_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()
{
PrepassRC rc = {};
float tempMatrix[16];
float normalMatrix[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;
}
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));
R_InvMatrix(backEnd.modelMatrix, tempMatrix);
R_TransposeMatrix(tempMatrix, normalMatrix);
2024-07-27 00:25:35 +00:00
// select the right motion vector code path in the shader for view weapons
// we should identify view weapons directly instead of using the depth hack flag
int mblurMode = crp_mblur->integer;
float mblurScale = batchMotionScale;
if(batchDepthHack && mblurMode == MotionBlurModes::CameraOnly)
{
mblurScale = 0.0f;
mblurMode = 0; // doesn't really matter but shows intent in the root constants
}
else if(batchDepthHack && mblurMode == MotionBlurModes::ObjectOnly)
{
mblurMode = MotionBlurModes::Full;
}
memcpy(rc.modelViewMatrix, backEnd.orient.modelMatrix, sizeof(rc.modelViewMatrix));
memcpy(rc.modelMatrix, backEnd.modelMatrix, sizeof(rc.modelMatrix));
et.CompareEntity(rc.prevModelMatrix, batchEntityId, backEnd.modelMatrix);
rc.normalMatrix[0] = normalMatrix[ 0];
rc.normalMatrix[1] = normalMatrix[ 1];
rc.normalMatrix[2] = normalMatrix[ 2];
rc.normalMatrix[3] = normalMatrix[ 4];
rc.normalMatrix[4] = normalMatrix[ 5];
rc.normalMatrix[5] = normalMatrix[ 6];
rc.normalMatrix[6] = normalMatrix[ 8];
rc.normalMatrix[7] = normalMatrix[ 9];
rc.normalMatrix[8] = normalMatrix[10];
2024-07-27 00:25:35 +00:00
rc.motionBlurScale = mblurScale;
rc.textureIndex = texIdx;
rc.samplerIndex = sampIdx;
rc.alphaTest = alphaTest;
2024-07-27 00:25:35 +00:00
rc.motionBlurMode = (uint32_t)mblurMode;
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
db.DrawStage(vertexCount, indexCount);
db.EndBaseBatch(vertexCount);
clean_up:
tess.tessellator = Tessellator::None;
tess.numVertexes = 0;
tess.numIndexes = 0;
}