implemented dynamic lights

This commit is contained in:
myT 2023-09-10 18:45:55 +02:00
parent ccb9827b03
commit ffde1f2b1b
16 changed files with 602 additions and 100 deletions

View file

@ -304,6 +304,16 @@ struct FrameStats
int skippedFrames;
};
struct BatchType
{
enum Id
{
Standard,
DynamicLight,
Count
};
};
struct World
{
void Init();
@ -312,7 +322,7 @@ struct World
void Begin();
void End();
void DrawPrePass(const drawSceneViewCommand_t& cmd);
void BeginBatch(const shader_t* shader, bool hasStaticGeo);
void BeginBatch(const shader_t* shader, bool hasStaticGeo, BatchType::Id batchType);
void EndBatch();
void EndSkyBatch();
void RestartBatch();
@ -322,6 +332,8 @@ struct World
void BindVertexBuffers(bool staticGeo, uint32_t firstStage, uint32_t stageCount);
void BindIndexBuffer(bool staticGeo);
void DrawFog();
void DrawLitSurfaces(dlight_t* dl, bool opaque);
void DrawDynamicLights(bool opaque);
void DrawSkyBox();
void DrawClouds();
@ -356,6 +368,7 @@ struct World
uint32_t boundStaticVertexBuffersFirst;
uint32_t boundStaticVertexBuffersCount;
HPipeline batchPSO;
BatchType::Id batchType;
bool batchHasStaticGeo;
int psoChangeCount;
bool batchDepthHack;
@ -384,6 +397,17 @@ struct World
// shader trace
HBuffer traceRenderBuffer;
HBuffer traceReadbackBuffer;
// dynamic lights
HRootSignature dlRootSignature;
HPipeline dlPipelines[CT_COUNT * 2 * 2]; // { cull type, polygon offset, depth test }
bool dlOpaque;
float dlIntensity; // 1 for most surfaces, but can be scaled down for liquids etc.
bool dlDepthTestEqual;
// quick explanation on why dlOpaque is useful in the first place:
// - opaque surfaces can have a diffuse texture whose alpha isn't 255 everywhere
// - when that happens and we multiply the color by the the alpha (DL uses additive blending),
// we get "light holes" in opaque surfaces, which is not what we want
};
struct UI

View file

