Skip to content

Commit

Permalink
Merge branch 'terrain-height-baking' into 'main'
Browse files Browse the repository at this point in the history
[REMIX-2105] Integrating Terrain Baking with POM (raymarch only for now)

See merge request lightspeedrtx/dxvk-remix-nv!687
  • Loading branch information
MarkEHenderson committed Feb 27, 2024
2 parents ac93a4b + e89df94 commit 9549ee6
Show file tree
Hide file tree
Showing 25 changed files with 410 additions and 64 deletions.
5 changes: 3 additions & 2 deletions documentation/TerrainSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ Supported:
- Replacement materials - AlbedoOpacity texture and secondary PBR textures: Normal, Tangent, Roughness, Metallic and Emissive texture

Caveats/limitations:
- Material property constants [rtx.terrainBaker.material.properties.*](../RtxOptions.md) used by Terrain Baker are set for the material used for terrain rendering. These properties are not baked. If a replacement material for any of the input terrain texture layers replaces this constant (i.e. roughness, emissive, etc. ), then the shared constant will not be used. Therefore all materials tagged as terrain need to have this replacement texture type set. In the future this could be handled by baking the constant into a terrain texture if a replacement texture is specified for any of the active terrain meshes.
- Material property constants [rtx.terrainBaker.material.properties.*](../RtxOptions.md) used by Terrain Baker are set for the material used for terrain rendering. These properties are not baked. If a replacement material for any of the input terrain texture layers replaces this constant (i.e. roughness, emissive, etc. ), then the shared constant will not be used. Therefore all materials tagged as terrain need to have the same types of replacement textures set. In the future this could be handled by baking the constant into a terrain texture if a replacement texture is specified for any of the active terrain meshes.
- Using too high resolution replacement textures can have noticeable performance impact. Adjust [rtx.terrainBaker.material.maxResolutionToUseForReplacementMaterials](../RtxOptions.md) to balance quality vs performance cost of baking replacement textures.
- Terrain baking is not free. It has a computational and memory cost. You can parametrize its properties to suit your needs and memory limitations. In the future there may be a more adaptive mechanism to fit in-game scenario and resource availability. Tune following settings to adjust resolution and memory overhead of the Terrain System [rtx.terrainBaker.cascadeMap.*](../RtxOptions.md) and [rtx.terrainBaker.material.maxResolutionToUseForReplacementMaterials](../RtxOptions.md).
- Programmable shaders with Shader Model 2.0+ utilize a preprocessing compute pass to support baking of secondary PBR textures. This is an expensive operation both performance and memory wise. Draw calls using fixed function pipeline and programmable pipelines with Shader Model 1.0 instead rely on shader injection. This shader injection functionality is controlled via [rtx.terrainBaker.material.replacementSupportInPS_programmableShaders](../RtxOptions.md) and [rtx.terrainBaker.material.replacementSupportInPS_fixedFunction](../RtxOptions.md) that needs to be set prior to launching the game on modification. Both of these are enabled by default. While these options should work they are still in experimental stage. If you observe any crashes or quality issues which get fixed by disabling the two options then file github issues for them.
- Differences between the UV tiling size of the incoming terrain draw calls and the baked terrain map can cause precision loss in baked heightmaps. Specifically, the baked heightmap's max displacement distance may be significantly larger (or smaller) than the actual max displacement distance of any of the incoming draw calls, resulting in a loss of detail. [rtx.terrainBaker.material.properties.displaceInFactor](../RtxOptions.md) can be used to fix that by shrinking the baked heightmap's max displacement. When it is too low, the displacement will lack detail. When it is too high, the lowest parts of the POM will flatten out. The `Calculate Scene's Optimal Displace In Factor` button in the terrain material properties panel can be used to automatically calculate the highest safe value for a given scene. `rtx.conf` creators should use that button in representative sample of a game's levels, and then set `displaceInFactor` to the lowest observed value.

