Added tesselation

This commit is contained in:
Justin Marshall
2026-05-12 14:08:28 -07:00
parent f41fe6722a
commit 135aa685b9
+359 -21
View File
@@ -659,6 +659,7 @@ struct BatchKey
PipelineMode pipeline = PIPE_OPAQUE_UNTEX;
D3D12_PRIMITIVE_TOPOLOGY topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
bool useTessellation = false;
UINT tex0SrvIndex = 0;
UINT tex1SrvIndex = 0;
@@ -775,6 +776,7 @@ static bool BatchKeyEquals(const BatchKey& a, const BatchKey& b)
RectEquals(a.scissor, b.scissor) &&
a.pipeline == b.pipeline &&
a.topology == b.topology &&
a.useTessellation == b.useTessellation &&
a.tex0SrvIndex == b.tex0SrvIndex &&
a.tex1SrvIndex == b.tex1SrvIndex &&
a.normalMapSrvIndex == b.normalMapSrvIndex &&
@@ -1294,6 +1296,8 @@ struct GLState
ComPtr<ID3D12PipelineState> postGBufferPointResolveMsaaPSO;
ComPtr<ID3DBlob> vsMainBlob;
ComPtr<ID3DBlob> hsMainBlob;
ComPtr<ID3DBlob> dsMainBlob;
ComPtr<ID3DBlob> psMainBlob;
ComPtr<ID3DBlob> psAlphaBlob;
ComPtr<ID3DBlob> psUntexturedBlob;
@@ -2301,6 +2305,7 @@ cbuffer DrawCB : register(b0)
#define gUseSpecularMap gMaterialMapPad.x
#define gSpecularMapStrength gMaterialMapPad.y
#define gAlphaBlendPass gMaterialMapPad.z
#define gTessellationDisplacement max(gMaterialMapPad.w, 0.0)
Texture2D gTex0 : register(t0);
Texture2D gTex1 : register(t1);
@@ -2338,6 +2343,8 @@ struct VSOut
float4 attr : TEXCOORD7;
float4 prevClip : TEXCOORD8;
float4 currClip : TEXCOORD9;
float3 objPos : TEXCOORD10;
float3 objNormal : TEXCOORD11;
float psize : PSIZE;
};
@@ -2529,14 +2536,28 @@ float3 QD3D12_DecodeTangentSpaceNormal(float3 encodedNormal)
return QD3D12_SafeNormalize(n, float3(0.0, 0.0, 1.0));
}
float QD3D12_HeightFromNormalSample(float4 nm)
{
// Many legacy normal maps are RGB/DXT uploads with an implicitly forced
// opaque alpha channel. Treat alpha as height only when it looks like real
// authored data; otherwise derive a conservative pseudo-height from the
// normal itself. A flat normal (0.5, 0.5, 1.0) stays centered at 0.5, so
// RGB-only normal maps do not push whole surfaces outward.
bool hasAuthoredHeight = (nm.a > 0.0001 && nm.a < 0.999);
float3 decoded = nm.xyz * 2.0 - 1.0;
float slopeHeight = 0.5 + (1.0 - saturate(abs(decoded.z))) * 0.25;
float h = hasAuthoredHeight ? nm.a : slopeHeight;
return saturate(h);
}
float QD3D12_GetHeightFromNormalMap(float2 uv)
{
float4 nm = gNormalMap.Sample(gSamp2, uv);
return QD3D12_HeightFromNormalSample(gNormalMap.Sample(gSamp2, uv));
}
float lum = dot(nm.rgb, float3(0.299, 0.587, 0.114));
float h = (nm.a > 0.0001) ? nm.a : ((lum + nm.b) * 0.5);
return saturate(h);
float QD3D12_GetHeightFromNormalMapLOD(float2 uv, float lod)
{
return QD3D12_HeightFromNormalSample(gNormalMap.SampleLevel(gSamp2, uv, lod));
}
float3 QD3D12_BuildFallbackTangent(float3 n)
@@ -2669,8 +2690,11 @@ float4 BuildTexturedColor(VSOut i)
gTexComb0RGB, gTexComb0Alpha, gTexComb0Operand, gTexEnvColor0);
}
if (gUseTex1 > 0.5 && !gUseNormalMap)
if (gUseTex1 > 0.5)
{
// Normal maps are bound through t2. Texture unit 1 remains a real fixed-function
// color/lightmap stage when the CPU side marks it as usable, so do not drop
// legacy lightmaps just because a normal map is active.
float4 tex1 = gTex1.Sample(gSamp1, uv1);
outColor = ApplyTexCombine(outColor, tex1, primary, gTexEnvMode1,
gTexComb1RGB, gTexComb1Alpha, gTexComb1Operand, gTexEnvColor1);
@@ -2749,6 +2773,8 @@ VSOut VSMain(VSIn i)
o.pos = currClip;
o.currClip = currClip;
o.prevClip = prevClip;
o.objPos = i.pos;
o.objNormal = i.normal;
o.fogCoord = abs(currClip.z / max(abs(currClip.w), 0.00001));
o.uv0 = i.uv0;
o.uv1 = i.uv1;
@@ -2761,6 +2787,153 @@ VSOut VSMain(VSIn i)
o.psize = gPointSize;
return o;
}
)HLSL"
R"HLSL(
struct TessCP
{
float4 pos : POSITION0;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float4 col : COLOR0;
float fogCoord : TEXCOORD2;
float3 worldPos : TEXCOORD3;
float3 normal : TEXCOORD4;
float3 tangent : TEXCOORD5;
float3 binormal : TEXCOORD6;
float4 attr : TEXCOORD7;
float4 prevClip : TEXCOORD8;
float4 currClip : TEXCOORD9;
float3 objPos : TEXCOORD10;
float3 objNormal : TEXCOORD11;
};
struct HSConstOut
{
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
TessCP QD3D12_MakeTessCP(VSOut i)
{
TessCP o;
o.pos = i.pos;
o.uv0 = i.uv0;
o.uv1 = i.uv1;
o.col = i.col;
o.fogCoord = i.fogCoord;
o.worldPos = i.worldPos;
o.normal = i.normal;
o.tangent = i.tangent;
o.binormal = i.binormal;
o.attr = i.attr;
o.prevClip = i.prevClip;
o.currClip = i.currClip;
o.objPos = i.objPos;
o.objNormal = i.objNormal;
return o;
}
float QD3D12_ComputeNormalMapTessFactor(InputPatch<VSOut, 3> patch)
{
float h0 = QD3D12_GetHeightFromNormalMapLOD(patch[0].uv0, 0.0);
float h1 = QD3D12_GetHeightFromNormalMapLOD(patch[1].uv0, 0.0);
float h2 = QD3D12_GetHeightFromNormalMapLOD(patch[2].uv0, 0.0);
float heightRange = max(abs(h0 - h1), max(abs(h1 - h2), abs(h2 - h0)));
// Keep the factor conservative. Most legacy normal maps are not authored as
// height maps, so aggressive tessellation creates cracks and flickering without
// adding useful detail. Height variation still nudges the factor upward.
float strength = saturate(max(gNormalMapStrength, 0.0));
float factor = 1.0 + strength + heightRange * 4.0;
return (gUseNormalMap > 0.5) ? clamp(factor, 1.0, 4.0) : 1.0;
}
HSConstOut HSMainConstants(InputPatch<VSOut, 3> patch)
{
HSConstOut o;
float factor = QD3D12_ComputeNormalMapTessFactor(patch);
o.edge[0] = factor;
o.edge[1] = factor;
o.edge[2] = factor;
o.inside = factor;
return o;
}
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_ccw")]
[outputcontrolpoints(3)]
[patchconstantfunc("HSMainConstants")]
TessCP HSMain(InputPatch<VSOut, 3> patch, uint cpId : SV_OutputControlPointID)
{
return QD3D12_MakeTessCP(patch[cpId]);
}
float2 QD3D12_Interp2(float2 a, float2 b, float2 c, float3 w)
{
return a * w.x + b * w.y + c * w.z;
}
float3 QD3D12_Interp3(float3 a, float3 b, float3 c, float3 w)
{
return a * w.x + b * w.y + c * w.z;
}
float4 QD3D12_Interp4(float4 a, float4 b, float4 c, float3 w)
{
return a * w.x + b * w.y + c * w.z;
}
[domain("tri")]
VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const OutputPatch<TessCP, 3> patch)
{
TessCP i;
i.pos = QD3D12_Interp4(patch[0].pos, patch[1].pos, patch[2].pos, bary);
i.uv0 = QD3D12_Interp2(patch[0].uv0, patch[1].uv0, patch[2].uv0, bary);
i.uv1 = QD3D12_Interp2(patch[0].uv1, patch[1].uv1, patch[2].uv1, bary);
i.col = QD3D12_Interp4(patch[0].col, patch[1].col, patch[2].col, bary);
i.fogCoord = patch[0].fogCoord * bary.x + patch[1].fogCoord * bary.y + patch[2].fogCoord * bary.z;
i.worldPos = QD3D12_Interp3(patch[0].worldPos, patch[1].worldPos, patch[2].worldPos, bary);
i.normal = QD3D12_Interp3(patch[0].normal, patch[1].normal, patch[2].normal, bary);
i.tangent = QD3D12_Interp3(patch[0].tangent, patch[1].tangent, patch[2].tangent, bary);
i.binormal = QD3D12_Interp3(patch[0].binormal, patch[1].binormal, patch[2].binormal, bary);
i.attr = QD3D12_Interp4(patch[0].attr, patch[1].attr, patch[2].attr, bary);
i.prevClip = QD3D12_Interp4(patch[0].prevClip, patch[1].prevClip, patch[2].prevClip, bary);
i.currClip = QD3D12_Interp4(patch[0].currClip, patch[1].currClip, patch[2].currClip, bary);
i.objPos = QD3D12_Interp3(patch[0].objPos, patch[1].objPos, patch[2].objPos, bary);
i.objNormal = QD3D12_Interp3(patch[0].objNormal, patch[1].objNormal, patch[2].objNormal, bary);
VSOut o;
float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0));
float height = QD3D12_GetHeightFromNormalMapLOD(i.uv0, 0.0);
float displacement = (height - 0.5) * gTessellationDisplacement;
float3 displacedObjPos = i.objPos + objNormal * displacement;
float4 worldPos = mul(gModelMatrix, float4(displacedObjPos, 1.0));
float4 currClip = mul(gMVP, float4(displacedObjPos, 1.0));
float4 prevClip = mul(gPrevMVP, float4(displacedObjPos, 1.0));
float3 worldNormal = mul((float3x3)gModelMatrix, objNormal);
currClip.z = 0.5 * (currClip.z + currClip.w);
o.pos = currClip;
o.currClip = currClip;
o.prevClip = prevClip;
o.objPos = displacedObjPos;
o.objNormal = objNormal;
o.fogCoord = abs(currClip.z / max(abs(currClip.w), 0.00001));
o.uv0 = i.uv0;
o.uv1 = i.uv1;
o.col = i.col;
o.worldPos = worldPos.xyz;
o.normal = QD3D12_SafeNormalize(worldNormal, QD3D12_SafeNormalize(i.normal, float3(0.0, 0.0, 1.0)));
o.tangent = QD3D12_SafeNormalize(i.tangent, QD3D12_BuildFallbackTangent(o.normal));
o.binormal = QD3D12_SafeNormalize(i.binormal, QD3D12_SafeNormalize(cross(o.normal, o.tangent), float3(0.0, 1.0, 0.0)));
o.attr = float4(geometryFlag, gRoughness, gMaterialType, 0.0);
o.psize = gPointSize;
return o;
}
PSOut PSMain(VSOut i)
{
@@ -3318,7 +3491,8 @@ static D3D12_PRIMITIVE_TOPOLOGY GetDrawTopology(GLenum originalMode)
return D3D_PRIMITIVE_TOPOLOGY_LINELIST; // we manually convert this, so im adjusting to be a linelist.
case GL_TRIANGLE_STRIP:
return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
// FlushImmediate expands strips into standalone triangles, so the IA sees a list.
return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
case GL_TRIANGLES:
default:
@@ -3334,6 +3508,11 @@ static float MapTexCombineMode(GLenum mode);
static float MapTexCombineSource(GLenum source);
static float MapTexCombineOperandRGB(GLenum operand);
static float MapTexCombineOperandAlpha(GLenum operand);
static bool QD3D12_ShouldEnableNormalMapTessellation(GLenum originalMode, const BatchKey& key);
static bool QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(GLenum originalMode, size_t vertexCount);
static bool QD3D12_UseNormalMapTessellationPSO(const BatchKey& key, bool nativeColorOnly);
static D3D12_PRIMITIVE_TOPOLOGY_TYPE QD3D12_EffectiveTopologyTypeForPSO(const BatchKey& key, bool nativeColorOnly);
static D3D12_PRIMITIVE_TOPOLOGY QD3D12_EffectiveIATopologyForDraw(const BatchKey& key, bool nativeColorOnly);
static UINT8 QD3D12_CurrentColorWriteMask()
{
@@ -3383,24 +3562,42 @@ static void QD3D12_FillTexCombineKey(BatchKey& key, UINT unit)
color[3] = g_gl.texEnvColor[unit][3];
}
static bool QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(const TextureResource* tex)
{
if (!tex || tex == &g_gl.whiteTexture || tex->glId == 0)
return false;
// Tagged/explicit material maps are sampled through their dedicated shader
// slots. Do not also feed them into fixed-function texture unit 0/1, or a
// normal/glow/specular map can replace the diffuse/lightmap color stage.
if (tex->isNormalMap || tex->isGlowMap || tex->isSpecularMap)
return true;
return tex->glId == g_gl.currentNormalMapTexture ||
tex->glId == g_gl.currentGlowMapTexture ||
tex->glId == g_gl.currentSpecularMapTexture;
}
#ifdef _DEBUG
#pragma optimize off
#endif
static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource* tex0, const TextureResource* tex1, TextureResource* const* allTextures)
{
const bool useTex0 = g_gl.texture2D[0];
const bool useTex1 = g_gl.texture2D[1];
const bool tex0IsMaterialMapOnly = QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(tex0);
const bool tex1IsMaterialMapOnly = QD3D12_TextureIsMaterialMapOnlyForFixedFunctionColor(tex1);
const bool useTex0 = g_gl.texture2D[0] && !tex0IsMaterialMapOnly;
const bool useTex1 = g_gl.texture2D[1] && !tex1IsMaterialMapOnly;
BatchKey key{};
key.pipeline = PickPipeline(useTex0, useTex1);
key.topology = GetDrawTopology(originalMode);
key.tex0SrvIndex = tex0 ? tex0->srvIndex : 0;
key.tex1SrvIndex = tex1 ? tex1->srvIndex : 0;
key.tex0SrvIndex = (useTex0 && tex0 && tex0->srvIndex != UINT_MAX) ? tex0->srvIndex : g_gl.whiteTexture.srvIndex;
key.tex1SrvIndex = (useTex1 && tex1 && tex1->srvIndex != UINT_MAX) ? tex1->srvIndex : g_gl.whiteTexture.srvIndex;
for (UINT i = 0; i < QD3D12_MaxTextureUnits; ++i)
key.textureSrvIndex[i] = (allTextures && allTextures[i]) ? allTextures[i]->srvIndex : 0;
TextureResource* normalMap = QD3D12_SelectNormalMapTexture(allTextures);
if (normalMap && normalMap->texture && normalMap->srvIndex != UINT_MAX)
if (normalMap && normalMap->texture && normalMap->srvIndex != UINT_MAX && normalMap->gpuValid)
{
key.normalMapSrvIndex = normalMap->srvIndex;
key.useNormalMap = 1.0f;
@@ -3412,6 +3609,7 @@ static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource*
}
key.normalMapStrength = g_gl.currentNormalMapStrength;
key.normalMapYSign = g_gl.currentNormalMapYSign;
key.useTessellation = false;
TextureResource* glowMap = QD3D12_SelectGlowMapTexture(allTextures);
if (glowMap && glowMap->texture && glowMap->srvIndex != UINT_MAX)
@@ -3496,6 +3694,9 @@ static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource*
key.geometryFlag = QD3D12_CurrentEffectiveGeometryFlag();
key.roughness = g_gl.currentSurfaceRoughness;
key.materialType = QD3D12_CurrentEffectiveMaterialType();
key.useTessellation = QD3D12_ShouldEnableNormalMapTessellation(originalMode, key);
if (key.useARBPrograms)
key.useTessellation = false;
g_gl.currObjectMVPs[key.motionObjectId] = key.mvp;
return key;
}
@@ -3944,6 +4145,115 @@ static D3D12_PRIMITIVE_TOPOLOGY_TYPE GetTopologyTypeFromTopology(D3D12_PRIMITIVE
}
}
static bool QD3D12_OriginalModeCanUseNormalMapTessellation(GLenum originalMode)
{
switch (originalMode)
{
case GL_TRIANGLES:
case GL_TRIANGLE_STRIP:
case GL_TRIANGLE_FAN:
case GL_QUADS:
case GL_QUAD_STRIP:
case GL_POLYGON:
return true;
default:
return false;
}
}
static bool QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(GLenum originalMode, size_t vertexCount)
{
if (!QD3D12_OriginalModeCanUseNormalMapTessellation(originalMode))
return false;
switch (originalMode)
{
case GL_TRIANGLES:
return vertexCount >= 3 && (vertexCount % 3) == 0;
case GL_TRIANGLE_STRIP:
case GL_TRIANGLE_FAN:
case GL_POLYGON:
return vertexCount >= 3;
case GL_QUADS:
case GL_QUAD_STRIP:
return vertexCount >= 4;
default:
return false;
}
}
static bool QD3D12_CurrentDrawLooks3DForTessellation()
{
if (g_gl.projStack.empty())
return false;
// UI/HUD paths in these engines normally use glOrtho. Keep tessellation out
// of those 2D passes and only allow it under a perspective projection.
return QD3D12_IsPerspectiveProjectionCM(g_gl.projStack.back().m);
}
static bool QD3D12_ShouldEnableNormalMapTessellation(GLenum originalMode, const BatchKey& key)
{
if (key.useARBPrograms)
return false;
if (key.useNormalMap <= 0.5f)
return false;
if (key.normalMapSrvIndex == UINT_MAX || key.normalMapSrvIndex == g_gl.whiteTexture.srvIndex)
return false;
if (key.topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
return false;
// Keep decals, alpha-tested cutouts, translucent passes, post-upscale overlays,
// shadow/color-mask-only work, and depth-disabled geometry on the proven VS/PS route.
if (key.pipeline != PIPE_OPAQUE_TEX && key.pipeline != PIPE_OPAQUE_UNTEX)
return false;
if (!key.depthTest || !key.depthWrite || key.colorWriteMask == 0)
return false;
if (!QD3D12_OriginalModeCanUseNormalMapTessellation(originalMode))
return false;
if (!QD3D12_CurrentDrawLooks3DForTessellation())
return false;
if (g_gl.framePhase == QD3D12_FRAME_NATIVE_POST_UPSCALE)
return false;
return true;
}
static bool QD3D12_UseNormalMapTessellationPSO(const BatchKey& key, bool nativeColorOnly)
{
return key.useTessellation &&
!nativeColorOnly &&
!key.useARBPrograms &&
(key.pipeline == PIPE_OPAQUE_TEX || key.pipeline == PIPE_OPAQUE_UNTEX) &&
key.depthTest &&
key.depthWrite &&
key.useNormalMap > 0.5f &&
key.normalMapSrvIndex != UINT_MAX &&
key.normalMapSrvIndex != g_gl.whiteTexture.srvIndex &&
key.topology == D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
}
static D3D12_PRIMITIVE_TOPOLOGY_TYPE QD3D12_EffectiveTopologyTypeForPSO(const BatchKey& key, bool nativeColorOnly)
{
return QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly)
? D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH
: GetTopologyTypeFromTopology(key.topology);
}
static D3D12_PRIMITIVE_TOPOLOGY QD3D12_EffectiveIATopologyForDraw(const BatchKey& key, bool nativeColorOnly)
{
return QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly)
? D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST
: key.topology;
}
// ============================================================
// SECTION 6: upload helpers
// ============================================================
@@ -5827,7 +6137,7 @@ static void QD3D12_CreateRootSignature()
params[1 + i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
params[1 + i].DescriptorTable.NumDescriptorRanges = 1;
params[1 + i].DescriptorTable.pDescriptorRanges = &ranges[i];
params[1 + i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
params[1 + i].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
}
D3D12_STATIC_SAMPLER_DESC samps[QD3D12_MaxTextureUnits] = {};
@@ -5839,7 +6149,7 @@ static void QD3D12_CreateRootSignature()
samps[i].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
samps[i].ShaderRegister = i;
samps[i].RegisterSpace = 0;
samps[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
samps[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
samps[i].MaxLOD = D3D12_FLOAT32_MAX;
}
@@ -5861,6 +6171,8 @@ static void QD3D12_CreateRootSignature()
static void QD3D12_CompileShaders()
{
g_gl.vsMainBlob = CompileShaderVariant("VSMain", "vs_6_0");
g_gl.hsMainBlob = CompileShaderVariant("HSMain", "hs_6_0");
g_gl.dsMainBlob = CompileShaderVariant("DSMain", "ds_6_0");
g_gl.psMainBlob = CompileShaderVariant("PSMain", "ps_6_0");
g_gl.psAlphaBlob = CompileShaderVariant("PSMainAlphaTest", "ps_6_0");
g_gl.psUntexturedBlob = CompileShaderVariant("PSMainUntextured", "ps_6_0");
@@ -5916,6 +6228,8 @@ static D3D12_CPU_DESCRIPTOR_HANDLE CurrentNormalRTV()
static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc(
PipelineMode mode,
ID3DBlob* vs,
ID3DBlob* hs,
ID3DBlob* ds,
ID3DBlob* ps,
const BatchKey& key,
D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType,
@@ -5924,12 +6238,18 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc(
D3D12_GRAPHICS_PIPELINE_STATE_DESC d{};
d.pRootSignature = g_gl.rootSig.Get();
d.VS = { vs->GetBufferPointer(), vs->GetBufferSize() };
const bool useTessellation = QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) && hs && ds;
if (useTessellation)
{
d.HS = { hs->GetBufferPointer(), hs->GetBufferSize() };
d.DS = { ds->GetBufferPointer(), ds->GetBufferSize() };
}
d.PS = { ps->GetBufferPointer(), ps->GetBufferSize() };
d.InputLayout.pInputElementDescs = kGLVertexInputLayout;
d.InputLayout.NumElements = _countof(kGLVertexInputLayout);
d.PrimitiveTopologyType = topoType;
d.PrimitiveTopologyType = useTessellation ? D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH : topoType;
d.NumRenderTargets = nativeColorOnly ? 1u : 6u;
d.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
@@ -6006,6 +6326,13 @@ static D3D12_GRAPHICS_PIPELINE_STATE_DESC BuildPSODesc(
if (alphaBlended)
depthStencilKey.depthWrite = false;
ApplyRasterDepthStencilState(d, depthStencilKey);
if (useTessellation)
{
// Hull/domain shader triangle orientation can differ on legacy content that
// flips GL front-face state. Avoid dropping tessellated patches entirely;
// depth still rejects hidden surfaces.
d.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
}
return d;
}
@@ -6125,6 +6452,7 @@ static uint64_t MakePSOKey(
mix(key.depthWrite ? 1ull : 0ull);
mix(uint32_t(key.depthFunc));
mix(uint32_t(topoType));
mix(QD3D12_UseNormalMapTessellationPSO(key, nativeColorOnly) ? 1ull : 0ull);
mix(nativeColorOnly ? 1ull : 0ull);
mix(nativeColorOnly ? 1ull : uint64_t(QD3D12_GBufferSampleCount()));
mix(uint32_t(key.colorWriteMask));
@@ -6178,6 +6506,8 @@ static ID3D12PipelineState* QD3D12_GetPSO(
auto desc = BuildPSODesc(
key.pipeline,
g_gl.vsMainBlob.Get(),
g_gl.hsMainBlob.Get(),
g_gl.dsMainBlob.Get(),
psBlob,
key,
topoType,
@@ -8190,6 +8520,8 @@ static void FlushImmediate(GLenum mode, const GLVertex* src, size_t n)
TextureResource* tex1 = boundTextures[1];
BatchKey key = BuildCurrentBatchKey(mode, tex0, tex1, boundTextures);
if (key.useTessellation && !QD3D12_ImmediateVertexCountCanUseNormalMapTessellation(mode, n))
key.useTessellation = false;
const size_t markerCursor = g_gl.queryMarkers.size();
QueuedBatch* batch = nullptr;
@@ -8596,7 +8928,12 @@ static void QD3D12_FlushQueuedBatches()
dc->materialMapPad[0] = batch.key.useSpecularMap;
dc->materialMapPad[1] = batch.key.specularMapStrength;
dc->materialMapPad[2] = QD3D12_PipelineUsesAlphaBlend(batch.key.pipeline) ? 1.0f : 0.0f;
dc->materialMapPad[3] = 0.0f;
// Normal-map strength is a shading control, not an object-space height in
// Quake/idTech units. Use it only as a small scalar for displacement so the
// tessellation pass cannot shove surfaces through walls or the near plane.
const float tessDisplacementScale = 0.03f;
dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ?
(ClampValue<float>(batch.key.normalMapStrength, 0.0f, 4.0f) * tessDisplacementScale) : 0.0f;
if (batch.key.useARBPrograms)
{
@@ -8612,9 +8949,9 @@ static void QD3D12_FlushQueuedBatches()
ID3D12PipelineState* pso = nullptr;
const D3D12_PRIMITIVE_TOPOLOGY_TYPE topoType =
GetTopologyTypeFromTopology(batch.key.topology);
QD3D12_EffectiveTopologyTypeForPSO(batch.key, nativeColorOnly);
if (topoType == D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT) {
if (GetTopologyTypeFromTopology(batch.key.topology) == D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT) {
dc->PointSize = g_gl.pointSize;
}
else {
@@ -8719,10 +9056,11 @@ static void QD3D12_FlushQueuedBatches()
g_gl.cmdList->SetGraphicsRootConstantBufferView(0, cbAlloc.gpu);
g_gl.cmdList->OMSetStencilRef(batch.key.stencilRef);
if (batch.key.topology != lastTopo)
const D3D12_PRIMITIVE_TOPOLOGY drawTopology = QD3D12_EffectiveIATopologyForDraw(batch.key, nativeColorOnly);
if (drawTopology != lastTopo)
{
g_gl.cmdList->IASetPrimitiveTopology(batch.key.topology);
lastTopo = batch.key.topology;
g_gl.cmdList->IASetPrimitiveTopology(drawTopology);
lastTopo = drawTopology;
}
g_gl.cmdList->IASetVertexBuffers(0, 1, &vbv);