@ -43,6 +43,11 @@ namespace fog_outside
{
#include "hlsl/fog_outside_ps.h"
}
namespace dyn_light
{
#include "hlsl/dynamic_light_vs.h"
#include "hlsl/dynamic_light_ps.h"
}
#pragma pack(push, 4)
@ -66,6 +71,26 @@ 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)
@ -85,15 +110,20 @@ static const uint32_t zppMaxIndexCount = 8 * zppMaxVertexCount;
static uint32_t zppIndices[zppMaxIndexCount];
static bool HasStaticGeo(const drawSurf_t* drawSurf, const shader_t* shader)
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 &&
drawSurf->staticGeoChunk > 0 &&
drawSurf->staticGeoChunk < ARRAY_LEN(grp.world.statChunks);
staticGeoChunk > 0 &&
staticGeoChunk < ARRAY_LEN(grp.world.statChunks);
}
static void UpdateEntityData(bool& depthHack, int entityNum, double originalTime)
@ -130,6 +160,11 @@ static void UpdateEntityData(bool& depthHack, int entityNum, double originalTime
}
}
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()
{
@ -294,6 +329,22 @@ void World::Init()
desc.memoryUsage = MemoryUsage::Readback;
traceReadbackBuffer = CreateBuffer(desc);
}
//
// 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);
}
}
//
@ -325,6 +376,37 @@ void World::Init()
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()
@ -413,8 +495,6 @@ void World::Begin()
void World::End()
{
EndBatch();
grp.renderMode = RenderMode::None;
}
@ -482,7 +562,7 @@ void World::DrawPrePass(const drawSceneViewCommand_t& cmd)
CmdDrawIndexed(fullIndexCount, firstIndex, 0);
}
void World::BeginBatch(const shader_t* shader, bool hasStaticGeo)
void World::BeginBatch(const shader_t* shader, bool hasStaticGeo, BatchType::Id type)
{
tess.numVertexes = 0;
tess.numIndexes = 0;
@ -497,6 +577,7 @@ void World::BeginBatch(const shader_t* shader, bool hasStaticGeo)
tess.shaderTime = tess.shader->clampTime;
}
batchHasStaticGeo = hasStaticGeo;
batchType = type;
}
void World::EndBatch()
@ -508,17 +589,23 @@ void World::EndBatch()
tess.shader->numStages == 0 ||
tess.shader->numPipelines <= 0)
{
// @TODO: make sure we never get tess.shader->numStages 0 here in the first place
tess.numVertexes = 0;
tess.numIndexes = 0;
return;
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)
{
return;
goto clean_up;
}
GeometryBuffers& db = dynBuffers[GetFrameIndex()];
@ -526,17 +613,29 @@ void World::EndBatch()
!db.indexBuffer.CanAdd(indexCount))
{
Q_assert(!"Dynamic buffer too small!");
return;
goto clean_up;
}
const shader_t* const shader = tess.shader;
if(!tess.deformsPreApplied)
{
RB_DeformTessGeometry(0, vertexCount, 0, indexCount);
for(int s = 0; s < shader->numStages; ++s)
if(batchType == BatchType::DynamicLight)
{
R_ComputeColors(shader->stages[s], tess.svars[s], 0, vertexCount);
R_ComputeTexCoords(shader->stages[s], tess.svars[s], 0, vertexCount, qfalse);
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);
}
}
}
@ -575,53 +674,96 @@ void World::EndBatch()
batchOldShadingRate = batchShadingRate;
}
for(int p = 0; p < shader->numPipelines; ++p)
if(batchType == BatchType::DynamicLight)
{
const pipeline_t& pipeline = shader->pipelines[p];
const int psoIndex = backEnd.viewParms.isMirror ? pipeline.mirrorPipeline : pipeline.pipeline;
if(batchPSO != grp.psos[psoIndex].pipeline)
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 = grp.psos[psoIndex].pipeline;
batchPSO = pipeline;
CmdBindPipeline(batchPSO);
psoChangeCount++;
}
WorldVertexRC vertexRC = {};
DynLightVertexRC 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);
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);
WorldPixelRC pixelRC = {};
DynLightPixelRC pixelRC = {};
pixelRC.greyscale = tess.greyscale;
pixelRC.frameSeed = grp.frameSeed;
pixelRC.noiseScale = r_ditherStrength->value;
pixelRC.invBrightness = 1.0f / r_brightness->value;
pixelRC.invGamma = 1.0f / r_gamma->value;
pixelRC.shaderTrace = (uint32_t)!!tr.traceWorldShader;
pixelRC.shaderIndex = (uint32_t)shader->index;
pixelRC.frameIndex = RHI::GetFrameIndex();
pixelRC.centerPixel = (glConfig.vidWidth / 2) | ((glConfig.vidHeight / 2) << 16);
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);
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, pipeline.firstStage, pipeline.numStages);
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.frameSeed = grp.frameSeed;
pixelRC.noiseScale = r_ditherStrength->value;
pixelRC.invBrightness = 1.0f / r_brightness->value;
pixelRC.invGamma = 1.0f / r_gamma->value;
pixelRC.shaderTrace = (uint32_t)!!tr.traceWorldShader;
pixelRC.shaderIndex = (uint32_t)shader->index;
pixelRC.frameIndex = RHI::GetFrameIndex();
pixelRC.centerPixel = (glConfig.vidWidth / 2) | ((glConfig.vidHeight / 2) << 16);
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;
}
@ -662,7 +804,7 @@ void World::EndSkyBatch()
void World::RestartBatch()
{
EndBatch();
BeginBatch(tess.shader, batchHasStaticGeo);
BeginBatch(tess.shader, batchHasStaticGeo, BatchType::Standard);
}
void World::DrawGUI()
@ -935,6 +1077,7 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
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;
@ -955,20 +1098,21 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
CmdSetShadingRate(lowShadingRate);
batchOldShadingRate = lowShadingRate;
DrawDynamicLights(qtrue);
DrawFog();
if(transpCount > 0)
{
CmdBindRootSignature(grp.uberRootSignature);
CmdBindDescriptorTable(grp.uberRootSignature, grp.descriptorTable);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
batchPSO = RHI_MAKE_NULL_HANDLE();
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
// needed after DrawDynamicLights
forceEntityChange = true;
const TextureBarrier depthWriteBarrier(depthTexture, ResourceStates::DepthWriteBit);
CmdBarrier(1, &depthWriteBarrier);
}
CmdBindRootSignature(grp.uberRootSignature);
CmdBindDescriptorTable(grp.uberRootSignature, grp.descriptorTable);
CmdBindRenderTargets(1, &grp.renderTarget, &depthTexture);
batchPSO = RHI_MAKE_NULL_HANDLE();
boundVertexBuffers = BufferFamily::Invalid;
boundIndexBuffer = BufferFamily::Invalid;
const TextureBarrier depthWriteBarrier(depthTexture, ResourceStates::DepthWriteBit);
CmdBarrier(1, &depthWriteBarrier);
}
int entityNum;
@ -985,7 +1129,7 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
}
}
const bool hasStaticGeo = forceDynamic ? false : HasStaticGeo(drawSurf, shader);
const bool hasStaticGeo = HasStaticGeo(drawSurf->staticGeoChunk, shader);
const bool staticChanged = hasStaticGeo != oldHasStaticGeo;
const bool shaderChanged = shader != oldShader;
bool entityChanged = entityNum != oldEntityNum;
@ -1006,14 +1150,15 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
oldHasStaticGeo = hasStaticGeo;
EndSkyBatch();
EndBatch();
BeginBatch(shader, hasStaticGeo);
BeginBatch(shader, hasStaticGeo, BatchType::Standard);
tess.greyscale = drawSurf->greyscale;
batchShadingRate = lowShadingRate;
}
if(entityChanged)
if(entityChanged || forceEntityChange)
{
UpdateEntityData(batchDepthHack, entityNum, originalTime);
forceEntityChange = false;
}
if(hasStaticGeo)
@ -1022,7 +1167,7 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
if(tess.numIndexes + chunk.indexCount > SHADER_MAX_INDEXES)
{
EndBatch();
BeginBatch(tess.shader, batchHasStaticGeo);
BeginBatch(tess.shader, batchHasStaticGeo, BatchType::Standard);
batchShadingRate = lowShadingRate;
}
@ -1053,12 +1198,17 @@ void World::DrawSceneView(const drawSceneViewCommand_t& cmd)
EndSkyBatch();
EndBatch();
CmdSetShadingRate(ShadingRate::SR_1x1);
if(transpCount <= 0)
{
DrawDynamicLights(qtrue);
CmdSetShadingRate(lowShadingRate);
batchOldShadingRate = lowShadingRate;
DrawFog();
}
else
{
DrawDynamicLights(qfalse);
}
db.vertexBuffers.EndUpload();
db.indexBuffer.EndUpload();
@ -1205,6 +1355,154 @@ void World::DrawFog()
}
}
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)
{
SCOPED_RENDER_PASS("Dynamic Lights", 1.0f, 0.25f, 0.25f);
if(r_dynamiclight->integer == 0 ||
backEnd.refdef.num_dlights == 0 ||
backEnd.viewParms.isMirror)
{
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)

View file

@ -0,0 +1,128 @@
/*
===========================================================================
Copyright (C) 2019-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/>.
===========================================================================
*/
// dynamic light shader
#if VERTEX_SHADER
struct VIn
{
float3 position : POSITION;
float3 normal : NORMAL;
float2 texCoords : TEXCOORD0;
float4 color : COLOR0;
};
#endif
struct VOut
{
float4 position : SV_Position;
float3 normal : NORMAL;
float2 texCoords : TEXCOORD0;
float4 color : COLOR0;
float3 osLightVec : TEXCOORD1;
float3 osEyeVec : TEXCOORD2;
};
#if VERTEX_SHADER
cbuffer RootConstants
{
matrix modelViewMatrix;
matrix projectionMatrix;
float4 osLightPos;
float4 osEyePos;
};
VOut vs(VIn input)
{
float4 positionVS = mul(modelViewMatrix, float4(input.position.xyz, 1));
VOut output;
output.position = mul(projectionMatrix, positionVS);
output.normal = input.normal;
output.texCoords = input.texCoords;
output.color = input.color;
output.osLightVec = osLightPos.xyz - input.position.xyz;
output.osEyeVec = osEyePos.xyz - input.position.xyz;
return output;
}
#endif
#if PIXEL_SHADER
#include "shared.hlsli"
cbuffer RootConstants
{
uint4 stageIndices; // low 16 = texture, high 16 = sampler
float3 color;
float recSqrRadius; // 1 / (r * r)
float greyscale;
float intensity;
float opaque;
};
Texture2D textures2D[4096] : register(t0);
SamplerState samplers[96] : register(s0);
float BezierEase(float t)
{
return t * t * (3.0 - 2.0 * t);
}
[earlydepthstencil]
float4 ps(VOut input) : SV_Target
{
// fetch data
uint textureIndex = stageIndices.x & 0xFFFF;
uint samplerIndex = stageIndices.x >> 16;
float lightRecSqrRadius = recSqrRadius;
float3 lightColor = color;
float4 baseColored = input.color * textures2D[textureIndex].Sample(samplers[samplerIndex], input.texCoords);
float4 base = MakeGreyscale(baseColored, greyscale);
float3 nL = normalize(input.osLightVec); // normalized object-space light vector
float3 nV = normalize(input.osEyeVec); // normalized object-space view vector
float3 nN = input.normal; // normalized object-space normal vector
// light intensity
float intensFactor = min(dot(input.osLightVec, input.osLightVec) * lightRecSqrRadius, 1.0);
float3 intens = lightColor * BezierEase(1.0 - sqrt(intensFactor));
// specular reflection term (N.H)
float specFactor = min(abs(dot(nN, normalize(nL + nV))), 1.0);
float spec = pow(specFactor, 16.0) * 0.25;
// Lambertian diffuse reflection term (N.L)
float diffuse = min(abs(dot(nN, nL)), 1.0);
float3 color = (base.rgb * diffuse.rrr + spec.rrr) * intens * intensity;
float alpha = lerp(opaque, 1.0, base.a);
float4 final = float4(color.rgb * alpha, alpha);
return final;
}
#endif

View file

@ -29,22 +29,10 @@ to do:
- git cherry-pick c75b2b27fa936854d27dadf458e3ec3b03829561
- https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
- GPU resident vertex data for models: load on demand based on submitted { surface, shader } pairs
- reloading a map can lead to a TDR timeout
could it be related to the copy queue?
use PIX to capture and replay the bad command list?
seems to not happen ever since I fixed the usage of the stale depth buffer?!
- transparents don't batch properly I think -> entityMergeable support? ("entityMergable" in the code)
- use Application Verifier to catch issues
- tone mapping: look at https://github.com/h3r2tic/tony-mc-mapface
- ubershader PS: run-time alpha test evaluation to reduce PSO count?
- mip-map generation: figure out whether out of bounds texture UAV writes are OK or not
I think they are in this use case but an explicit confirmation would be nice...
- working depth pre-pass (account for cull mode, generate buffers on demand)
full depth pre-pass -> Z-buffer is complete and won't get updated by opaque drawing
1 full-screen pass per dynamic light, culled early with the depth bounds test -> write out to a light buffer
draw opaques, locate the light stage and add the light buffer data to the light stage result
- r_depthFade
- r_dynamiclight
- when creating the root signature, validate that neither of the tables have any gap
- use root signature 1.1 to use the hints that help the drivers optimize out static resources
- is it possible to force Resource Binding Tier 2 somehow? are we supposed to run on old HW to test? :(
@ -57,7 +45,6 @@ to do:
- move UI to the uber shader system, tessellate to generate proper data
- share structs between HLSL and C++ with .hlsli files -> change cbuffer to ConstantBuffer<MyStruct>
- share texture and sampler array sizes between HLSL and C++ with .hlsli files
- what's the actual fog curve used by Q3?
- roq video textures support?
*/
@ -826,6 +813,7 @@ namespace RHI
else
{
mapped = (uint8_t*)MapBuffer(userHBuffer);
Q_assert(mapped != NULL);
}
userBuffer.uploading = true;
@ -3108,9 +3096,10 @@ namespace RHI
return NULL;
}
void* mappedPtr;
void* mappedPtr = NULL;
D3D(buffer.buffer->Map(0, NULL, &mappedPtr));
buffer.mapped = true;
Q_assert(mappedPtr != NULL);
return (uint8_t*)mappedPtr;
}