Future considerations / not supported (yet):
- Material constants per input terrain replacement material.
- Baking of height maps.
- Supporting Quadtree POM with baked heightmaps.
- Orthogonal surfaces (i.e. walls with floor) and separate multi-layer surfaces (i.e. baked road on a bridge over another baked road below). For these cases you can use the decal system. The benefit of the terrain system to the decal system is that the terrain baking exactly replicates original game's blending while the decal system only approximates the original blending. On the other hand, decal system supports blending in any direction and the input decal textures are sampled at the desired resolution at the time of ray hits the surface while the terrain system resamples original textures to a shared terrain texture. This can result in a of loss of image fidelity depending on the parametrization of the terrain system (see [rtx.terrainBaker.*](../RtxOptions.md)).
4 changes: 4 additions & 0 deletions src/d3d9/d3d9_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6478,6 +6478,9 @@ namespace dxvk {

data->Stages[i].BumpEnvLScale = bit::cast<float>(m_state.textureStages[i][DXVK_TSS_BUMPENVLSCALE]);
data->Stages[i].BumpEnvLOffset = bit::cast<float>(m_state.textureStages[i][DXVK_TSS_BUMPENVLOFFSET]);
// NV-DXVK start: support height map scaling in terrain baking
data->Stages[i].textureScale = 1.f;
// NV-DXVK end
}
}

