2024-01-13 21:40:13 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 2023 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 - OIT geometry pass
|
|
|
|
|
|
|
|
|
|
|
|
#include "crp_local.h"
|
2024-01-14 21:43:20 +00:00
|
|
|
#include "compshaders/crp/transp_draw.h"
|
2024-01-13 21:40:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
#pragma pack(push, 4)
|
|
|
|
|
|
|
|
struct TranspDrawVertexRC : WorldVertexRC
|
|
|
|
{
|
|
|
|
};
|
|
|
|
|
|
|
|
struct TranspDrawPixelRC
|
|
|
|
{
|
|
|
|
uint32_t textureIndex;
|
|
|
|
uint32_t samplerIndex;
|
|
|
|
uint32_t alphaTest;
|
|
|
|
uint32_t counterBuffer;
|
|
|
|
uint32_t indexTexture;
|
|
|
|
uint32_t fragmentBuffer;
|
|
|
|
float greyscale;
|
|
|
|
uint32_t stateBits;
|
|
|
|
uint32_t shaderTrace;
|
|
|
|
uint16_t hFadeDistance;
|
|
|
|
uint16_t hFadeOffset;
|
|
|
|
uint32_t depthFadeScaleBias; // color bias: 4 - color scale: 4
|
|
|
|
};
|
|
|
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
|
|
static uint32_t GetFixedStageBits(uint32_t stateBits, uint32_t stageIndex)
|
|
|
|
{
|
|
|
|
// makes sure we're not overwriting anything useful
|
|
|
|
assert((stateBits & GLS_STAGEINDEX_BITS) == 0);
|
|
|
|
|
|
|
|
// transform "no blend" into a "replace" blend mode
|
|
|
|
if((stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)) == 0)
|
|
|
|
{
|
|
|
|
stateBits |= GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO;
|
|
|
|
}
|
|
|
|
|
|
|
|
stateBits |= stageIndex << GLS_STAGEINDEX_SHIFT;
|
|
|
|
|
|
|
|
return stateBits;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void WorldTransp::Init()
|
|
|
|
{
|
|
|
|
psoCache.Init(psoCacheEntries, ARRAY_LEN(psoCacheEntries));
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorldTransp::Draw(const drawSceneViewCommand_t& cmd)
|
|
|
|
{
|
|
|
|
if(cmd.numTranspSurfs <= 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
srp.renderMode = RenderMode::World;
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
SCOPED_RENDER_PASS("Transparent", 1.0f, 0.5f, 0.5f);
|
|
|
|
|
|
|
|
CmdSetViewportAndScissor(backEnd.viewParms);
|
|
|
|
batchOldDepthHack = false;
|
|
|
|
batchDepthHack = false;
|
|
|
|
|
2024-01-15 16:10:36 +00:00
|
|
|
CmdBeginBarrier();
|
|
|
|
CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit);
|
|
|
|
CmdTextureBarrier(crp.oitIndexTexture, ResourceStates::UnorderedAccessBit);
|
|
|
|
CmdBufferBarrier(crp.oitFragmentBuffer, ResourceStates::UnorderedAccessBit);
|
|
|
|
CmdBufferBarrier(crp.oitCounterBuffer, ResourceStates::CopyDestinationBit);
|
|
|
|
CmdEndBarrier();
|
2024-01-13 21:40:13 +00:00
|
|
|
|
|
|
|
GeoBuffers& db = crp.dynBuffers[GetFrameIndex()];
|
|
|
|
db.BeginUpload();
|
|
|
|
|
|
|
|
CmdBindRenderTargets(0, NULL, &crp.depthTexture);
|
|
|
|
CmdBindVertexBuffers(ARRAY_LEN(db.vertexBuffers), db.vertexBuffers, db.vertexBufferStrides, NULL);
|
|
|
|
CmdBindIndexBuffer(db.indexBuffer.buffer, IndexType::UInt32, 0);
|
|
|
|
|
|
|
|
// reset the fragment counter
|
2024-01-15 16:10:36 +00:00
|
|
|
CmdCopyBuffer(crp.oitCounterBuffer, crp.oitCounterStagingBuffer);
|
2024-01-13 21:40:13 +00:00
|
|
|
|
|
|
|
// clear the index texture
|
2024-01-15 16:10:36 +00:00
|
|
|
const uint32_t zeroes[4] = {};
|
|
|
|
CmdClearTextureUAV(crp.oitIndexTexture, 0, zeroes);
|
2024-01-13 21:40:13 +00:00
|
|
|
|
2024-01-15 16:10:36 +00:00
|
|
|
CmdBeginBarrier();
|
|
|
|
CmdBufferBarrier(crp.oitCounterBuffer, ResourceStates::UnorderedAccessBit);
|
|
|
|
CmdTextureBarrier(crp.oitIndexTexture, ResourceStates::UnorderedAccessBit);
|
|
|
|
CmdEndBarrier();
|
2024-01-13 21:40:13 +00:00
|
|
|
|
|
|
|
const drawSurf_t* drawSurfs = cmd.drawSurfs;
|
|
|
|
const int opaqueSurfCount = cmd.numDrawSurfs - cmd.numTranspSurfs;
|
|
|
|
const int transpSurfCount = 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;
|
|
|
|
|
|
|
|
const drawSurf_t* drawSurf = drawSurfs + opaqueSurfCount;
|
|
|
|
for(int ds = 0; ds < transpSurfCount; ++ds, ++drawSurf)
|
|
|
|
{
|
|
|
|
int entityNum;
|
|
|
|
R_DecomposeSort(drawSurf->sort, &entityNum, &shader);
|
|
|
|
Q_assert(shader != NULL);
|
|
|
|
Q_assert(!shader->isOpaque);
|
|
|
|
|
|
|
|
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 WorldTransp::ProcessShader(shader_t& shader)
|
|
|
|
{
|
|
|
|
Q_assert(!shader.isOpaque);
|
|
|
|
|
|
|
|
if(shader.numStages < 1)
|
|
|
|
{
|
|
|
|
shader.numTranspPipelines = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool clampDepth = r_depthClamp->integer != 0 || shader.isSky;
|
|
|
|
|
|
|
|
for(int s = 0; s < shader.numStages; ++s)
|
|
|
|
{
|
|
|
|
int a = 0;
|
|
|
|
|
|
|
|
// @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 = "transp";
|
|
|
|
desc.rootSignature = RHI_MAKE_NULL_HANDLE();
|
|
|
|
desc.shortLifeTime = true; // the PSO cache is only valid for this map!
|
2024-01-14 21:43:20 +00:00
|
|
|
desc.vertexShader = g_transp_draw_vs;
|
|
|
|
desc.pixelShader = g_transp_draw_ps;
|
2024-01-13 21:40:13 +00:00
|
|
|
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::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 = false;
|
|
|
|
desc.rasterizer.cullMode = shader.cullType;
|
|
|
|
desc.rasterizer.polygonOffset = shader.polygonOffset != 0;
|
|
|
|
desc.rasterizer.clampDepth = clampDepth;
|
|
|
|
|
|
|
|
pipeline_t& p = shader.transpPipelines[s];
|
|
|
|
p.firstStage = s;
|
|
|
|
p.numStages = 1;
|
|
|
|
p.pipeline = psoCache.AddPipeline(desc, va("transp %d %d", psoCache.entryCount, s + 1));
|
|
|
|
desc.rasterizer.cullMode = GetMirrorredCullType(desc.rasterizer.cullMode);
|
|
|
|
p.mirrorPipeline = psoCache.AddPipeline(desc, va("transp %d %d mirrored", psoCache.entryCount, s + 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
shader.numTranspPipelines = shader.numStages;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorldTransp::TessellationOverflow()
|
|
|
|
{
|
|
|
|
EndBatch();
|
|
|
|
BeginBatch(tess.shader);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorldTransp::BeginBatch(const shader_t* shader)
|
|
|
|
{
|
|
|
|
tess.tessellator = Tessellator::Transp;
|
|
|
|
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 WorldTransp::EndBatch()
|
|
|
|
{
|
|
|
|
const int vertexCount = tess.numVertexes;
|
|
|
|
const int indexCount = tess.numIndexes;
|
|
|
|
if(vertexCount <= 0 ||
|
|
|
|
indexCount <= 0 ||
|
|
|
|
tess.shader->numStages == 0 ||
|
|
|
|
tess.shader->numTranspPipelines <= 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
TranspDrawVertexRC vertexRC = {};
|
|
|
|
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));
|
|
|
|
CmdSetGraphicsRootConstants(0, sizeof(vertexRC), &vertexRC);
|
|
|
|
|
|
|
|
for(int s = 0; s < shader->numStages; ++s)
|
|
|
|
{
|
|
|
|
const shaderStage_t* const stage = shader->stages[s];
|
|
|
|
|
|
|
|
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->transpPipelines[s];
|
|
|
|
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);
|
|
|
|
const uint32_t enableShaderTrace = tr.traceWorldShader && s == 0 ? 1 : 0;
|
|
|
|
const uint32_t enableDepthFade = shader->dfType != DFT_NONE ? 1 : 0;
|
|
|
|
Q_assert(sampIdx < ARRAY_LEN(crp.samplers));
|
|
|
|
|
|
|
|
TranspDrawPixelRC pixelRC = {};
|
|
|
|
pixelRC.alphaTest = alphaTest;
|
|
|
|
pixelRC.counterBuffer = GetBufferIndexUAV(crp.oitCounterBuffer);
|
|
|
|
pixelRC.fragmentBuffer = GetBufferIndexUAV(crp.oitFragmentBuffer);
|
|
|
|
pixelRC.greyscale = tess.greyscale;
|
|
|
|
pixelRC.indexTexture = GetTextureIndexUAV(crp.oitIndexTexture, 0);
|
|
|
|
pixelRC.samplerIndex = sampIdx;
|
|
|
|
pixelRC.stateBits = GetFixedStageBits(stage->stateBits, s);
|
|
|
|
pixelRC.textureIndex = texIdx;
|
|
|
|
pixelRC.shaderTrace = ((uint32_t)shader->index << 3) | (RHI::GetFrameIndex() << 1) | enableShaderTrace;
|
|
|
|
pixelRC.hFadeDistance = f32tof16(shader->dfInvDist);
|
|
|
|
pixelRC.hFadeOffset = f32tof16(shader->dfBias);
|
|
|
|
pixelRC.depthFadeScaleBias = (enableDepthFade << 8) | (uint32_t)r_depthFadeScaleAndBias[shader->dfType];
|
|
|
|
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;
|
|
|
|
}
|