View file

@ -25,11 +25,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
backEndData_t* backEndData;
backEndState_t backEnd;
// @TODO: remove all this
#if 0
static int64_t startTime;
#if 0
static void RB_Set2D()
{
backEnd.projection2D = qtrue;

View file

@ -511,7 +511,7 @@ static const cvarTableItem_t r_cvars[] =
},
{
&r_dynamiclight, "r_dynamiclight", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, "enables dynamic lights",
"", 0, "", "" // @TODO: CVARCAT_GRAPHICS | CVARCAT_PERFORMANCE once implemented
"Enable dynamic lights", CVARCAT_GRAPHICS | CVARCAT_PERFORMANCE, "For power-ups, muzzle flashes, rockets, explosions, ...", ""
},
{
&r_lego, "r_lego", "0", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, "LEGO(R) texture filtering",

View file

@ -552,6 +552,7 @@ struct litSurf_t {
const surfaceType_t* surface; // any of surface*_t
litSurf_t* next;
float greyscale;
int staticGeoChunk;
};
struct dlight_t {
@ -837,6 +838,24 @@ enum drawSurfGeneralSort_t {
};
enum litSurfGeneralSort_t {
// dimensions - the sum should stay within 32 bits
LITSORT_ENTITY_BITS = 10, // GENTITYNUM_BITS
LITSORT_SHADER_BITS = 14, // log2 MAX_SHADERS
LITSORT_STATICGEO_BITS = 1,
LITSORT_CULLTYPE_BITS = 2, // PSO part 1
LITSORT_POLYGONOFFSET_BITS = 1, // PSO part 2
LITSORT_DEPTHTEST_BITS = 1, // PSO part 3
// offsets
LITSORT_ENTITY_INDEX = 0,
LITSORT_SHADER_INDEX = LITSORT_ENTITY_INDEX + LITSORT_ENTITY_BITS,
LITSORT_STATICGEO_INDEX = LITSORT_SHADER_INDEX + LITSORT_SHADER_BITS,
LITSORT_CULLTYPE_INDEX = LITSORT_STATICGEO_INDEX + LITSORT_STATICGEO_BITS,
LITSORT_POLYGONOFFSET_INDEX = LITSORT_CULLTYPE_INDEX + LITSORT_CULLTYPE_BITS,
LITSORT_DEPTHTEST_INDEX = LITSORT_POLYGONOFFSET_INDEX + LITSORT_POLYGONOFFSET_BITS
};
#define MAX_TMUS 4
@ -852,15 +871,6 @@ typedef struct {
byte color2D[4];
trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering
// dynamic lights data set by the back-end for the GAL
qbool dlOpaque; // qtrue when drawing an opaque surface
float dlIntensity; // 1 for most surfaces, but can be scaled down for liquids etc.
unsigned int dlStateBits; // the state bits to apply for this draw call
// quick explanation on why dlOpaque is useful in the first place:
// - opaque surfaces can have a diffuse texture whose alpha isn't 255 everywhere
// - when that happens and we multiply the color by the the alpha (DL uses additive blending),
// we get "light holes" in opaque surfaces, which is not what we want
qbool renderFrame;
int* pc; // current stats set, depending on projection2D
@ -1119,9 +1129,11 @@ void R_AddMD3Surfaces( trRefEntity_t *e );
void R_AddPolygonSurfaces();
void R_AddDrawSurf(const surfaceType_t* surface, const shader_t* shader, int fogIndex, int staticGeoChunk = 0, int zppFirstIndex = 0, int zppIndexCount = 0, float radiusOverZ = 666.0f );
void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex );
void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex, int staticGeoChunk );
uint64_t R_ComposeSort( int entityNum, const shader_t* shader, int staticGeoChunk );
void R_DecomposeSort( uint64_t sort, int* entityNum, const shader_t** shader );
uint32_t R_ComposeLitSort( int entityNum, const shader_t* shader, int staticGeoChunk );
void R_DecomposeLitSort( uint32_t sort, int* entityNum, const shader_t** shader );
#define CULL_IN 0 // completely unclipped
@ -1311,7 +1323,6 @@ struct shaderCommands_t
vec2_t texCoords[SHADER_MAX_VERTEXES];
vec2_t texCoords2[SHADER_MAX_VERTEXES];
color4ub_t vertexColors[SHADER_MAX_VERTEXES];
unsigned int dlIndexes[SHADER_MAX_INDEXES];
stageVars_t svars[MAX_SHADER_STAGES];
stageVars_t svarsFog;
@ -1323,7 +1334,6 @@ struct shaderCommands_t
int numIndexes;
int numVertexes;
int dlNumIndexes;
const dlight_t* light;

View file

@ -1280,13 +1280,14 @@ static float SurfGreyscaleAmount( const shader_t* shader )
}
void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex, int staticGeoChunk, int zppFirstIndex, int zppIndexCount, float radiusOverZ )
// @TODO: remove the fogIndex argument
void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int /*fogIndex*/, int staticGeoChunk, int zppFirstIndex, int zppIndexCount, float radiusOverZ )
{
if (tr.refdef.numDrawSurfs >= MAX_DRAWSURFS)
return;
drawSurf_t* const drawSurf = &tr.refdef.drawSurfs[tr.refdef.numDrawSurfs++];
drawSurf->sort = R_ComposeSort( tr.currentEntityNum, shader, fogIndex );
drawSurf->sort = R_ComposeSort( tr.currentEntityNum, shader, staticGeoChunk );
drawSurf->surface = surface;
drawSurf->model = tr.currentModel != NULL ? tr.currentModel->index : 0;
drawSurf->shaderNum = shader->index;
@ -1298,7 +1299,8 @@ void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int fo
}
void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex )
// @TODO: remove the fogIndex argument
void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int /*fogIndex*/, int staticGeoChunk )
{
if (tr.refdef.numLitSurfs >= MAX_DRAWSURFS)
return;
@ -1306,10 +1308,11 @@ void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int fog
tr.pc[RF_LIT_SURFS]++;
litSurf_t* const litSurf = &tr.refdef.litSurfs[tr.refdef.numLitSurfs++];
litSurf->sort = R_ComposeSort( tr.currentEntityNum, shader, fogIndex );
litSurf->sort = R_ComposeLitSort( tr.currentEntityNum, shader, staticGeoChunk );
litSurf->surface = surface;
litSurf->shaderNum = shader->index;
litSurf->greyscale = SurfGreyscaleAmount( shader );
litSurf->staticGeoChunk = staticGeoChunk;
if (!tr.light->head)
tr.light->head = litSurf;
@ -1339,7 +1342,28 @@ void R_DecomposeSort( uint64_t sort, int* entityNum, const shader_t** shader )
{
*entityNum = ( sort >> DRAWSORT_ENTITY_INDEX ) & MAX_REFENTITIES;
*shader = tr.sortedShaders[ ( sort >> DRAWSORT_SHADER_INDEX ) & (MAX_SHADERS-1) ];
}
uint32_t R_ComposeLitSort( int entityNum, const shader_t* shader, int staticGeoChunk )
{
const int stageIndex = max( shader->lightingStages[ST_DIFFUSE], 0 );
const int depthTestEquals = ( shader->stages[stageIndex]->stateBits & GLS_DEPTHFUNC_EQUAL ) != 0;
return
( (uint32_t)entityNum << LITSORT_ENTITY_INDEX ) |
( (uint32_t)shader->sortedIndex << LITSORT_SHADER_INDEX ) |
( (uint32_t)( staticGeoChunk > 0 ? 0 : 1 ) << LITSORT_STATICGEO_INDEX ) |
( (uint32_t)shader->cullType << LITSORT_CULLTYPE_INDEX ) |
( (uint32_t)shader->polygonOffset << LITSORT_POLYGONOFFSET_INDEX ) |
( (uint32_t)depthTestEquals << LITSORT_DEPTHTEST_INDEX );
}
void R_DecomposeLitSort( uint32_t sort, int* entityNum, const shader_t** shader )
{
*entityNum = ( sort >> LITSORT_ENTITY_INDEX ) & MAX_REFENTITIES;
*shader = tr.sortedShaders[ ( sort >> LITSORT_SHADER_INDEX ) & (MAX_SHADERS-1) ];
}

View file

@ -35,6 +35,10 @@ SURFACE SHADERS
shaderCommands_t tess;
// @TODO: remove all this...
#if 0
// we must set some things up before beginning any tesselation
// because a surface may be forced to perform a RB_End due to overflow
@ -251,3 +255,6 @@ void RB_EndSurface()
// clear shader so we can tell we don't have any unclosed surfaces
tess.numIndexes = 0;
}
#endif

