cnq3/code/renderer/grp_world.cpp

1490 lines
42 KiB
C++

/*
===========================================================================
Copyright (C) 2022-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/>.
===========================================================================
*/
// Gameplay Rendering Pipeline - world/3D rendering
//#define ENABLE_GUI
#include "grp_local.h"
#include "../client/cl_imgui.h"
namespace zpp
{
#include "compshaders/grp/depth_pre_pass_vs.h"
#include "compshaders/grp/depth_pre_pass_ps.h"
}
namespace fog
{
#include "compshaders/grp/fog_vs.h"
}
namespace fog_inside
{
#include "compshaders/grp/fog_inside_ps.h"
}
namespace fog_outside
{
#include "compshaders/grp/fog_outside_ps.h"
}
namespace dyn_light
{
#include "compshaders/grp/dynamic_light_vs.h"
#include "compshaders/grp/dynamic_light_ps.h"
}
#pragma pack(push, 4)
struct ZPPVertexRC
{
float modelViewMatrix[16];
float projectionMatrix[16];
};
struct FogVertexRC
{
float modelViewMatrix[16];
float projectionMatrix[16];
float boxMin[4];
float boxMax[4];
};
struct FogPixelRC
{
float colorDepth[4];
};
struct DynLightVertexRC
{
float modelViewMatrix[16];
float projectionMatrix[16];
float osLightPos[4];
float osEyePos[4];
};
struct DynLightPixelRC
{
uint32_t stageIndices; // low 16 = texture, high 16 = sampler
uint32_t pad0[3];
float color[3];
float recSqrRadius; // 1 / (r * r)
float greyscale;
float intensity;
float opaque;
float pad1;
};
#pragma pack(pop)
static bool drawPrePass = true;
static bool drawDynamic = true;
static bool drawTransparents = true;
static bool drawFog = true;
static bool drawSky = true;
static bool drawClouds = true;
static bool forceDynamic = false;
static int zppMaxTriangleCount = 64;
static float zppMinRadiusOverZ = 0.5f;
static int zppDrawnTriangleCount = 0;
static const uint32_t zppMaxVertexCount = 1 << 20;
static const uint32_t zppMaxIndexCount = 8 * zppMaxVertexCount;
static uint32_t zppIndices[zppMaxIndexCount];
static bool HasStaticGeo(int staticGeoChunk, const shader_t* shader)
{
if(forceDynamic)
{
return false;
}
// @NOTE: the shader->isDynamic check is needed because of the shader editing feature
// without it, an edited shader that changes vertex attributes won't display correctly
// because the original "baked" vertex attributes would be used instead
return
!shader->isDynamic &&
staticGeoChunk > 0 &&
staticGeoChunk < ARRAY_LEN(grp.world.statChunks);
}
static int GetDynamicLightPipelineIndex(cullType_t cullType, qbool polygonOffset, qbool depthTestEquals)
{
return (int)cullType + CT_COUNT * (int)polygonOffset + CT_COUNT * 2 * (int)depthTestEquals;
}
void World::Init()
{
if(srp.firstInit)
{
fogDescriptorTable = RHI_MAKE_NULL_HANDLE();
}
{
TextureDesc desc("depth buffer", glConfig.vidWidth, glConfig.vidHeight);
desc.committedResource = true;
desc.shortLifeTime = true;
desc.initialState = ResourceStates::DepthWriteBit;
desc.allowedState = ResourceStates::DepthAccessBits | ResourceStates::PixelShaderAccessBit;
desc.format = TextureFormat::Depth32_Float;
desc.SetClearDepthStencil(0.0f, 0);
depthTexture = CreateTexture(desc);
depthTextureIndex = grp.RegisterTexture(depthTexture);
if(!IsNullHandle(fogDescriptorTable))
{
DescriptorTableUpdate update;
update.SetTextures(1, &depthTexture);
UpdateDescriptorTable(fogDescriptorTable, update);
}
}
if(srp.firstInit)
{
//
// depth pre-pass
//
{
RootSignatureDesc desc("Z pre-pass");
desc.usingVertexBuffers = true;
desc.constants[ShaderStage::Vertex].byteCount = sizeof(ZPPVertexRC);
desc.constants[ShaderStage::Pixel].byteCount = 0;
desc.samplerVisibility = ShaderStages::None;
desc.genericVisibility = ShaderStages::None;
zppRootSignature = CreateRootSignature(desc);
}
{
zppDescriptorTable = CreateDescriptorTable(DescriptorTableDesc("Z pre-pass", zppRootSignature));
}
{
// we could handle all 3 cull mode modes and alpha testing, but we do a partial pre-pass
// it wouldn't make sense going any further instead of trying a visibility buffer approach
GraphicsPipelineDesc desc("Z pre-pass", zppRootSignature);
desc.vertexShader = ShaderByteCode(zpp::g_vs);
desc.pixelShader = ShaderByteCode(zpp::g_ps);
desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual;
desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float;
desc.depthStencil.enableDepthTest = true;
desc.depthStencil.enableDepthWrites = true;
desc.rasterizer.cullMode = CT_FRONT_SIDED;
zppPipeline = CreateGraphicsPipeline(desc);
}
{
zppVertexBuffer.Init(zppMaxVertexCount, 3 * sizeof(float));
{
BufferDesc desc("depth pre-pass vertex", zppVertexBuffer.byteCount, ResourceStates::VertexBufferBit);
zppVertexBuffer.buffer = CreateBuffer(desc);
}
}
//
// dynamic (streamed) geometry
//
for(uint32_t f = 0; f < FrameCount; ++f)
{
// the doubled index count is for the depth pre-pass
const int MaxDynamicVertexCount = 256 << 10;
const int MaxDynamicIndexCount = MaxDynamicVertexCount * 8 * 2;
GeometryBuffers& db = dynBuffers[f];
db.vertexBuffers.Create(va("dynamic #%d", f + 1), MemoryUsage::Upload, MaxDynamicVertexCount);
db.indexBuffer.Create(va("dynamic #%d", f + 1), MemoryUsage::Upload, MaxDynamicIndexCount);
}
//
// static (GPU-resident) geometry
//
{
const int MaxVertexCount = 256 << 10;
const int MaxIndexCount = MaxVertexCount * 8;
statBuffers.vertexBuffers.Create("static", MemoryUsage::GPU, MaxVertexCount);
statBuffers.indexBuffer.Create("static", MemoryUsage::GPU, MaxIndexCount);
}
//
// fog
//
{
RootSignatureDesc desc("fog");
desc.usingVertexBuffers = true;
desc.AddRange(DescriptorType::Texture, 0, 1);
desc.genericVisibility = ShaderStages::PixelBit;
desc.constants[ShaderStage::Vertex].byteCount = sizeof(FogVertexRC);
desc.constants[ShaderStage::Pixel].byteCount = sizeof(FogPixelRC);
fogRootSignature = CreateRootSignature(desc);
}
{
DescriptorTableDesc desc("fog", fogRootSignature);
fogDescriptorTable = CreateDescriptorTable(desc);
DescriptorTableUpdate update;
update.SetTextures(1, &depthTexture);
UpdateDescriptorTable(fogDescriptorTable, update);
}
{
const uint32_t indices[] =
{
0, 1, 2, 2, 1, 3,
4, 0, 6, 6, 0, 2,
7, 5, 6, 6, 5, 4,
3, 1, 7, 7, 1, 5,
4, 5, 0, 0, 5, 1,
3, 7, 2, 2, 7, 6
};
BufferDesc desc("box index", sizeof(indices), ResourceStates::IndexBufferBit);
boxIndexBuffer = CreateBuffer(desc);
uint8_t* mapped = BeginBufferUpload(boxIndexBuffer);
memcpy(mapped, indices, sizeof(indices));
EndBufferUpload(boxIndexBuffer);
}
{
const float vertices[] =
{
0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f
};
BufferDesc desc("box vertex", sizeof(vertices), ResourceStates::VertexBufferBit);
boxVertexBuffer = CreateBuffer(desc);
uint8_t* mapped = BeginBufferUpload(boxVertexBuffer);
memcpy(mapped, vertices, sizeof(vertices));
EndBufferUpload(boxVertexBuffer);
}
//
// dynamic lights
//
{
RootSignatureDesc desc("dynamic lights");
desc.usingVertexBuffers = true;
desc.samplerCount = ARRAY_LEN(grp.samplers);
desc.samplerVisibility = ShaderStages::PixelBit;
desc.genericVisibility = ShaderStages::VertexBit | ShaderStages::PixelBit;
desc.AddRange(DescriptorType::Texture, 0, MAX_DRAWIMAGES * 2);
desc.AddRange(DescriptorType::RWBuffer, MAX_DRAWIMAGES * 2, 1);
desc.constants[ShaderStage::Vertex].byteCount = sizeof(DynLightVertexRC);
desc.constants[ShaderStage::Pixel].byteCount = sizeof(DynLightPixelRC);
dlRootSignature = CreateRootSignature(desc);
}
}
//
// fog
//
{
GraphicsPipelineDesc desc("fog outside", fogRootSignature);
desc.shortLifeTime = true;
desc.vertexShader = ShaderByteCode(fog::g_vs);
desc.pixelShader = ShaderByteCode(fog_outside::g_ps);
desc.depthStencil.DisableDepth();
desc.rasterizer.cullMode = CT_BACK_SIDED;
desc.rasterizer.polygonOffset = false;
desc.rasterizer.clampDepth = true;
desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, grp.renderTargetFormat);
desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
fogOutsidePipeline = CreateGraphicsPipeline(desc);
}
{
GraphicsPipelineDesc desc("fog inside", fogRootSignature);
desc.shortLifeTime = true;
desc.vertexShader = ShaderByteCode(fog::g_vs);
desc.pixelShader = ShaderByteCode(fog_inside::g_ps);
desc.depthStencil.DisableDepth();
desc.rasterizer.cullMode = CT_FRONT_SIDED;
desc.rasterizer.polygonOffset = false;
desc.rasterizer.clampDepth = true;
desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, grp.renderTargetFormat);
desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
fogInsidePipeline = CreateGraphicsPipeline(desc);
}
//
// dynamic lights
//
{
GraphicsPipelineDesc desc("dynamic light opaque", dlRootSignature);
desc.shortLifeTime = true;
desc.vertexShader = ShaderByteCode(dyn_light::g_vs);
desc.pixelShader = ShaderByteCode(dyn_light::g_ps);
desc.depthStencil.enableDepthWrites = false;
desc.rasterizer.clampDepth = r_depthClamp->integer != 0;
desc.AddRenderTarget(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE, grp.renderTargetFormat);
desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0);
desc.vertexLayout.AddAttribute(1, ShaderSemantic::Normal, DataType::Float32, 3, 0);
desc.vertexLayout.AddAttribute(2, ShaderSemantic::TexCoord, DataType::Float32, 2, 0);
desc.vertexLayout.AddAttribute(3, ShaderSemantic::Color, DataType::UNorm8, 4, 0);
for(int cullType = 0; cullType < CT_COUNT; ++cullType)
{
desc.rasterizer.cullMode = (cullType_t)cullType;
for(int polygonOffset = 0; polygonOffset < 2; ++polygonOffset)
{
desc.rasterizer.polygonOffset = polygonOffset != 0;
for(int depthTestEquals = 0; depthTestEquals < 2; ++depthTestEquals)
{
desc.depthStencil.depthComparison = depthTestEquals ? ComparisonFunction::Equal : ComparisonFunction::GreaterEqual;
const int index = GetDynamicLightPipelineIndex((cullType_t)cullType, (qbool)polygonOffset, (qbool)depthTestEquals);
dlPipelines[index] = CreateGraphicsPipeline(desc);
}
}
}
}
}
void World::BeginFrame()
{
dynBuffers[GetFrameIndex()].Rewind();
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
boundStaticVertexBuffersFirst = 0;
boundStaticVertexBuffersCount = UINT32_MAX;
DrawGUI();
psoChangeCount = 0; // read by the GUI code
}
void World::EndFrame()
{
}
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));
}
CmdSetViewportAndScissor(backEnd.viewParms);
batchOldDepthHack = false;
batchDepthHack = false;
batchOldShadingRate = ShadingRate::SR_1x1;
batchShadingRate = ShadingRate::SR_1x1;
TextureBarrier tb(depthTexture, ResourceStates::DepthWriteBit);
CmdBarrier(1, &tb);
}
void World::End()
{
srp.renderMode = RenderMode::None;
}
void World::DrawPrePass(const drawSceneViewCommand_t& cmd)
{
if(!drawPrePass || tr.world == NULL)
{
return;
}
GeometryBuffers& db = dynBuffers[GetFrameIndex()];
const uint32_t firstIndex = db.indexBuffer.batchFirst;
uint32_t* indices = db.indexBuffer.GetCurrentAddress();
int fullIndexCount = 0;
// we can't do a single draw because some world surfaces could be referenced by animated entities
const int surfCount = cmd.numDrawSurfs;
const drawSurf_t* drawSurf = cmd.drawSurfs;
for(int ds = 0; ds < surfCount; ++ds, ++drawSurf)
{
int entityNum = 0;
const shader_t* shader = NULL;
R_DecomposeSort(drawSurf->sort, &entityNum, &shader);
const int surfIndexCount = drawSurf->zppIndexCount;
if(surfIndexCount <= 0 ||
entityNum != ENTITYNUM_WORLD ||
surfIndexCount > zppMaxTriangleCount * 3 ||
drawSurf->radiusOverZ < zppMinRadiusOverZ)
{
continue;
}
if(!db.indexBuffer.CanAdd(surfIndexCount))
{
Q_assert(!"Dynamic buffer too small!");
break;
}
memcpy(indices, zppIndices + drawSurf->zppFirstIndex, surfIndexCount * sizeof(uint32_t));
fullIndexCount += surfIndexCount;
indices += surfIndexCount;
db.indexBuffer.EndBatch(surfIndexCount);
}
zppDrawnTriangleCount = fullIndexCount;
if(fullIndexCount <= 0)
{
return;
}
SCOPED_RENDER_PASS("Depth Pre-Pass", 0.75f, 0.75f, 0.375f);
CmdBindRenderTargets(0, NULL, &depthTexture);
CmdBindRootSignature(zppRootSignature);
CmdBindPipeline(zppPipeline);
psoChangeCount++;
CmdBindDescriptorTable(zppRootSignature, zppDescriptorTable);
ZPPVertexRC vertexRC;
memcpy(vertexRC.modelViewMatrix, backEnd.viewParms.world.modelMatrix, sizeof(vertexRC.modelViewMatrix));
memcpy(vertexRC.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(vertexRC.projectionMatrix));
CmdSetRootConstants(zppRootSignature, ShaderStage::Vertex, &vertexRC);
const uint32_t vertexStride = 3 * sizeof(float);
CmdBindVertexBuffers(1, &zppVertexBuffer.buffer, &vertexStride, NULL);
boundVertexBuffers = BufferFamily::PrePass;
BindIndexBuffer(false);
CmdDrawIndexed(fullIndexCount, firstIndex, 0);
}
void World::BeginBatch(const shader_t* shader, bool hasStaticGeo, BatchType::Id type)
{
if(type == BatchType::DepthFade && batchType != BatchType::DepthFade)
{
TextureBarrier barrier(depthTexture, ResourceStates::PixelShaderAccessBit);
CmdBarrier(1, &barrier);
CmdBindRenderTargets(1, &grp.renderTarget, NULL);
}
else if(type != BatchType::DepthFade && batchType == BatchType::DepthFade)
{
TextureBarrier barrier(depthTexture, ResourceStates::DepthWriteBit);
CmdBarrier(1, &barrier);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
}
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;
}
batchHasStaticGeo = hasStaticGeo;
batchType = type;
}
void World::EndBatch()
{
const int vertexCount = tess.numVertexes;
const int indexCount = tess.numIndexes;
if((!batchHasStaticGeo && vertexCount <= 0) ||
indexCount <= 0 ||
tess.shader->numStages == 0 ||
tess.shader->numPipelines <= 0)
{
goto clean_up;
}
if(batchType == BatchType::DynamicLight)
{
if(tess.shader->lightingStages[ST_DIFFUSE] < 0 ||
tess.shader->lightingStages[ST_DIFFUSE] >= MAX_SHADER_STAGES)
{
goto clean_up;
}
}
// for debugging of sort order issues, stop rendering after a given sort value
if(r_debugSort->value > 0.0f &&
r_debugSort->value < tess.shader->sort)
{
goto clean_up;
}
GeometryBuffers& db = dynBuffers[GetFrameIndex()];
if(!db.vertexBuffers.CanAdd(vertexCount) ||
!db.indexBuffer.CanAdd(indexCount))
{
Q_assert(!"Dynamic buffer too small!");
goto clean_up;
}
const shader_t* const shader = tess.shader;
if(!tess.deformsPreApplied)
{
RB_DeformTessGeometry(0, vertexCount, 0, indexCount);
if(batchType == BatchType::DynamicLight)
{
const int stageIndex = shader->lightingStages[ST_DIFFUSE];
if(stageIndex >= 0 && stageIndex < MAX_SHADER_STAGES)
{
R_ComputeColors(shader->stages[stageIndex], tess.svars[0], 0, vertexCount);
R_ComputeTexCoords(shader->stages[stageIndex], tess.svars[0], 0, vertexCount, qfalse);
}
}
else
{
for(int s = 0; s < shader->numStages; ++s)
{
R_ComputeColors(shader->stages[s], tess.svars[s], 0, vertexCount);
R_ComputeTexCoords(shader->stages[s], tess.svars[s], 0, vertexCount, qfalse);
}
}
}
// darken the hell fog in the BS pit on cpm21 for debugging
#if 0
if(Q_stristr(shader->name, "hellfog"))
{
for(int v = 0; v < vertexCount; ++v)
{
tess.svars[0].colors[v][0] = 127;
tess.svars[0].colors[v][1] = 127;
tess.svars[0].colors[v][2] = 127;
tess.svars[0].colors[v][3] = 255;
}
}
#endif
if(!batchHasStaticGeo)
{
db.vertexBuffers.Upload(0, shader->numStages);
}
db.indexBuffer.Upload();
BindIndexBuffer(false);
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;
}
if(batchShadingRate != batchOldShadingRate)
{
CmdSetShadingRate(batchShadingRate);
batchOldShadingRate = batchShadingRate;
}
if(batchType == BatchType::DynamicLight)
{
const dlight_t* const dl = tess.light;
const int stageIndex = tess.shader->lightingStages[ST_DIFFUSE];
const image_t* image = GetBundleImage(shader->stages[stageIndex]->bundle);
const uint32_t texIdx = image->textureIndex;
const uint32_t sampIdx = GetSamplerIndex(image);
Q_assert(texIdx > 0);
const int pipelineIndex = GetDynamicLightPipelineIndex(shader->cullType, shader->polygonOffset, dlDepthTestEqual);
const HPipeline pipeline = dlPipelines[pipelineIndex];
if(batchPSO != pipeline)
{
batchPSO = pipeline;
CmdBindPipeline(batchPSO);
psoChangeCount++;
}
DynLightVertexRC vertexRC = {};
memcpy(vertexRC.modelViewMatrix, backEnd.orient.modelMatrix, sizeof(vertexRC.modelViewMatrix));
memcpy(vertexRC.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(vertexRC.projectionMatrix));
VectorCopy(dl->transformed, vertexRC.osLightPos);
vertexRC.osLightPos[3] = 1.0f;
VectorCopy(backEnd.orient.viewOrigin, vertexRC.osEyePos);
vertexRC.osEyePos[3] = 1.0f;
CmdSetRootConstants(dlRootSignature, ShaderStage::Vertex, &vertexRC);
DynLightPixelRC pixelRC = {};
pixelRC.greyscale = tess.greyscale;
pixelRC.stageIndices = (sampIdx << 16) | (texIdx);
VectorCopy(tess.light->color, pixelRC.color);
pixelRC.recSqrRadius = 1.0f / Square(dl->radius);
pixelRC.intensity = dlIntensity;
pixelRC.opaque = dlOpaque ? 1.0f : 0.0f;
CmdSetRootConstants(dlRootSignature, ShaderStage::Pixel, &pixelRC);
BindVertexBuffers(batchHasStaticGeo, 0, 1);
CmdDrawIndexed(indexCount, db.indexBuffer.batchFirst, batchHasStaticGeo ? 0 : db.vertexBuffers.batchFirst);
}
else
{
for(int p = 0; p < shader->numPipelines; ++p)
{
const pipeline_t& pipeline = shader->pipelines[p];
const int psoIndex = backEnd.viewParms.isMirror ? pipeline.mirrorPipeline : pipeline.pipeline;
if(batchPSO != grp.psos[psoIndex].pipeline)
{
batchPSO = grp.psos[psoIndex].pipeline;
CmdBindPipeline(batchPSO);
psoChangeCount++;
}
WorldVertexRC 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));
CmdSetRootConstants(grp.uberRootSignature, ShaderStage::Vertex, &vertexRC);
WorldPixelRC pixelRC = {};
pixelRC.greyscale = tess.greyscale;
pixelRC.hFrameSeed = f32tof16(grp.frameSeed);
pixelRC.hNoiseScale = f32tof16(r_ditherStrength->value);
pixelRC.hInvGamma = f32tof16(1.0f / r_gamma->value);
pixelRC.hInvBrightness = f32tof16(1.0f / r_brightness->value);
pixelRC.shaderTrace = (uint32_t)!!tr.traceWorldShader | (RHI::GetFrameIndex() << 1) | ((uint32_t)shader->index << 3);
pixelRC.centerPixelX = glConfig.vidWidth / 2;
pixelRC.centerPixelY = glConfig.vidHeight / 2;
pixelRC.hFadeDistance = f32tof16(tess.shader->dfInvDist);
pixelRC.hFadeOffset = f32tof16(tess.shader->dfBias);
pixelRC.depthFadeColorTex = (uint32_t)r_depthFadeScaleAndBias[tess.shader->dfType] | (depthTextureIndex << 8);
for(int s = 0; s < pipeline.numStages; ++s)
{
const image_t* image = GetBundleImage(shader->stages[pipeline.firstStage + s]->bundle);
const uint32_t texIdx = image->textureIndex;
const uint32_t sampIdx = GetSamplerIndex(image);
Q_assert(texIdx > 0);
pixelRC.stageIndices[s] = (sampIdx << 16) | (texIdx);
}
CmdSetRootConstants(grp.uberRootSignature, ShaderStage::Pixel, &pixelRC);
BindVertexBuffers(batchHasStaticGeo, pipeline.firstStage, pipeline.numStages);
CmdDrawIndexed(indexCount, db.indexBuffer.batchFirst, batchHasStaticGeo ? 0 : db.vertexBuffers.batchFirst);
}
}
if(!batchHasStaticGeo)
{
db.vertexBuffers.EndBatch(tess.numVertexes);
}
db.indexBuffer.EndBatch(tess.numIndexes);
clean_up:
tess.numVertexes = 0;
tess.numIndexes = 0;
}
void World::EndSkyBatch()
{
// this only exists as a separate function from EndBatch so that
// we don't have to deal with recursion (through the call to RB_DrawSky)
if(tess.shader == NULL ||
!tess.shader->isSky ||
(!drawSky && !drawClouds) ||
tess.numVertexes <= 0 ||
tess.numIndexes <= 0)
{
return;
}
Q_assert(batchHasStaticGeo == false);
SCOPED_RENDER_PASS("Sky", 0.0, 0.5f, 1.0f);
CmdSetShadingRate(ShadingRate::SR_1x1);
const viewParms_t& vp = backEnd.viewParms;
CmdSetViewport(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight, 0.0f, 0.0f);
RB_DrawSky();
CmdSetViewport(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight, 0.0f, 1.0f);
tess.numVertexes = 0;
tess.numIndexes = 0;
batchOldDepthHack = false;
batchDepthHack = false;
batchOldShadingRate = ShadingRate::SR_1x1;
batchShadingRate = ShadingRate::SR_1x1;
}
void World::RestartBatch()
{
EndBatch();
BeginBatch(tess.shader, batchHasStaticGeo, batchType);
}
void World::DrawGUI()
{
#if defined(ENABLE_GUI)
if(tr.world == NULL)
{
return;
}
#if 0
TextureBarrier tb(depthTexture, ResourceStates::DepthReadBit);
CmdBarrier(1, &tb);
if(ImGui::Begin("depth", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Image((ImTextureID)depthTextureIndex, ImVec2(640, 360));
}
ImGui::End();
#endif
static bool active = false;
ToggleBooleanWithShortcut(active, ImGuiKey_W);
GUI_AddMainMenuItem(GUI_MainMenu::Info, "World rendering", "Ctrl+W", &active);
if(active)
{
if(ImGui::Begin("World", &active, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Checkbox("Depth Pre-Pass", &drawPrePass);
ImGui::SliderInt("Pre-pass Max Triangles", &zppMaxTriangleCount, 1, 256);
ImGui::SliderFloat("Pre-pass Min Radius", &zppMinRadiusOverZ, 1.0f / 256.0f, 256.0f / 256.0f);
ImGui::Checkbox("Draw Dynamic", &drawDynamic);
ImGui::Checkbox("Draw Transparents", &drawTransparents);
ImGui::Checkbox("Draw Fog", &drawFog);
ImGui::Checkbox("Draw Sky", &drawSky);
ImGui::Checkbox("Draw Clouds", &drawClouds);
ImGui::Checkbox("Force Dynamic", &forceDynamic);
ImGui::Text("PSO count: %d", (int)grp.psoCount);
ImGui::Text("PSO changes: %d", psoChangeCount);
ImGui::Text("ZPP triangles: %d", zppDrawnTriangleCount);
#if 0
vec3_t axis[3];
R_CameraAxisVectorsFromMatrix(axis[0], axis[1], axis[2], backEnd.viewParms.world.modelMatrix);
const char* axisNames[] = { "X", "Y", "Z", };
for(int a = 0; a < 3; ++a)
{
ImGui::Text("%s: %s", axisNames[a], v3tos(axis[a]));
}
vec3_t cameraPos;
R_CameraPositionFromMatrix(cameraPos, backEnd.viewParms.world.modelMatrix);
ImGui::Text("Camera Position: %s", v3tos(cameraPos));
for(int f = 1; f < tr.world->numfogs; ++f)
{
ImGui::Text("Fog: %s", v4tos(tr.world->fogs[f].surface));
}
#endif
}
ImGui::End();
}
#endif
}
void World::ProcessWorld(world_t& world)
{
{
zppVertexBuffer.batchFirst = 0;
zppVertexBuffer.batchCount = 0;
float* vtx = (float*)BeginBufferUpload(zppVertexBuffer.buffer);
int firstVertex = 0;
int firstIndex = 0;
for(int s = 0; s < world.numsurfaces; ++s)
{
msurface_t* const surf = &world.surfaces[s];
if(surf->shader->numStages == 0 ||
surf->shader->isDynamic ||
!surf->shader->isOpaque ||
surf->shader->isAlphaTestedOpaque ||
surf->shader->cullType != CT_FRONT_SIDED)
{
continue;
}
tess.numVertexes = 0;
tess.numIndexes = 0;
tess.shader = surf->shader; // unsure if needed, but just in case
R_TessellateSurface(surf->data);
const int surfVertexCount = tess.numVertexes;
const int surfIndexCount = tess.numIndexes;
if(surfVertexCount <= 0 || surfIndexCount <= 0)
{
continue;
}
if(firstVertex + surfVertexCount >= zppVertexBuffer.totalCount)
{
break;
}
for(int v = 0; v < surfVertexCount; ++v)
{
*vtx++ = tess.xyz[v][0];
*vtx++ = tess.xyz[v][1];
*vtx++ = tess.xyz[v][2];
}
uint32_t* idx = zppIndices + firstIndex;
for(int i = 0; i < surfIndexCount; ++i)
{
*idx++ = tess.indexes[i] + firstVertex;
}
surf->zppFirstIndex = firstIndex;
surf->zppIndexCount = surfIndexCount;
firstVertex += surfVertexCount;
firstIndex += surfIndexCount;
tess.numVertexes = 0;
tess.numIndexes = 0;
}
EndBufferUpload(zppVertexBuffer.buffer);
zppVertexBuffer.batchCount = firstVertex;
zppVertexBuffer.batchFirst = 0;
}
statChunkCount = 1; // index 0 is invalid
statIndexCount = 0;
statBuffers.vertexBuffers.Rewind();
statBuffers.indexBuffer.Rewind();
statBuffers.vertexBuffers.BeginUpload();
statBuffers.indexBuffer.BeginUpload();
for(int s = 0; s < world.numsurfaces; ++s)
{
if(statChunkCount >= ARRAY_LEN(statChunks))
{
Q_assert(0);
break;
}
msurface_t* const surf = &world.surfaces[s];
surf->staticGeoChunk = 0;
if(surf->shader->numStages == 0 ||
surf->shader->isDynamic)
{
continue;
}
tess.numVertexes = 0;
tess.numIndexes = 0;
tess.shader = surf->shader; // needed by R_ComputeTexCoords at least
R_TessellateSurface(surf->data);
const int surfVertexCount = tess.numVertexes;
const int surfIndexCount = tess.numIndexes;
if(surfVertexCount <= 0 || surfIndexCount <= 0)
{
continue;
}
if(!statBuffers.vertexBuffers.CanAdd(surfVertexCount) ||
!statBuffers.indexBuffer.CanAdd(surfIndexCount) ||
statIndexCount + surfIndexCount > ARRAY_LEN(statIndices))
{
Q_assert(0);
break;
}
for(int i = 0; i < surf->shader->numStages; ++i)
{
const shaderStage_t* const stage = surf->shader->stages[i];
R_ComputeColors(stage, tess.svars[i], 0, tess.numVertexes);
R_ComputeTexCoords(stage, tess.svars[i], 0, tess.numVertexes, qfalse);
}
// update GPU buffers
statBuffers.vertexBuffers.Upload(0, surf->shader->numStages);
statBuffers.indexBuffer.Upload();
// update CPU buffer
const uint32_t firstGPUVertex = statBuffers.vertexBuffers.batchFirst;
const uint32_t firstCPUIndex = statIndexCount;
uint32_t* cpuIndices = statIndices + firstCPUIndex;
for(int i = 0; i < surfIndexCount; ++i)
{
cpuIndices[i] = tess.indexes[i] + firstGPUVertex;
}
statIndexCount += surfIndexCount;
const uint32_t chunkIndex = statChunkCount++;
StaticGeometryChunk& chunk = statChunks[chunkIndex];
chunk.vertexCount = surfVertexCount;
chunk.indexCount = surfIndexCount;
chunk.firstGPUVertex = statBuffers.vertexBuffers.batchFirst;
chunk.firstGPUIndex = statBuffers.indexBuffer.batchFirst;
chunk.firstCPUIndex = firstCPUIndex;
surf->staticGeoChunk = chunkIndex;
statBuffers.vertexBuffers.EndBatch(surfVertexCount);
statBuffers.indexBuffer.EndBatch(surfIndexCount);
}
statBuffers.vertexBuffers.EndUpload();
statBuffers.indexBuffer.EndUpload();
tess.numVertexes = 0;
tess.numIndexes = 0;
}
void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
{
if(cmd.shouldClearColor)
{
const viewParms_t& vp = cmd.viewParms;
const Rect rect(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight);
CmdClearColorTarget(grp.renderTarget, cmd.clearColor, &rect);
}
if(cmd.numDrawSurfs <= 0 || !cmd.shouldDrawScene)
{
return;
}
backEnd.refdef = cmd.refdef;
backEnd.viewParms = cmd.viewParms;
Begin();
CmdClearDepthStencilTarget(depthTexture, true, 0.0f);
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
// the index buffer is also used for the Z pre-pass
GeometryBuffers& db = dynBuffers[GetFrameIndex()];
db.vertexBuffers.BeginUpload();
db.indexBuffer.BeginUpload();
// portals get the chance to write to depth and color before everyone else
{
const shader_t* shader = NULL;
int entityNum;
R_DecomposeSort(cmd.drawSurfs->shaderSort, &entityNum, &shader);
if(shader->sort != SS_PORTAL)
{
DrawPrePass(cmd);
}
}
SCOPED_RENDER_PASS("3D Scene View", 1.0f, 0.5f, 0.5f);
CmdBindRootSignature(grp.uberRootSignature);
CmdBindDescriptorTable(grp.uberRootSignature, grp.descriptorTable);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
batchPSO = RHI_MAKE_NULL_HANDLE();
batchType = BatchType::Standard;
const drawSurf_t* drawSurfs = cmd.drawSurfs;
const int surfCount = cmd.numDrawSurfs;
const int opaqueCount = cmd.numDrawSurfs - cmd.numTranspSurfs;
const int transpCount = cmd.numTranspSurfs;
const double originalTime = backEnd.refdef.floatTime;
const shader_t* shader = NULL;
const shader_t* oldShader = NULL;
int oldEntityNum = -1;
bool oldHasStaticGeo = false;
bool forceEntityChange = false;
backEnd.currentEntity = &tr.worldEntity;
ShadingRate::Id lowShadingRate = (ShadingRate::Id)r_shadingRate->integer;
batchShadingRate = ShadingRate::SR_1x1;
batchOldShadingRate = ShadingRate::SR_1x1;
tess.numVertexes = 0;
tess.numIndexes = 0;
int ds;
const drawSurf_t* drawSurf;
for(ds = 0, drawSurf = drawSurfs; ds < surfCount; ++ds, ++drawSurf)
{
if(ds == opaqueCount)
{
EndSkyBatch();
EndBatch();
CmdSetShadingRate(lowShadingRate);
batchOldShadingRate = lowShadingRate;
DrawDynamicLights(qtrue);
DrawFog();
// needed after DrawDynamicLights
forceEntityChange = true;
CmdBindRootSignature(grp.uberRootSignature);
CmdBindDescriptorTable(grp.uberRootSignature, grp.descriptorTable);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
batchPSO = RHI_MAKE_NULL_HANDLE();
batchType = BatchType::Standard;
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
const TextureBarrier depthWriteBarrier(depthTexture, ResourceStates::DepthWriteBit);
CmdBarrier(1, &depthWriteBarrier);
}
int entityNum;
R_DecomposeSort(drawSurf->sort, &entityNum, &shader);
// sky shaders can have no stages and be valid (box drawn with no clouds)
if(!shader->isSky)
{
if(shader->numPipelines == 0 ||
shader->pipelines[0].pipeline <= 0 ||
shader->pipelines[0].numStages <= 0)
{
continue;
}
}
const bool hasStaticGeo = HasStaticGeo(drawSurf->staticGeoChunk, shader);
const bool staticChanged = hasStaticGeo != oldHasStaticGeo;
const bool shaderChanged = shader != oldShader;
bool entityChanged = entityNum != oldEntityNum;
Q_assert(shader != NULL);
if(!hasStaticGeo && !drawDynamic)
{
continue;
}
if(ds >= opaqueCount && !drawTransparents)
{
continue;
}
const BatchType::Id type = IsDepthFadeEnabled(*shader) ? BatchType::DepthFade : BatchType::Standard;
if(staticChanged || shaderChanged || entityChanged)
{
oldShader = shader;
oldEntityNum = entityNum;
oldHasStaticGeo = hasStaticGeo;
EndSkyBatch();
EndBatch();
BeginBatch(shader, hasStaticGeo, type);
tess.greyscale = drawSurf->greyscale;
batchShadingRate = lowShadingRate;
}
if(entityChanged || forceEntityChange)
{
UpdateEntityData(batchDepthHack, entityNum, originalTime);
forceEntityChange = false;
}
if(hasStaticGeo)
{
const StaticGeometryChunk& chunk = statChunks[drawSurf->staticGeoChunk];
if(tess.numIndexes + chunk.indexCount > SHADER_MAX_INDEXES)
{
EndBatch();
BeginBatch(tess.shader, batchHasStaticGeo, type);
batchShadingRate = lowShadingRate;
}
memcpy(tess.indexes + tess.numIndexes, statIndices + chunk.firstCPUIndex, chunk.indexCount * sizeof(uint32_t));
tess.numIndexes += chunk.indexCount;
}
else
{
R_TessellateSurface(drawSurf->surface);
}
// we want full rate for nopicmipped sprites and nopicmipped alpha tests
if((shader->imgflags & IMG_NOPICMIP) != 0)
{
if(entityNum != ENTITYNUM_WORLD && tr.refdef.entities[entityNum].e.reType == RT_SPRITE)
{
batchShadingRate = ShadingRate::SR_1x1;
}
else if(shader->isAlphaTestedOpaque)
{
batchShadingRate = ShadingRate::SR_1x1;
}
}
}
backEnd.refdef.floatTime = originalTime;
EndSkyBatch();
EndBatch();
if(transpCount <= 0)
{
DrawDynamicLights(qtrue);
CmdSetShadingRate(lowShadingRate);
batchOldShadingRate = lowShadingRate;
DrawFog();
}
else
{
DrawDynamicLights(qfalse);
}
db.vertexBuffers.EndUpload();
db.indexBuffer.EndUpload();
// restores the potentially "hacked" depth range as well
CmdSetViewportAndScissor(backEnd.viewParms);
batchOldDepthHack = false;
batchDepthHack = false;
CmdSetShadingRate(ShadingRate::SR_1x1);
batchOldShadingRate = ShadingRate::SR_1x1;
batchShadingRate = ShadingRate::SR_1x1;
}
void World::BindVertexBuffers(bool staticGeo, uint32_t firstStage, uint32_t stageCount)
{
const BufferFamily::Id type = staticGeo ? BufferFamily::Static : BufferFamily::Dynamic;
if(type == boundVertexBuffers &&
firstStage == boundStaticVertexBuffersFirst &&
stageCount == boundStaticVertexBuffersCount)
{
return;
}
VertexBuffers& vb = staticGeo ? statBuffers.vertexBuffers : dynBuffers[GetFrameIndex()].vertexBuffers;
const uint32_t count = VertexBuffers::BaseCount + VertexBuffers::StageCount * stageCount;
HBuffer buffers[VertexBuffers::BufferCount];
memcpy(buffers, vb.buffers, VertexBuffers::BaseCount * sizeof(HBuffer));
memcpy(
buffers + VertexBuffers::BaseCount,
vb.buffers + VertexBuffers::BaseCount + VertexBuffers::StageCount * firstStage,
VertexBuffers::StageCount * stageCount * sizeof(HBuffer));
CmdBindVertexBuffers(count, buffers, vb.strides, NULL);
boundVertexBuffers = type;
boundStaticVertexBuffersFirst = firstStage;
boundStaticVertexBuffersCount = stageCount;
}
void World::BindIndexBuffer(bool staticGeo)
{
const BufferFamily::Id type = staticGeo ? BufferFamily::Static : BufferFamily::Dynamic;
if(type == boundIndexBuffer)
{
return;
}
IndexBuffer& ib = staticGeo ? statBuffers.indexBuffer : dynBuffers[GetFrameIndex()].indexBuffer;
CmdBindIndexBuffer(ib.buffer, indexType, 0);
boundIndexBuffer = type;
}
void World::DrawFog()
{
// fog 0 is invalid
if(!drawFog ||
tr.world == NULL ||
tr.world->numfogs <= 1 ||
(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0)
{
return;
}
SCOPED_RENDER_PASS("Fog", 0.25f, 0.125f, 0.0f);
// fog 0 is invalid, it must be skipped
int insideIndex = -1;
if(tr.world->numfogs > 1)
{
CmdBindPipeline(fogOutsidePipeline);
psoChangeCount++;
CmdBindRootSignature(fogRootSignature);
CmdBindDescriptorTable(fogRootSignature, fogDescriptorTable);
const uint32_t stride = sizeof(vec3_t);
CmdBindVertexBuffers(1, &boxVertexBuffer, &stride, NULL);
CmdBindIndexBuffer(boxIndexBuffer, IndexType::UInt32, 0);
CmdBindRenderTargets(1, &grp.renderTarget, NULL);
const TextureBarrier depthReadBarrier(depthTexture, ResourceStates::PixelShaderAccessBit);
CmdBarrier(1, &depthReadBarrier);
for(int f = 1; f < tr.world->numfogs; ++f)
{
const fog_t& fog = tr.world->fogs[f];
bool inside = true;
for(int a = 0; a < 3; ++a)
{
if(backEnd.viewParms.orient.origin[a] <= fog.bounds[0][a] ||
backEnd.viewParms.orient.origin[a] >= fog.bounds[1][a])
{
inside = false;
break;
}
}
if(inside)
{
insideIndex = f;
break;
}
}
}
for(int f = 1; f < tr.world->numfogs; ++f)
{
if(f == insideIndex)
{
continue;
}
const fog_t& fog = tr.world->fogs[f];
FogVertexRC vertexRC = {};
memcpy(vertexRC.modelViewMatrix, backEnd.viewParms.world.modelMatrix, sizeof(vertexRC.modelViewMatrix));
memcpy(vertexRC.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(vertexRC.projectionMatrix));
VectorCopy(fog.bounds[0], vertexRC.boxMin);
VectorCopy(fog.bounds[1], vertexRC.boxMax);
CmdSetRootConstants(fogRootSignature, ShaderStage::Vertex, &vertexRC);
FogPixelRC pixelRC = {};
VectorScale(fog.parms.color, tr.identityLight, pixelRC.colorDepth);
pixelRC.colorDepth[3] = fog.parms.depthForOpaque;
CmdSetRootConstants(fogRootSignature, ShaderStage::Pixel, &pixelRC);
CmdDrawIndexed(36, 0, 0);
}
if(insideIndex > 0)
{
CmdBindPipeline(fogInsidePipeline);
psoChangeCount++;
CmdBindRootSignature(fogRootSignature);
CmdBindDescriptorTable(fogRootSignature, fogDescriptorTable);
const fog_t& fog = tr.world->fogs[insideIndex];
FogVertexRC vertexRC = {};
memcpy(vertexRC.modelViewMatrix, backEnd.viewParms.world.modelMatrix, sizeof(vertexRC.modelViewMatrix));
memcpy(vertexRC.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(vertexRC.projectionMatrix));
VectorCopy(fog.bounds[0], vertexRC.boxMin);
VectorCopy(fog.bounds[1], vertexRC.boxMax);
CmdSetRootConstants(fogRootSignature, ShaderStage::Vertex, &vertexRC);
FogPixelRC pixelRC = {};
VectorScale(fog.parms.color, tr.identityLight, pixelRC.colorDepth);
pixelRC.colorDepth[3] = fog.parms.depthForOpaque;
CmdSetRootConstants(fogRootSignature, ShaderStage::Pixel, &pixelRC);
CmdDrawIndexed(36, 0, 0);
}
}
void World::DrawLitSurfaces(dlight_t* dl, bool opaque)
{
if(dl->head == NULL)
{
return;
}
if(!opaque && !drawTransparents)
{
return;
}
const shader_t* shader = NULL;
const shader_t* oldShader = NULL;
int oldEntityNum = -666;
bool oldHasStaticGeo = false;
// save original time for entity shader offsets
const double originalTime = backEnd.refdef.floatTime;
// draw everything
const int liquidFlags = CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER;
oldEntityNum = -1;
dlOpaque = opaque;
backEnd.currentEntity = &tr.worldEntity;
tess.light = dl;
tess.numVertexes = 0;
tess.numIndexes = 0;
for(litSurf_t* litSurf = dl->head; litSurf != NULL; litSurf = litSurf->next)
{
backEnd.pc3D[RB_LIT_SURFACES]++;
int entityNum;
R_DecomposeLitSort(litSurf->sort, &entityNum, &shader);
if(opaque && shader->sort > SS_OPAQUE)
{
continue;
}
if(!opaque && shader->sort <= SS_OPAQUE)
{
continue;
}
const int stageIndex = shader->lightingStages[ST_DIFFUSE];
if(stageIndex < 0 || stageIndex >= shader->numStages)
{
continue;
}
const bool hasStaticGeo = HasStaticGeo(litSurf->staticGeoChunk, shader);
const bool staticChanged = hasStaticGeo != oldHasStaticGeo;
const bool shaderChanged = shader != oldShader;
bool entityChanged = entityNum != oldEntityNum;
Q_assert(shader != NULL);
if(!hasStaticGeo && !drawDynamic)
{
continue;
}
if(staticChanged || shaderChanged || entityChanged)
{
oldShader = shader;
oldEntityNum = entityNum;
oldHasStaticGeo = hasStaticGeo;
EndBatch();
BeginBatch(shader, hasStaticGeo, BatchType::DynamicLight);
tess.greyscale = litSurf->greyscale;
const shaderStage_t* const stage = shader->stages[stageIndex];
dlIntensity = (shader->contentFlags & liquidFlags) != 0 ? 0.5f : 1.0f;
dlDepthTestEqual = opaque || (stage->stateBits & GLS_ATEST_BITS) != 0;
}
if(entityChanged)
{
UpdateEntityData(batchDepthHack, entityNum, originalTime);
}
R_TransformDlights(1, dl, &backEnd.orient);
if(hasStaticGeo)
{
const StaticGeometryChunk& chunk = statChunks[litSurf->staticGeoChunk];
if(tess.numIndexes + chunk.indexCount > SHADER_MAX_INDEXES)
{
EndBatch();
BeginBatch(tess.shader, batchHasStaticGeo, BatchType::DynamicLight);
}
memcpy(tess.indexes + tess.numIndexes, statIndices + chunk.firstCPUIndex, chunk.indexCount * sizeof(uint32_t));
tess.numIndexes += chunk.indexCount;
backEnd.pc3D[RB_LIT_INDICES_LATECULL_IN] += chunk.indexCount;
}
else
{
const int oldNumIndexes = tess.numIndexes;
R_TessellateSurface(litSurf->surface);
backEnd.pc3D[RB_LIT_INDICES_LATECULL_IN] += tess.numIndexes - oldNumIndexes;
}
}
// draw the contents of the last shader batch
if(shader != NULL)
{
EndBatch();
}
backEnd.refdef.floatTime = originalTime;
}
void World::DrawDynamicLights(bool opaque)
{
if(r_dynamiclight->integer == 0 ||
backEnd.viewParms.isMirror)
{
return;
}
SCOPED_RENDER_PASS(opaque ? "DL Opaque" : "DL Transp.", 1.0f, 0.25f, 0.25f);
if(backEnd.refdef.num_dlights == 0)
{
return;
}
CmdBindRootSignature(dlRootSignature);
CmdBindDescriptorTable(dlRootSignature, grp.descriptorTable);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
batchPSO = RHI_MAKE_NULL_HANDLE();
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
ShadingRate::Id lowShadingRate = (ShadingRate::Id)r_shadingRate->integer;
batchShadingRate = lowShadingRate;
batchOldShadingRate = ShadingRate::SR_1x1;
CmdSetShadingRate(lowShadingRate);
CmdSetViewportAndScissor(backEnd.viewParms);
batchOldDepthHack = false;
batchDepthHack = false;
tess.numVertexes = 0;
tess.numIndexes = 0;
for(int i = 0; i < backEnd.refdef.num_dlights; ++i)
{
DrawLitSurfaces(&backEnd.refdef.dlights[i], opaque);
}
}
void World::DrawSkyBox()
{
if(!drawSky)
{
tess.numVertexes = 0;
tess.numIndexes = 0;
return;
}
// force creation of a PSO for the temp shader
grp.ProcessShader((shader_t&)*tess.shader);
tess.deformsPreApplied = qtrue;
Q_assert(batchHasStaticGeo == false);
EndBatch();
}
void World::DrawClouds()
{
if(!drawClouds)
{
tess.numVertexes = 0;
tess.numIndexes = 0;
return;
}
Q_assert(batchHasStaticGeo == false);
EndBatch();
}