Expand Down Expand Up @@ -7193,6 +7196,7 @@ namespace dxvk {

auto& rs = m_state.renderStates;
DecodeD3DCOLOR((D3DCOLOR) rs[D3DRS_TEXTUREFACTOR], data->textureFactor.data);
data->textureScale = 1.f;

EmitCs([
cBuffer = m_psFixedFunction,
Expand Down
89 changes: 81 additions & 8 deletions src/d3d9/d3d9_fixed_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ namespace dxvk {

float_t,
float_t,

// NV-DXVK start: support height map scaling in terrain baking
float_t,
// NV-DXVK end
};

std::array<decltype(stageMembers), caps::TextureStageCount> members;
Expand Down Expand Up @@ -417,8 +421,11 @@ namespace dxvk {
spvModule.memberDecorateOffset(structType, stage * D3D9SharedPSStages_Count + D3D9SharedPSStages_BumpEnvLOffset, offset);
offset += sizeof(float);

spvModule.memberDecorateOffset(structType, stage * D3D9SharedPSStages_Count + D3D9SharedPSStages_TextureScale, offset);
offset += sizeof(float);

// Padding...
offset += sizeof(float) * 2;
offset += sizeof(float);
}

uint32_t sharedState = spvModule.newVar(
Expand Down Expand Up @@ -531,6 +538,7 @@ namespace dxvk {

enum D3D9FFPSMembers {
TextureFactor = 0,
TextureScale = 1,

MemberCount
};
Expand All @@ -541,6 +549,7 @@ namespace dxvk {

struct {
uint32_t textureFactor;
uint32_t textureScale;
} constants;

struct {
Expand Down Expand Up @@ -1680,6 +1689,9 @@ namespace dxvk {
uint32_t postprocessTextureReadForTerrainBaking(
SpirvModule& spvModule,
uint32_t textureValue,
uint32_t texcoord,
uint32_t texcoordType,
std::function<uint32_t()> loadTextureScaleFnc,
std::function<uint32_t()> loadAlbedoOpacityFnc,
std::function<void(uint32_t vec4value)> storeVec4ValueToRegisterFnc,
std::function<uint32_t()> loadVec4ValueFromRegisterFnc) {
Expand All @@ -1689,6 +1701,7 @@ namespace dxvk {
uint32_t floatType = spvModule.defFloatType(32);
uint32_t uint32Type = spvModule.defIntType(32, 0);
uint32_t vec4Type = spvModule.defVectorType(floatType, 4);
uint32_t vec2Type = spvModule.defVectorType(floatType, 2);

// Initialize spec constant for texture category
uint32_t textureCategory = spvModule.specConst32(uint32Type, 0);
Expand All @@ -1713,6 +1726,55 @@ namespace dxvk {
// Note: passing true as the value has already been stored to register storage before the if check above
textureValue = conditionallyDecodeReplacementTextureValue(spvModule, textureValue, textureCategory, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc, true);

// Scale the texture value if it uses a different constant multiplier than the baked output
{
// Branch labels
uint32_t scaleBeginLabel = spvModule.allocateId();
uint32_t scaleEndLabel = spvModule.allocateId();

storeVec4ValueToRegisterFnc(textureValue);

// if (replacement_texture_category == ReplacementMaterialTextureCategory::SecondaryScaled) { ... }
uint32_t isScaled = spvModule.opIEqual(boolType, textureCategory, spvModule.constu32(ReplacementMaterialTextureCategory::SecondaryScaled));
spvModule.opSelectionMerge(scaleEndLabel, spv::SelectionControlMaskNone);
spvModule.opBranchConditional(isScaled, scaleBeginLabel, scaleEndLabel);
{
// Scale the input texture
spvModule.opLabel(scaleBeginLabel);

// Account for the difference in UV density between the input terrain material and the baked terrain.
// This part is just doing the "divide by input uv density". The baked UV density is pre multiplied into the textureScale.
// We want the max length for the uv delta in either X or Y.
// Get the square of both deltas: lenSquare(x) = dot(x, x)
uint32_t uvDeltaX = spvModule.opDpdx(texcoordType, texcoord); // uv delta for 1 pixel horizontally
uint32_t uvDeltaXSqr = spvModule.opDot(floatType, uvDeltaX, uvDeltaX);
uint32_t uvDeltaY = spvModule.opDpdy(texcoordType, texcoord);
uint32_t uvDeltaYSqr = spvModule.opDot(floatType, uvDeltaY, uvDeltaY);
// Find the larger square, then take the square root of it.
uint32_t maxDelta = spvModule.opFMax(floatType, uvDeltaXSqr, uvDeltaYSqr);
maxDelta = spvModule.opSqrt(floatType, maxDelta);

// Divide textureScale by the max input uv delta.
uint32_t textureScale = spvModule.opFDiv(floatType, loadTextureScaleFnc(), maxDelta);

// Scale the texture components towards 1 instead of towards 0 by doing:
// new value = 1 - (textureScale * (1 - value))
textureValue = spvModule.opFSub(vec4Type, spvModule.constvec4f32(1.0f, 1.0f, 1.0f, 1.0f), textureValue);
textureValue = spvModule.opVectorTimesScalar(vec4Type, textureValue, textureScale);
textureValue = spvModule.opFSub(vec4Type, spvModule.constvec4f32(1.0f, 1.0f, 1.0f, 1.0f), textureValue);

// The new texture value needs to be stored before branching out, and reloaded after
storeVec4ValueToRegisterFnc(textureValue);

spvModule.opBranch(scaleEndLabel);
spvModule.opLabel(scaleEndLabel);

}// ~Scale the input texture

// reload the scaled value
textureValue = loadVec4ValueFromRegisterFnc();
}

// Add opacity to the secondary replacement texture
{
uint32_t albedoOpacityTexture = loadAlbedoOpacityFnc();
Expand Down Expand Up @@ -1930,8 +1992,15 @@ namespace dxvk {
auto loadVec4ValueFromRegisterFnc = [&]() -> uint32_t {
return m_module.opLoad(vec4Type, registerStorage);
};
auto loadTextureScaleFnc = [&]() -> uint32_t {
uint32_t textureScaleOffset = m_module.constu32(D3D9SharedPSStages_Count * i + D3D9SharedPSStages_TextureScale);
uint32_t textureScalePtr = m_module.opAccessChain(m_module.defPointerType(m_floatType, spv::StorageClassUniform),
m_ps.sharedState, 1, &textureScaleOffset);
return m_module.opLoad(m_floatType, textureScalePtr);
};


return postprocessTextureReadForTerrainBaking(m_module, textureValue, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc);
return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoord, texcoord_t, loadTextureScaleFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc);
};

texture = postprocessColorOutputTextureRead(texture);
Expand Down Expand Up @@ -2305,7 +2374,8 @@ namespace dxvk {

// Constant Buffer for PS.
std::array<uint32_t, uint32_t(D3D9FFPSMembers::MemberCount)> members = {
m_vec4Type // Texture Factor
m_vec4Type, // Texture Factor
m_floatType // Texture Scale
};

const uint32_t structType =
Expand All @@ -2314,13 +2384,15 @@ namespace dxvk {
m_module.decorateBlock(structType);
uint32_t offset = 0;

for (uint32_t i = 0; i < uint32_t(D3D9FFPSMembers::MemberCount); i++) {
m_module.memberDecorateOffset(structType, i, offset);
offset += sizeof(Vector4);
}
m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TextureFactor, offset);
offset += sizeof(Vector4);

m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TextureScale, offset);
offset += sizeof(float);

m_module.setDebugName(structType, "D3D9FixedFunctionPS");
m_module.setDebugMemberName(structType, 0, "textureFactor");
m_module.setDebugMemberName (structType, D3D9FFPSMembers::TextureFactor, "TextureFactor");
m_module.setDebugMemberName (structType, D3D9FFPSMembers::TextureScale, "TextureScale");

m_ps.constantBuffer = m_module.newVar(
m_module.defPointerType(structType, spv::StorageClassUniform),
Expand Down Expand Up @@ -2352,6 +2424,7 @@ namespace dxvk {
};

m_ps.constants.textureFactor = LoadConstant(m_vec4Type, uint32_t(D3D9FFPSMembers::TextureFactor));
m_ps.constants.textureScale = LoadConstant(m_floatType, uint32_t(D3D9FFPSMembers::TextureScale));

// Samplers
for (uint32_t i = 0; i < caps::TextureStageCount; i++) {
Expand Down
3 changes: 3 additions & 0 deletions src/d3d9/d3d9_fixed_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ namespace dxvk {
uint32_t postprocessTextureReadForTerrainBaking(
SpirvModule& spvModule,
uint32_t textureValue,
uint32_t texcoord,
uint32_t texcoordType,
std::function<uint32_t()> loadTextureScaleFnc,
std::function<uint32_t()> loadAlbedoOpacityFnc,
std::function<void(uint32_t vec4value)> storeVec4ValueToRegisterFnc,
std::function<uint32_t()> loadVec4ValueFromRegisterFnc);
Expand Down
3 changes: 2 additions & 1 deletion src/d3d9/d3d9_rtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ namespace dxvk {
// Get constant buffer bindings from D3D9
m_parent->EmitCs([vertexCaptureCB = m_vsVertexCaptureData](DxvkContext* ctx) {
const uint32_t vsFixedFunctionConstants = computeResourceSlotId(DxsoProgramType::VertexShader, DxsoBindingType::ConstantBuffer, DxsoConstantBuffers::VSFixedFunction);
static_cast<RtxContext*>(ctx)->setConstantBuffers(vsFixedFunctionConstants, vertexCaptureCB);
const uint32_t psSharedStateConstants = computeResourceSlotId(DxsoProgramType::PixelShader, DxsoBindingType::ConstantBuffer, DxsoConstantBuffers::PSShared);
static_cast<RtxContext*>(ctx)->setConstantBuffers(vsFixedFunctionConstants, psSharedStateConstants, vertexCaptureCB);
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/d3d9/d3d9_spec_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ namespace dxvk {

SamplerDepthMode = 10,

// NV-DXVK start
CustomVertexTransformEnabled = 11,
ReplacementTextureCategory = 12,
// NV-DXVK end

Count
};
Expand Down
9 changes: 8 additions & 1 deletion src/d3d9/d3d9_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ namespace dxvk {

struct D3D9FixedFunctionPS {
Vector4 textureFactor;
float textureScale;
};

enum D3D9SharedPSStages {
Expand All @@ -172,6 +173,9 @@ namespace dxvk {
D3D9SharedPSStages_BumpEnvMat1,
D3D9SharedPSStages_BumpEnvLScale,
D3D9SharedPSStages_BumpEnvLOffset,
// NV-DXVK start: support height map scaling in terrain baking
D3D9SharedPSStages_TextureScale,
// NV-DXVK end
D3D9SharedPSStages_Count,
};

Expand All @@ -181,7 +185,10 @@ namespace dxvk {
float BumpEnvMat[2][2];
float BumpEnvLScale;
float BumpEnvLOffset;
float Padding[2];
// NV-DXVK start: support height map scaling in terrain baking
float textureScale;
float Padding;
// NV-DXVK end
} Stages[8];
};

Expand Down
11 changes: 10 additions & 1 deletion src/dxso/dxso_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3184,7 +3184,16 @@ void DxsoCompiler::emitControlFlowGenericLoop(
return m_module.opLoad(vec4Type, regPtr.id);
};

return postprocessTextureReadForTerrainBaking(m_module, textureValue, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc);
auto loadTextureScaleFnc = [&]() -> uint32_t {
// Get the texture scale for heightmaps.
uint32_t textureScaleOffset = m_module.constu32(D3D9SharedPSStages_Count * ctx.dst.id.num + D3D9SharedPSStages_TextureScale);
uint32_t textureScalePtr = m_module.opAccessChain(m_module.defPointerType(floatType, spv::StorageClassUniform),
m_ps.sharedState, 1, &textureScaleOffset);

return m_module.opLoad(floatType, textureScalePtr);
};

return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoordVar.id, getVectorTypeId(texcoordVar.type), loadTextureScaleFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc);
};

result.id = postprocessColorOutputTextureRead(result.id);
Expand Down
15 changes: 11 additions & 4 deletions src/dxvk/rtx_render/rtx_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,9 @@ namespace dxvk {
Metrics::log(Metric::sys_memory_usage, static_cast<float>(sysUsageMib)); // In MB
}

void RtxContext::setConstantBuffers(const uint32_t vsFixedFunctionConstants, Rc<DxvkBuffer> vertexCaptureCB) {
void RtxContext::setConstantBuffers(const uint32_t vsFixedFunctionConstants, const uint32_t psSharedStateConstants, Rc<DxvkBuffer> vertexCaptureCB) {
m_rtState.vsFixedFunctionCB = m_rc[vsFixedFunctionConstants].bufferSlice.buffer();
m_rtState.psSharedStateCB = m_rc[psSharedStateConstants].bufferSlice.buffer();
m_rtState.vertexCaptureCB = vertexCaptureCB;
}

Expand Down Expand Up @@ -1780,12 +1781,18 @@ namespace dxvk {
return *static_cast<D3D9RtxVertexCaptureData*>(slice.mapPtr);
}

D3D9FixedFunctionVS& RtxContext::allocAndMapFixedFunctionConstantBuffer() {
D3D9FixedFunctionVS& RtxContext::allocAndMapFixedFunctionVSConstantBuffer() {
DxvkBufferSliceHandle slice = m_rtState.vsFixedFunctionCB->allocSlice();
invalidateBuffer(m_rtState.vsFixedFunctionCB, slice);

return *static_cast<D3D9FixedFunctionVS*>(slice.mapPtr);
}
D3D9SharedPS& RtxContext::allocAndMapPSSharedStateConstantBuffer() {
DxvkBufferSliceHandle slice = m_rtState.psSharedStateCB->allocSlice();
invalidateBuffer(m_rtState.psSharedStateCB, slice);

return *static_cast<D3D9SharedPS*>(slice.mapPtr);
}

void RtxContext::rasterizeToSkyMatte(const DrawParameters& params, float minZ, float maxZ) {
ScopedGpuProfileZone(this, "rasterizeToSkyMatte");
Expand Down Expand Up @@ -1959,7 +1966,7 @@ namespace dxvk {
newState.customWorldToProjection = proj * view;
} else {
// Push new state to the fixed function constants
D3D9FixedFunctionVS& newState = allocAndMapFixedFunctionConstantBuffer();
D3D9FixedFunctionVS& newState = allocAndMapFixedFunctionVSConstantBuffer();
newState = prevCB.fixedFunction;

// Create cube plane projection
Expand Down Expand Up @@ -2001,7 +2008,7 @@ namespace dxvk {
if (drawCallState.usesVertexShader) {
allocAndMapVertexCaptureConstantBuffer() = prevCB.programmablePipeline;
} else {
allocAndMapFixedFunctionConstantBuffer() = prevCB.fixedFunction;
allocAndMapFixedFunctionVSConstantBuffer() = prevCB.fixedFunction;
}
}

Expand Down
Loading

0 comments on commit 9549ee6

Please sign in to comment.