View file

@ -1809,8 +1809,8 @@ static void SortNewShader()
const int numLitSurfs = tr.refdef.numLitSurfs;
litSurf_t* litSurfs = tr.refdef.litSurfs;
for ( i = 0; i < numLitSurfs; ++i, ++litSurfs ) {
R_DecomposeSort( litSurfs->sort, &entityNum, &wrongShader );
litSurfs->sort = R_ComposeSort( entityNum, tr.shaders[litSurfs->shaderNum], drawSurfs->staticGeoChunk );
R_DecomposeLitSort( litSurfs->sort, &entityNum, &wrongShader );
litSurfs->sort = R_ComposeLitSort( entityNum, tr.shaders[litSurfs->shaderNum], drawSurfs->staticGeoChunk );
}
}

View file

@ -350,6 +350,10 @@ static void R_AddLitSurface( msurface_t* surf, const dlight_t* light )
if ( surf->shader->sort < SS_OPAQUE )
return;
// lighting a fog box' surface looks absolutely terrible
if ( (surf->shader->contentFlags & CONTENTS_FOG) != 0 )
return;
if ( surf->lightCount == tr.lightCount )
return; // already in the lit list (or already culled) for this light
@ -374,7 +378,7 @@ static void R_AddLitSurface( msurface_t* surf, const dlight_t* light )
return;
}
R_AddLitSurf( surf->data, surf->shader, surf->fogIndex );
R_AddLitSurf( surf->data, surf->shader, surf->fogIndex, surf->staticGeoChunk );
}

