mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-26 22:11:51 +00:00
1490 lines
42 KiB
C++
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();
|
|
}
|