View file

@ -49,7 +49,9 @@ void CompileShader(const ShaderArgs& args, int extraCount = 0, const char** extr
{
static char temp[4096];
strcpy(temp, va("dxc.exe -Fh %s -E %s -T %s -WX",
// -Ges: Enable strict mode
// -Gis: Force IEEE strictness
strcpy(temp, va("dxc.exe -Fh %s -E %s -T %s -WX -Ges -Gis",
args.headerPath, args.entryPoint, args.targetProfile));
for(int i = 0; i < extraCount; ++i)
@ -231,6 +233,7 @@ int main(int /*argc*/, const char** argv)
CompileVSAndPS("imgui", "imgui.hlsl");
CompileVSAndPS("ui", "ui.hlsl");
CompileVSAndPS("depth_pre_pass", "depth_pre_pass.hlsl");
CompileVSAndPS("dynamic_light", "dynamic_light.hlsl");
CompileVS("fog_vs.h", "fog_inside.hlsl");
CompilePS("fog_inside_ps.h", "fog_inside.hlsl");
CompilePS("fog_outside_ps.h", "fog_outside.hlsl");

View file

@ -259,6 +259,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\depth_pre_pass.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\dynamic_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\fog_inside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>

View file

@ -56,6 +56,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\depth_pre_pass.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\dynamic_light.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\fog_inside.hlsl">
<Filter>hlsl</Filter>
</FxCompile>

View file

@ -263,6 +263,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\depth_pre_pass.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\dynamic_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\fog_inside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>

View file

@ -56,6 +56,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\depth_pre_pass.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\dynamic_light.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\fog_inside.hlsl">
<Filter>hlsl</Filter>
</FxCompile>