From b7636e4b19b057ac786101b055c8a6abd0d4e4e5 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Wed, 23 Oct 2024 09:23:24 -0700 Subject: [PATCH] [REMIX-3048] supporting outwards displacement with POM --- RtxOptions.md | 2 +- documentation/RemixApiChangelog.md | 16 ++++ documentation/TerrainSystem.md | 3 +- packman-external.xml | 2 +- public/include/remix/remix.h | 3 +- public/include/remix/remix_c.h | 7 +- src/d3d9/d3d9_device.cpp | 4 + src/d3d9/d3d9_fixed_function.cpp | 64 ++++++++++++--- src/d3d9/d3d9_fixed_function.h | 2 + src/d3d9/d3d9_state.h | 10 ++- src/dxso/dxso_compiler.cpp | 32 +++++++- src/dxvk/rtx_render/rtx_debug_view.cpp | 2 +- src/dxvk/rtx_render/rtx_geometry_utils.cpp | 1 + src/dxvk/rtx_render/rtx_geometry_utils.h | 1 + src/dxvk/rtx_render/rtx_instance_manager.cpp | 5 +- src/dxvk/rtx_render/rtx_material_data.h | 1 + src/dxvk/rtx_render/rtx_materials.h | 60 +++++++++----- src/dxvk/rtx_render/rtx_remix_api.cpp | 6 +- src/dxvk/rtx_render/rtx_scene_manager.cpp | 18 ++-- src/dxvk/rtx_render/rtx_terrain_baker.cpp | 82 +++++++++++++------ src/dxvk/rtx_render/rtx_terrain_baker.h | 9 +- .../rtx/algorithm/geometry_resolver.slangh | 6 +- .../rtx/algorithm/integrator_indirect.slangh | 30 +++---- src/dxvk/shaders/rtx/algorithm/resolve.slangh | 4 +- .../shaders/rtx/algorithm/visibility.slangh | 53 ++++++------ .../shaders/rtx/concept/surface/surface.h | 6 -- .../surface/surface_interaction.slangh | 2 +- ...opaque_surface_material_interaction.slangh | 52 +++++++----- .../surface_material/surface_material.h | 26 ++++-- src/dxvk/shaders/rtx/pass/raytrace_args.h | 3 +- .../decode_and_add_opacity.comp.slang | 2 +- .../decode_and_add_opacity_binding_indices.h | 1 + .../shaders/rtx/utility/shared_constants.h | 3 +- tests/rtx/unit/test_pnext.cpp | 11 +++ 34 files changed, 358 insertions(+), 171 deletions(-) create mode 100644 documentation/RemixApiChangelog.md diff --git a/RtxOptions.md b/RtxOptions.md index 567d2c561..1c8937500 100644 --- a/RtxOptions.md +++ b/RtxOptions.md @@ -647,7 +647,7 @@ Tables below enumerate all the options and their defaults set by RTX Remix. Note |rtx.terrainBaker.material.bakeReplacementMaterials|bool|True|Enables baking of replacement materials when they are present\.| |rtx.terrainBaker.material.bakeSecondaryPBRTextures|bool|True|Enables baking of secondary textures in replacement materials when they are present\.
Secondary textures are all PBR textures except for albedoOpacity\. So that includes normal, roughness, etc\.| |rtx.terrainBaker.material.maxResolutionToUseForReplacementMaterials|int|8192|Max resolution to use for preprocessing and baking of input replacement material textures other than color opacity which is used as is\.
Applies only to a case when a preprocessing compute shader is used to support baking of secondary PBR materials\.
Replacement materials need to be preprocessed prior to baking them and limitting the max resolution allows to balance the quality vs performance cost\.| -|rtx.terrainBaker.material.properties.displaceInFactor|float|1|The max depth the baked terrain can support will be larger than the max depth
of any incoming draw call, which results in a loss of detail\. When this is
too low, the displacement will lack detail\. When it is too high, the lowest
parts of the POM will flatten out\.| +|rtx.terrainBaker.material.properties.displaceInFactor|float|1|The max depth and height the baked terrain can support will be larger than the max
of any incoming draw call, which results in a loss of detail\. When this is
too low, the displacement will lack detail\. When it is too high, the lowest
and highest parts of the POM will flatten out\. This affects both displaceIn
and displaceOut, despite the name\.| |rtx.terrainBaker.material.properties.emissiveColorConstant|float3|0, 0, 0|Emissive color constant\. Should be a color in sRGB colorspace with gamma encoding\.| |rtx.terrainBaker.material.properties.emissiveIntensity|float|0|Emissive intensity\.| |rtx.terrainBaker.material.properties.enableEmission|bool|False|A flag to determine if emission is enabled\.| diff --git a/documentation/RemixApiChangelog.md b/documentation/RemixApiChangelog.md new file mode 100644 index 000000000..89e0763f5 --- /dev/null +++ b/documentation/RemixApiChangelog.md @@ -0,0 +1,16 @@ +## Remix API Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.4.2] + +### Added +- MaterialInfoOpaqueEXT.displaceOut + +### Changed +- renamed MaterialInfoOpaqueEXT.heightTextureStrength to MaterialInfoOpaqueEXT.displaceIn + +### Fixed + +### Removed diff --git a/documentation/TerrainSystem.md b/documentation/TerrainSystem.md index 8ac8a9ee1..5199f2fb5 100644 --- a/documentation/TerrainSystem.md +++ b/documentation/TerrainSystem.md @@ -20,9 +20,8 @@ Caveats/limitations: - 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. +- 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 and highest parts of the POM will flatten out. The `Calculate Scene's Optimal Displacement 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. -- 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)). diff --git a/packman-external.xml b/packman-external.xml index 5e2750418..6bfdef8b7 100644 --- a/packman-external.xml +++ b/packman-external.xml @@ -15,7 +15,7 @@ - + diff --git a/public/include/remix/remix.h b/public/include/remix/remix.h index af6942ea0..5767d0205 100644 --- a/public/include/remix/remix.h +++ b/public/include/remix/remix.h @@ -275,13 +275,14 @@ namespace remix { thinFilmThickness_value = 200.f; alphaIsThinFilmThickness = false; heightTexture = {}; - heightTextureStrength = 0.0f; + displaceIn = 0.0f; useDrawCallAlphaState = true; blendType_hasvalue = false; blendType_value = 0; invertedBlend = false; alphaTestType = 7; alphaReferenceValue = 0; + displaceOut = 0.0f; static_assert(sizeof remixapi_MaterialInfoOpaqueEXT == 112); } diff --git a/public/include/remix/remix_c.h b/public/include/remix/remix_c.h index 1b5cd7f54..0834f0290 100644 --- a/public/include/remix/remix_c.h +++ b/public/include/remix/remix_c.h @@ -26,9 +26,11 @@ #include #include +#ifndef REMIX_ALLOW_X86 #if _WIN64 != 1 #error Remix API requires 64-bit for the ray tracing features. #endif +#endif // __stdcall convention @@ -52,7 +54,7 @@ #define REMIXAPI_VERSION_MAJOR 0 #define REMIXAPI_VERSION_MINOR 4 -#define REMIXAPI_VERSION_PATCH 1 +#define REMIXAPI_VERSION_PATCH 2 // External @@ -194,7 +196,7 @@ extern "C" { float thinFilmThickness_value; remixapi_Bool alphaIsThinFilmThickness; remixapi_Path heightTexture; - float heightTextureStrength; + float displaceIn; // If true, InstanceInfoBlendEXT is used as a source for alpha state remixapi_Bool useDrawCallAlphaState; remixapi_Bool blendType_hasvalue; @@ -202,6 +204,7 @@ extern "C" { remixapi_Bool invertedBlend; int alphaTestType; uint8_t alphaReferenceValue; + float displaceOut; } remixapi_MaterialInfoOpaqueEXT; // Valid only if remixapi_MaterialInfo contains remixapi_MaterialInfoOpaqueEXT in pNext chain diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 921791e45..e21834c1b 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -6665,7 +6665,9 @@ namespace dxvk { data->Stages[i].BumpEnvLScale = bit::cast(m_state.textureStages[i][DXVK_TSS_BUMPENVLSCALE]); data->Stages[i].BumpEnvLOffset = bit::cast(m_state.textureStages[i][DXVK_TSS_BUMPENVLOFFSET]); // NV-DXVK start: support height map scaling in terrain baking + data->Stages[i].texturePreOffset = 0.f; data->Stages[i].textureScale = 1.f; + data->Stages[i].texturePostOffset = 0.f; // NV-DXVK end } } @@ -7392,7 +7394,9 @@ namespace dxvk { auto& rs = m_state.renderStates; DecodeD3DCOLOR((D3DCOLOR) rs[D3DRS_TEXTUREFACTOR], data->textureFactor.data); + data->texturePreOffset = 0.f; data->textureScale = 1.f; + data->texturePostOffset = 0.f; EmitCs([ cBuffer = m_psFixedFunction, diff --git a/src/d3d9/d3d9_fixed_function.cpp b/src/d3d9/d3d9_fixed_function.cpp index d6fb0c1b4..3205de0c9 100644 --- a/src/d3d9/d3d9_fixed_function.cpp +++ b/src/d3d9/d3d9_fixed_function.cpp @@ -391,6 +391,8 @@ namespace dxvk { // NV-DXVK start: support height map scaling in terrain baking float_t, + float_t, + float_t, // NV-DXVK end }; @@ -422,12 +424,18 @@ namespace dxvk { offset += sizeof(float); // NV-DXVK start: support height map scaling in terrain baking + spvModule.memberDecorateOffset(structType, stage * D3D9SharedPSStages_Count + D3D9SharedPSStages_TexturePreOffset, offset); + offset += sizeof(float); + spvModule.memberDecorateOffset(structType, stage * D3D9SharedPSStages_Count + D3D9SharedPSStages_TextureScale, offset); offset += sizeof(float); -// NV-DXVK end - // Padding... + spvModule.memberDecorateOffset(structType, stage * D3D9SharedPSStages_Count + D3D9SharedPSStages_TexturePostOffset, offset); offset += sizeof(float); + + // Padding + offset += 3 * sizeof(float); +// NV-DXVK end } uint32_t sharedState = spvModule.newVar( @@ -540,7 +548,9 @@ namespace dxvk { enum D3D9FFPSMembers { TextureFactor = 0, - TextureScale = 1, + TexturePreOffset = 1, + TextureScale = 2, + TexturePostOffset = 3, MemberCount }; @@ -551,7 +561,9 @@ namespace dxvk { struct { uint32_t textureFactor; + uint32_t texturePreOffset; uint32_t textureScale; + uint32_t texturePostOffset; } constants; struct { @@ -1693,7 +1705,9 @@ namespace dxvk { uint32_t textureValue, uint32_t texcoord, uint32_t texcoordType, + std::function loadTexturePreOffsetFnc, std::function loadTextureScaleFnc, + std::function loadTexturePostOffsetFnc, std::function loadAlbedoOpacityFnc, std::function storeVec4ValueToRegisterFnc, std::function loadVec4ValueFromRegisterFnc) { @@ -1759,11 +1773,17 @@ namespace dxvk { // 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); + // The height values need to scale relative to a neutral value (which is different in the input image and the output map). + // First, subtract the neutral value from the pixel color: + uint32_t texturePreOffset4 = spvModule.opVectorTimesScalar(vec4Type, spvModule.constvec4f32(1.0f, 1.0f, 1.0f, 1.0f), loadTexturePreOffsetFnc()); + textureValue = spvModule.opFAdd(vec4Type, texturePreOffset4, textureValue); + + // Second, apply all of the scale operations: textureValue = spvModule.opVectorTimesScalar(vec4Type, textureValue, textureScale); - textureValue = spvModule.opFSub(vec4Type, spvModule.constvec4f32(1.0f, 1.0f, 1.0f, 1.0f), textureValue); + + // Third, add the output neutral value: + uint32_t texturePostOffset4 = spvModule.opVectorTimesScalar(vec4Type, spvModule.constvec4f32(1.0f, 1.0f, 1.0f, 1.0f), loadTexturePostOffsetFnc()); + textureValue = spvModule.opFAdd(vec4Type, texturePostOffset4, textureValue); // The new texture value needs to be stored before branching out, and reloaded after storeVec4ValueToRegisterFnc(textureValue); @@ -1994,15 +2014,27 @@ namespace dxvk { auto loadVec4ValueFromRegisterFnc = [&]() -> uint32_t { return m_module.opLoad(vec4Type, registerStorage); }; + auto loadTexturePreOffsetFnc = [&]() -> uint32_t { + uint32_t texturePreOffsetOffset = m_module.constu32(D3D9SharedPSStages_Count * kTerrainBakerSecondaryTextureStage + D3D9SharedPSStages_TexturePreOffset); + uint32_t texturePreOffsetPtr = m_module.opAccessChain(m_module.defPointerType(m_floatType, spv::StorageClassUniform), + m_ps.sharedState, 1, &texturePreOffsetOffset); + return m_module.opLoad(m_floatType, texturePreOffsetPtr); + }; auto loadTextureScaleFnc = [&]() -> uint32_t { uint32_t textureScaleOffset = m_module.constu32(D3D9SharedPSStages_Count * kTerrainBakerSecondaryTextureStage + 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); }; + auto loadTexturePostOffsetFnc = [&]() -> uint32_t { + uint32_t texturePostOffsetOffset = m_module.constu32(D3D9SharedPSStages_Count * kTerrainBakerSecondaryTextureStage + D3D9SharedPSStages_TexturePostOffset); + uint32_t texturePostOffsetPtr = m_module.opAccessChain(m_module.defPointerType(m_floatType, spv::StorageClassUniform), + m_ps.sharedState, 1, &texturePostOffsetOffset); + return m_module.opLoad(m_floatType, texturePostOffsetPtr); + }; - return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoord, texcoord_t, loadTextureScaleFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc); + return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoord, texcoord_t, loadTexturePreOffsetFnc, loadTextureScaleFnc, loadTexturePostOffsetFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc); }; texture = postprocessColorOutputTextureRead(texture); @@ -2376,8 +2408,10 @@ namespace dxvk { // Constant Buffer for PS. std::array members = { - m_vec4Type, // Texture Factor - m_floatType // Texture Scale + m_vec4Type, // TextureFactor + m_floatType, // TexturePreOffset + m_floatType, // TextureScale + m_floatType // TexturePostOffset }; const uint32_t structType = @@ -2389,12 +2423,20 @@ namespace dxvk { m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TextureFactor, offset); offset += sizeof(Vector4); + m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TexturePreOffset, offset); + offset += sizeof(float); + m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TextureScale, offset); offset += sizeof(float); + + m_module.memberDecorateOffset(structType, D3D9FFPSMembers::TexturePostOffset, offset); + offset += sizeof(float); m_module.setDebugName(structType, "D3D9FixedFunctionPS"); m_module.setDebugMemberName (structType, D3D9FFPSMembers::TextureFactor, "TextureFactor"); + m_module.setDebugMemberName (structType, D3D9FFPSMembers::TexturePreOffset, "TexturePreOffset"); m_module.setDebugMemberName (structType, D3D9FFPSMembers::TextureScale, "TextureScale"); + m_module.setDebugMemberName (structType, D3D9FFPSMembers::TexturePostOffset, "TexturePostOffset"); m_ps.constantBuffer = m_module.newVar( m_module.defPointerType(structType, spv::StorageClassUniform), @@ -2426,7 +2468,9 @@ namespace dxvk { }; m_ps.constants.textureFactor = LoadConstant(m_vec4Type, uint32_t(D3D9FFPSMembers::TextureFactor)); + m_ps.constants.texturePreOffset = LoadConstant(m_floatType, uint32_t(D3D9FFPSMembers::TexturePreOffset)); m_ps.constants.textureScale = LoadConstant(m_floatType, uint32_t(D3D9FFPSMembers::TextureScale)); + m_ps.constants.texturePostOffset = LoadConstant(m_floatType, uint32_t(D3D9FFPSMembers::TexturePostOffset)); // Samplers for (uint32_t i = 0; i < caps::TextureStageCount; i++) { diff --git a/src/d3d9/d3d9_fixed_function.h b/src/d3d9/d3d9_fixed_function.h index 81c1fbde5..45dd1debf 100644 --- a/src/d3d9/d3d9_fixed_function.h +++ b/src/d3d9/d3d9_fixed_function.h @@ -260,7 +260,9 @@ namespace dxvk { uint32_t textureValue, uint32_t texcoord, uint32_t texcoordType, + std::function loadTexturePreOffsetFnc, std::function loadTextureScaleFnc, + std::function loadTexturePostOffsetFnc, std::function loadAlbedoOpacityFnc, std::function storeVec4ValueToRegisterFnc, std::function loadVec4ValueFromRegisterFnc); diff --git a/src/d3d9/d3d9_state.h b/src/d3d9/d3d9_state.h index d6c4516e5..4a39cc607 100644 --- a/src/d3d9/d3d9_state.h +++ b/src/d3d9/d3d9_state.h @@ -168,7 +168,11 @@ namespace dxvk { struct D3D9FixedFunctionPS { Vector4 textureFactor; + // NV-DXVK start: support height map scaling in terrain baking + float texturePreOffset; float textureScale; + float texturePostOffset; + // NV-DXVK end }; enum D3D9SharedPSStages { @@ -178,7 +182,9 @@ namespace dxvk { D3D9SharedPSStages_BumpEnvLScale, D3D9SharedPSStages_BumpEnvLOffset, // NV-DXVK start: support height map scaling in terrain baking + D3D9SharedPSStages_TexturePreOffset, D3D9SharedPSStages_TextureScale, + D3D9SharedPSStages_TexturePostOffset, // NV-DXVK end D3D9SharedPSStages_Count, }; @@ -190,8 +196,10 @@ namespace dxvk { float BumpEnvLScale; float BumpEnvLOffset; // NV-DXVK start: support height map scaling in terrain baking + float texturePreOffset; float textureScale; - float Padding; + float texturePostOffset; + float padding[3]; // NV-DXVK end } Stages[8]; }; diff --git a/src/dxso/dxso_compiler.cpp b/src/dxso/dxso_compiler.cpp index 18959eb9b..938c1f5b6 100644 --- a/src/dxso/dxso_compiler.cpp +++ b/src/dxso/dxso_compiler.cpp @@ -3189,6 +3189,21 @@ void DxsoCompiler::emitControlFlowGenericLoop( return m_module.opLoad(vec4Type, regPtr.id); }; + auto loadTexturePreOffsetFnc = [&]() -> uint32_t { + if (m_programInfo.type() == DxsoProgramTypes::PixelShader && m_ps.sharedState != 0) { + // Get the texture Offset for heightmaps. + + // The correct index for baking the terrain to a dynamic texture stage is unclear here - + // both `ctx.dst.id.num` and `samplerIdx` can both have values greater than caps::TextureStageCount. + uint32_t texturePreOffsetOffset = m_module.constu32(D3D9SharedPSStages_Count * kTerrainBakerSecondaryTextureStage + D3D9SharedPSStages_TexturePreOffset); + uint32_t texturePreOffsetPtr = m_module.opAccessChain(m_module.defPointerType(floatType, spv::StorageClassUniform), + m_ps.sharedState, 1, &texturePreOffsetOffset); + + return m_module.opLoad(floatType, texturePreOffsetPtr); + } + return m_module.constf32(1.0); + }; + auto loadTextureScaleFnc = [&]() -> uint32_t { if (m_programInfo.type() == DxsoProgramTypes::PixelShader && m_ps.sharedState != 0) { // Get the texture scale for heightmaps. @@ -3204,7 +3219,22 @@ void DxsoCompiler::emitControlFlowGenericLoop( return m_module.constf32(1.0); }; - return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoordVar.id, getVectorTypeId(texcoordVar.type), loadTextureScaleFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc); + auto loadTexturePostOffsetFnc = [&]() -> uint32_t { + if (m_programInfo.type() == DxsoProgramTypes::PixelShader && m_ps.sharedState != 0) { + // Get the texture Offset for heightmaps. + + // The correct index for baking the terrain to a dynamic texture stage is unclear here - + // both `ctx.dst.id.num` and `samplerIdx` can both have values greater than caps::TextureStageCount. + uint32_t texturePostOffsetOffset = m_module.constu32(D3D9SharedPSStages_Count * kTerrainBakerSecondaryTextureStage + D3D9SharedPSStages_TexturePostOffset); + uint32_t texturePostOffsetPtr = m_module.opAccessChain(m_module.defPointerType(floatType, spv::StorageClassUniform), + m_ps.sharedState, 1, &texturePostOffsetOffset); + + return m_module.opLoad(floatType, texturePostOffsetPtr); + } + return m_module.constf32(1.0); + }; + + return postprocessTextureReadForTerrainBaking(m_module, textureValue, texcoordVar.id, getVectorTypeId(texcoordVar.type), loadTexturePreOffsetFnc, loadTextureScaleFnc, loadTexturePostOffsetFnc, loadAlbedoOpacityFnc, storeVec4ValueToRegisterFnc, loadVec4ValueFromRegisterFnc); }; result.id = postprocessColorOutputTextureRead(result.id); diff --git a/src/dxvk/rtx_render/rtx_debug_view.cpp b/src/dxvk/rtx_render/rtx_debug_view.cpp index 955030f11..70a8e9a90 100644 --- a/src/dxvk/rtx_render/rtx_debug_view.cpp +++ b/src/dxvk/rtx_render/rtx_debug_view.cpp @@ -260,7 +260,7 @@ namespace dxvk { {DEBUG_VIEW_POM_DIRECT_HIT_POS, "POM Direct Hit Position (Tangent Space)"}, {DEBUG_VIEW_HEIGHT_MAP, "Height Map Value", "Valid values will be greyscale." - "\nBlue, Green, or Red pixels indicate errors happened" + "\nColored pixels indicate errors happened" "\nwhen passing POM state between passes."}, {DEBUG_VIEW_RAYTRACED_RENDER_TARGET_GEOMETRY, "Raytraced Render Target Geometry" }, {DEBUG_VIEW_RAYTRACED_RENDER_TARGET_DIRECT, "Hit Raytraced Render Target in Direct"}, diff --git a/src/dxvk/rtx_render/rtx_geometry_utils.cpp b/src/dxvk/rtx_render/rtx_geometry_utils.cpp index 99ad01bfd..85dbdbf8f 100644 --- a/src/dxvk/rtx_render/rtx_geometry_utils.cpp +++ b/src/dxvk/rtx_render/rtx_geometry_utils.cpp @@ -617,6 +617,7 @@ namespace dxvk { args.rcpResolution = float2(1.f / extent.width, 1.f / extent.height); args.normalIntensity = OpaqueMaterialOptions::normalIntensity(); args.scale = conversionInfo.scale; + args.offset = conversionInfo.offset; ctx->pushConstants(0, sizeof(args), &args); diff --git a/src/dxvk/rtx_render/rtx_geometry_utils.h b/src/dxvk/rtx_render/rtx_geometry_utils.h index 83f9542d4..b3977c6a0 100644 --- a/src/dxvk/rtx_render/rtx_geometry_utils.h +++ b/src/dxvk/rtx_render/rtx_geometry_utils.h @@ -133,6 +133,7 @@ namespace dxvk { Rc sourceView = nullptr; TextureRef targetTexture; float scale = 1.f; + float offset = 0.f; }; void decodeAndAddOpacity( diff --git a/src/dxvk/rtx_render/rtx_instance_manager.cpp b/src/dxvk/rtx_render/rtx_instance_manager.cpp index 63f27ad6c..7868b3dd8 100644 --- a/src/dxvk/rtx_render/rtx_instance_manager.cpp +++ b/src/dxvk/rtx_render/rtx_instance_manager.cpp @@ -148,7 +148,7 @@ namespace dxvk { namespace { template struct CheckRtInstanceSize { // The second line of the build error should contain the new size of RtInstance in the template argument, i.e. `dxvk::CheckRtInstanceSize` - static_assert(RtInstanceSize == 720, "RtInstance size has changed. Fix the copy constructor above this message, then update the expected size."); + static_assert(RtInstanceSize == 712, "RtInstance size has changed. Fix the copy constructor above this message, then update the expected size."); }; CheckRtInstanceSize _rtInstanceSizeTest; } @@ -920,7 +920,6 @@ namespace dxvk { currentInstance.surface.colorBlendOp = drawCall.getMaterialData().colorBlendOp; uint8_t spriteSheetRows = 0, spriteSheetCols = 0, spriteSheetFPS = 0; - float displaceIn = 0.f; // Note: Extract spritesheet information from the associated material data as it ends up stored in the Surface // not in the Surface Material like most material information. @@ -930,7 +929,6 @@ namespace dxvk { spriteSheetCols = materialData.getOpaqueMaterialData().getSpriteSheetCols(); spriteSheetFPS = materialData.getOpaqueMaterialData().getSpriteSheetFPS(); - displaceIn = materialData.getOpaqueMaterialData().getDisplaceIn(); break; case MaterialDataType::Translucent: spriteSheetRows = materialData.getTranslucentMaterialData().getSpriteSheetRows(); @@ -949,7 +947,6 @@ namespace dxvk { currentInstance.surface.spriteSheetRows = spriteSheetRows; currentInstance.surface.spriteSheetCols = spriteSheetCols; currentInstance.surface.spriteSheetFPS = spriteSheetFPS; - currentInstance.surface.displaceIn = displaceIn; currentInstance.surface.objectPickingValue = drawCall.drawCallID; // For worldspace UI, we want to show the UI (unlit) in the world. So configure the blend mode if blending is used accordingly. diff --git a/src/dxvk/rtx_render/rtx_material_data.h b/src/dxvk/rtx_render/rtx_material_data.h index 760a6c4d6..4916ed038 100644 --- a/src/dxvk/rtx_render/rtx_material_data.h +++ b/src/dxvk/rtx_render/rtx_material_data.h @@ -74,6 +74,7 @@ X(AlphaTestReferenceValue, alpha_test_reference_value, uint8_t, 0, 255, 0) \ /* Note: Maximum clamped to float 16 max due to GPU encoding requirements. */ \ X(DisplaceIn, displace_in, float, 0.f, 65504.0f, 0.05f) \ + X(DisplaceOut, displace_out, float, 0.f, 65504.0f, 0.0f) \ X(SubsurfaceTransmittanceColor, subsurface_transmittance_color, Vector3, Vector3(0.f), Vector3(1.f), Vector3(0.5f, 0.5f, 0.5f)) \ X(SubsurfaceMeasurementDistance, subsurface_measurement_distance, float, 0.f, 65504.0f, 0.f) \ X(SubsurfaceSingleScatteringAlbedo, subsurface_single_scattering_albedo, Vector3, Vector3(0.f), Vector3(1.f), Vector3(0.5f, 0.5f, 0.5f)) \ diff --git a/src/dxvk/rtx_render/rtx_materials.h b/src/dxvk/rtx_render/rtx_materials.h index 509e6db46..c68b439ad 100644 --- a/src/dxvk/rtx_render/rtx_materials.h +++ b/src/dxvk/rtx_render/rtx_materials.h @@ -25,8 +25,9 @@ #include "rtx_option.h" #include "../../util/util_color.h" #include "../../util/util_macro.h" -#include "../shaders/rtx/utility/shared_constants.h" -#include "../shaders/rtx/concept/surface/surface_shared.h" +#include "rtx/utility/shared_constants.h" +#include "rtx/concept/surface/surface_shared.h" +#include "rtx/pass/common_binding_indices.h" #include "../../dxso/dxso_util.h" #include "rtx_material_data.h" #include "../../lssusd/mdl_helpers.h" @@ -96,9 +97,8 @@ struct RtSurface { writeGPUHelperExplicit<2>(data, offset, indexBufferIndex); writeGPUHelperExplicit<2>(data, offset, color0BufferIndex); - float displaceInCombined = displaceIn * getDisplacementFactor(); - assert(displaceInCombined <= FLOAT16_MAX); - writeGPUHelper(data, offset, glm::packHalf1x16(displaceInCombined)); + // 2 unused bytes here. + writeGPUPadding<2>(data, offset); const uint16_t packedHash = (uint16_t) (associatedGeometryHash >> 48) ^ @@ -320,8 +320,6 @@ struct RtSurface { XXH64_hash_t associatedGeometryHash; // NOTE: This is used for the debug view uint32_t objectPickingValue = 0; // NOTE: a value to fill GBUFFER_BINDING_PRIMARY_OBJECT_PICKING_OUTPUT uint32_t decalSortOrder = 0; // see: InstanceManager::m_decalSortOrderCounter - - float displaceIn = 0.f; }; // Shared Material Defaults/Limits @@ -381,7 +379,7 @@ struct RtOpaqueSurfaceMaterial { float roughnessConstant, float metallicConstant, const Vector3& emissiveColorConstant, bool enableEmission, bool ignoreAlphaChannel, bool enableThinFilm, bool alphaIsThinFilmThickness, float thinFilmThicknessConstant, - uint32_t samplerIndex, float displaceIn, + uint32_t samplerIndex, float displaceIn, float displaceOut, uint32_t subsurfaceMaterialIndex, bool isRaytracedRenderTarget) : m_albedoOpacityTextureIndex{ albedoOpacityTextureIndex }, m_normalTextureIndex{ normalTextureIndex }, m_tangentTextureIndex { tangentTextureIndex }, m_heightTextureIndex { heightTextureIndex }, m_roughnessTextureIndex{ roughnessTextureIndex }, @@ -392,7 +390,7 @@ struct RtOpaqueSurfaceMaterial { m_emissiveColorConstant{ emissiveColorConstant }, m_enableEmission{ enableEmission }, m_ignoreAlphaChannel { ignoreAlphaChannel }, m_enableThinFilm { enableThinFilm }, m_alphaIsThinFilmThickness { alphaIsThinFilmThickness }, m_thinFilmThicknessConstant { thinFilmThicknessConstant }, m_samplerIndex{ samplerIndex }, m_displaceIn{ displaceIn }, - m_subsurfaceMaterialIndex(subsurfaceMaterialIndex), m_isRaytracedRenderTarget(isRaytracedRenderTarget) { + m_displaceOut{ displaceOut }, m_subsurfaceMaterialIndex(subsurfaceMaterialIndex), m_isRaytracedRenderTarget(isRaytracedRenderTarget) { updateCachedData(); updateCachedHash(); } @@ -424,13 +422,25 @@ struct RtOpaqueSurfaceMaterial { flags |= OPAQUE_SURFACE_MATERIAL_FLAG_IS_RAYTRACED_RENDER_TARGET; } - // data[0 - 3] - writeGPUHelper(data, offset, flags); float displaceIn = m_displaceIn * getDisplacementFactor(); + float displaceOut = m_displaceOut * getDisplacementFactor(); + uint32_t heightTextureIndex = m_heightTextureIndex; + if (hasValidDisplacement()) { + flags |= OPAQUE_SURFACE_MATERIAL_FLAG_HAS_DISPLACEMENT; + } else { + // If any POM attribute would disable POM, just disable all POM attributes. + displaceIn = 0.f; + displaceOut = 0.f; + heightTextureIndex = BINDING_INDEX_INVALID; + } assert(displaceIn <= FLOAT16_MAX); - writeGPUHelper(data, offset, glm::packHalf1x16(displaceIn)); + assert(displaceOut <= FLOAT16_MAX); + + // data[0 - 3] + writeGPUHelper(data, offset, flags); writeGPUHelperExplicit<2>(data, offset, m_samplerIndex); writeGPUHelperExplicit<2>(data, offset, m_albedoOpacityTextureIndex); + writeGPUHelperExplicit<2>(data, offset, m_subsurfaceMaterialIndex); // data[4 - 7] writeGPUHelper(data, offset, glm::packHalf1x16(m_albedoOpacityConstant.x)); @@ -439,31 +449,32 @@ struct RtOpaqueSurfaceMaterial { writeGPUHelper(data, offset, glm::packHalf1x16(m_albedoOpacityConstant.w)); // data[8 - 11] + writeGPUHelper(data, offset, glm::packHalf1x16(displaceIn)); + writeGPUHelper(data, offset, glm::packHalf1x16(displaceOut)); writeGPUHelperExplicit<2>(data, offset, m_heightTextureIndex); - writeGPUHelperExplicit<2>(data, offset, m_subsurfaceMaterialIndex); writeGPUHelper(data, offset, glm::packHalf1x16(m_cachedThinFilmNormalizedThicknessConstant)); - writeGPUHelperExplicit<2>(data, offset, m_emissiveColorTextureIndex); // data[12 - 15] + writeGPUHelperExplicit<2>(data, offset, m_emissiveColorTextureIndex); writeGPUHelperExplicit<2>(data, offset, m_roughnessTextureIndex); writeGPUHelperExplicit<2>(data, offset, m_metallicTextureIndex); writeGPUHelperExplicit<2>(data, offset, m_normalTextureIndex); - assert(m_cachedEmissiveIntensity <= FLOAT16_MAX); - writeGPUHelper(data, offset, glm::packHalf1x16(m_cachedEmissiveIntensity)); // data[16 - 19] writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorConstant.x)); writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorConstant.y)); writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorConstant.z)); - writeGPUHelper(data, offset, glm::packHalf1x16(m_roughnessConstant)); + assert(m_cachedEmissiveIntensity <= FLOAT16_MAX); + writeGPUHelper(data, offset, glm::packHalf1x16(m_cachedEmissiveIntensity)); - // data[20 - 22] + // data[20 - 23] + writeGPUHelper(data, offset, glm::packHalf1x16(m_roughnessConstant)); writeGPUHelper(data, offset, glm::packHalf1x16(m_metallicConstant)); writeGPUHelper(data, offset, glm::packHalf1x16(m_anisotropy)); writeGPUHelperExplicit<2>(data, offset, m_tangentTextureIndex); - // data[23 - 31] - writeGPUPadding<18>(data, offset); + // data[24 - 31] + writeGPUPadding<16>(data, offset); assert(offset - oldOffset == kSurfaceMaterialGPUSize); } @@ -479,6 +490,10 @@ struct RtOpaqueSurfaceMaterial { return !hasTexture || m_samplerIndex != kSurfaceMaterialInvalidTextureIndex; } + bool hasValidDisplacement() const { + return (m_displaceIn > 0.f || m_displaceOut > 0.f) && m_heightTextureIndex != BINDING_INDEX_INVALID; + } + bool operator==(const RtOpaqueSurfaceMaterial& r) const { return m_cachedHash == r.m_cachedHash; } @@ -579,6 +594,7 @@ struct RtOpaqueSurfaceMaterial { h = XXH64(&m_thinFilmThicknessConstant, sizeof(m_thinFilmThicknessConstant), h); h = XXH64(&m_samplerIndex, sizeof(m_samplerIndex), h); h = XXH64(&m_displaceIn, sizeof(m_displaceIn), h); + h = XXH64(&m_displaceOut, sizeof(m_displaceOut), h); h = XXH64(&m_subsurfaceMaterialIndex, sizeof(m_subsurfaceMaterialIndex), h); h = XXH64(&m_isRaytracedRenderTarget, sizeof(m_isRaytracedRenderTarget), h); @@ -621,9 +637,9 @@ struct RtOpaqueSurfaceMaterial { float m_thinFilmThicknessConstant; // How far inwards a height_texture value of 0 maps to. - // TODO: if we ever support a displacement algorithm that supports outwards displacements, we'll need - // to add a displaceOut parameter. With POM, displaceOut is locked to 0. float m_displaceIn; + // How far outwards a height_texture value of 1 maps to. + float m_displaceOut; uint32_t m_subsurfaceMaterialIndex; diff --git a/src/dxvk/rtx_render/rtx_remix_api.cpp b/src/dxvk/rtx_render/rtx_remix_api.cpp index 887320e27..0749cccab 100644 --- a/src/dxvk/rtx_render/rtx_remix_api.cpp +++ b/src/dxvk/rtx_render/rtx_remix_api.cpp @@ -70,6 +70,7 @@ namespace dxvk { } namespace { + uint64_t s_apiVersion{ 0 }; IDirect3D9Ex* s_dxvkD3D9 { nullptr }; dxvk::D3D9DeviceEx* s_dxvkDevice { nullptr }; dxvk::mutex s_mutex {}; @@ -261,6 +262,7 @@ namespace { src.getAlphaTestType(), src.getAlphaTestReferenceValue(), src.getDisplaceIn(), + src.getDisplaceOut(), src.getSubsurfaceTransmittanceColor(), src.getSubsurfaceMeasurementDistance(), src.getSubsurfaceSingleScatteringAlbedo(), @@ -351,7 +353,8 @@ namespace { tobool(extOpaque->invertedBlend), static_cast(extOpaque->alphaTestType), extOpaque->alphaReferenceValue, - extOpaque->heightTextureStrength, // displaceIn + extOpaque->displaceIn, + (s_apiVersion >= REMIXAPI_VERSION_MAKE(0, 4, 2)) ? extOpaque->displaceOut : 0.f, extSubsurface ? tovec3(extSubsurface->subsurfaceTransmittanceColor) : Vector3{ 0.5f, 0.5f, 0.5f }, extSubsurface ? extSubsurface->subsurfaceMeasurementDistance : 0.f, extSubsurface ? tovec3(extSubsurface->subsurfaceSingleScatteringAlbedo) : Vector3{ 0.5f, 0.5f, 0.5f }, @@ -1455,6 +1458,7 @@ extern "C" if (!isVersionCompatible(info->version)) { return REMIXAPI_ERROR_CODE_INCOMPATIBLE_VERSION; } + s_apiVersion = info->version; auto interf = remixapi_Interface {}; { diff --git a/src/dxvk/rtx_render/rtx_scene_manager.cpp b/src/dxvk/rtx_render/rtx_scene_manager.cpp index 6a0efb1d2..6c9f8950f 100644 --- a/src/dxvk/rtx_render/rtx_scene_manager.cpp +++ b/src/dxvk/rtx_render/rtx_scene_manager.cpp @@ -579,7 +579,7 @@ namespace dxvk { input.getGeometryData().indexBuffer.defined() && input.getGeometryData().vertexCount > input.getGeometryData().indexCount; if (highlightUnsafeAnchor) { static MaterialData sHighlightMaterialData(OpaqueMaterialData(TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), - 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.0f, 0.1f, 0.1f, Vector3(0.46f, 0.26f, 0.31f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, + 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.0f, 0.1f, 0.1f, Vector3(0.46f, 0.26f, 0.31f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, lss::Mdl::Filter::Nearest, lss::Mdl::WrapMode::Repeat, lss::Mdl::WrapMode::Repeat)); overrideMaterialData = &sHighlightMaterialData; } @@ -682,7 +682,7 @@ namespace dxvk { } if (highlightUnsafeReplacement) { static MaterialData sHighlightMaterialData(OpaqueMaterialData(TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), - 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.f, 0.1f, 0.1f, Vector3(1.f, 0.f, 0.f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, + 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.f, 0.1f, 0.1f, Vector3(1.f, 0.f, 0.f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, lss::Mdl::Filter::Nearest, lss::Mdl::WrapMode::Repeat, lss::Mdl::WrapMode::Repeat)); if (getGameTimeSinceStartMS() / 200 % 2 == 0) { overrideMaterialData = &sHighlightMaterialData; @@ -923,7 +923,8 @@ namespace dxvk { bool thinFilmEnable = false; bool alphaIsThinFilmThickness = false; float thinFilmThicknessConstant = 0.0f; - float displaceIn = 1.0f; + float displaceIn = 0.0f; + float displaceOut = 0.0f; bool isUsingRaytracedRenderTarget = drawCallState.isUsingRaytracedRenderTarget; // Ignore colormap alpha of legacy texture if tagged as 'ignoreAlphaOnTextures' @@ -1012,10 +1013,7 @@ namespace dxvk { alphaIsThinFilmThickness = opaqueMaterialData.getAlphaIsThinFilmThickness(); thinFilmThicknessConstant = opaqueMaterialData.getThinFilmThicknessConstant(); displaceIn = opaqueMaterialData.getDisplaceIn(); - - if (heightTextureIndex != kSurfaceMaterialInvalidTextureIndex && displaceIn > 0.0f) { - ++m_activePOMCount; - } + displaceOut = opaqueMaterialData.getDisplaceOut(); subsurfaceMeasurementDistance = opaqueMaterialData.getSubsurfaceMeasurementDistance() * RtxOptions::SubsurfaceScattering::surfaceThicknessScale(); @@ -1050,10 +1048,14 @@ namespace dxvk { roughnessConstant, metallicConstant, emissiveColorConstant, enableEmissive, ignoreAlphaChannel, thinFilmEnable, alphaIsThinFilmThickness, - thinFilmThicknessConstant, samplerIndex, displaceIn, + thinFilmThicknessConstant, samplerIndex, displaceIn, displaceOut, subsurfaceMaterialIndex, isUsingRaytracedRenderTarget }; + if (opaqueSurfaceMaterial.hasValidDisplacement()) { + ++m_activePOMCount; + } + surfaceMaterial.emplace(opaqueSurfaceMaterial); } else if (renderMaterialDataType == MaterialDataType::Translucent) { const auto& translucentMaterialData = renderMaterialData.getTranslucentMaterialData(); diff --git a/src/dxvk/rtx_render/rtx_terrain_baker.cpp b/src/dxvk/rtx_render/rtx_terrain_baker.cpp index b89d7e980..6d3399d28 100644 --- a/src/dxvk/rtx_render/rtx_terrain_baker.cpp +++ b/src/dxvk/rtx_render/rtx_terrain_baker.cpp @@ -52,19 +52,6 @@ namespace dxvk { } } - VkClearColorValue getClearColor(ReplacementMaterialTextureType::Enum textureType) { - switch (textureType) { - case ReplacementMaterialTextureType::Height: - // height maps should be cleared to 1, which keeps the displaced surface identical to the original surface. - return { 1.f, 1.f, 1.f, 1.f }; - break; - - default: - return { 0.0f, 0.0f, 0.0f, 0.0f }; - break; - } - } - VkFormat getTextureFormat(ReplacementMaterialTextureType::Enum textureType) { switch (textureType) { case ReplacementMaterialTextureType::Normal: @@ -91,6 +78,20 @@ namespace dxvk { } } + VkClearColorValue TerrainBaker::getClearColor(ReplacementMaterialTextureType::Enum textureType) { + float neutral_height = m_prevFrameMaxDisplaceIn / (m_prevFrameMaxDisplaceIn + m_prevFrameMaxDisplaceOut); + switch (textureType) { + case ReplacementMaterialTextureType::Height: + // height maps should be cleared to neutral_height, which keeps the displaced surface identical to the original surface. + return { neutral_height, neutral_height, neutral_height, neutral_height }; + break; + + default: + return { 0.0f, 0.0f, 0.0f, 0.0f }; + break; + } + } + bool TerrainBaker::isPSReplacementSupportEnabled(const DrawCallState& drawCallState) { if (drawCallState.usesPixelShader) { return Material::replacementSupportInPS() && @@ -182,9 +183,14 @@ namespace dxvk { conversionInfo.sourceTexture = &texture; if (textureType == ReplacementMaterialTextureType::Height) { - // Normalize the displaceIn to the previous frame's max displaceIn. - conversionInfo.scale = m_prevFrameMaxDisplaceIn <= 0.f ? 0.f : replacementMaterial->getDisplaceIn() / m_prevFrameMaxDisplaceIn; + // Normalize the displaceIn and displaceOut to the previous frame's displacement range. + const float prevFrameTotalHeight = m_prevFrameMaxDisplaceIn + m_prevFrameMaxDisplaceOut; + const float materialTotalHeight = replacementMaterial->getDisplaceIn() + replacementMaterial->getDisplaceOut(); + + conversionInfo.scale = prevFrameTotalHeight <= 0.f ? 0.f : materialTotalHeight / prevFrameTotalHeight; + conversionInfo.offset = prevFrameTotalHeight <= 0.f ? 0.f : -1.f * replacementMaterial->getDisplaceIn() / materialTotalHeight; m_currFrameMaxDisplaceIn = std::max(m_currFrameMaxDisplaceIn, replacementMaterial->getDisplaceIn()); + m_currFrameMaxDisplaceOut = std::max(m_currFrameMaxDisplaceOut, replacementMaterial->getDisplaceOut()); } if (isPSReplacementSupportEnabled(drawCallState)) { @@ -303,15 +309,24 @@ namespace dxvk { return isBaked; } - if (m_calculatingDisplaceInFactor && replacementMaterial->getDisplaceIn() > 0.f) { + if (m_calculatingDisplaceInFactor && replacementMaterial != nullptr && (replacementMaterial->getDisplaceIn() > 0.f || replacementMaterial->getDisplaceOut() > 0.f)) { + const float maxUvTileSize = RtxGeometryUtils::computeMaxUVTileSize(drawCallState.getGeometryData(), drawCallState.getTransformData().objectToWorld); // This is the deepest any part of this mesh can go. - float maxInputDepth = RtxGeometryUtils::computeMaxUVTileSize(drawCallState.getGeometryData(), drawCallState.getTransformData().objectToWorld) * replacementMaterial->getDisplaceIn(); + const float maxInputDepth = maxUvTileSize * replacementMaterial->getDisplaceIn(); + // Ths is the highest any part of the mesh can go + const float maxInputHeight = maxUvTileSize * replacementMaterial->getDisplaceOut(); + + const float maxInputDisplacement = maxInputDepth + maxInputHeight; // The deepest the baked terrain can go. - float maxBakedDepth = 2 * RtxOptions::Get()->getMeterToWorldUnitScale() * cascadeMap.levelHalfWidth() * m_prevFrameMaxDisplaceIn; + const float maxBakedDepth = 2 * RtxOptions::Get()->getMeterToWorldUnitScale() * cascadeMap.levelHalfWidth() * m_prevFrameMaxDisplaceIn; + // The highest the baked terrain can go. + const float maxBakedHeight = 2 * RtxOptions::Get()->getMeterToWorldUnitScale() * cascadeMap.levelHalfWidth() * m_prevFrameMaxDisplaceOut; + + const float maxBakedDisplacement = maxBakedDepth + maxBakedHeight; // Optimal displaceInFactor for this mesh (multiply the pixel value, divide the baked drawcall's displaceIn) - float displaceInFactor = maxBakedDepth / maxInputDepth; + const float displaceInFactor = maxBakedDisplacement / maxInputDisplacement; // Need the largest value from any of the meshes, or else the bottom m_calculatedDisplaceInFactor = std::max(m_calculatedDisplaceInFactor, displaceInFactor); @@ -387,11 +402,15 @@ namespace dxvk { bool bakingResult = false; ctx->setSpecConstant(VK_PIPELINE_BIND_POINT_GRAPHICS, D3D9SpecConstantId::ReplacementTextureCategory, static_cast(ReplacementMaterialTextureCategory::AlbedoOpacity)); + + // The height value that corresponds to the original surface height. + const float neutralDisplacement = m_prevFrameMaxDisplaceIn / ( m_prevFrameMaxDisplaceIn + m_prevFrameMaxDisplaceOut); // Bake all material textures for (uint32_t iTexture = 0; iTexture < numTexturesToBake; iTexture++) { ReplacementMaterialTextureType::Enum textureType = ReplacementMaterialTextureType::AlbedoOpacity; + float texturePreOffset = 0.f; float textureScale = 1.f; // Bind a source replacement texture to bake, if available. @@ -400,6 +419,7 @@ namespace dxvk { TextureRef& replacementTexture = replacementTextures[iTexture].targetTexture; textureType = replacementTextures[iTexture].type; textureScale = replacementTextures[iTexture].scale; + texturePreOffset = replacementTextures[iTexture].offset; ctx->bindResourceView(colorTextureSlot, replacementTexture.getImageView(), nullptr); @@ -520,7 +540,12 @@ namespace dxvk { for (int i = 0; i < caps::TextureStageCount; ++i) { sharedState.Stages[i] = prevSharedState.Stages[i]; } + // The neutral height value of the input image and the output map don't match. + // To account, first subtract the input's neutral value from the pixel, + // then apply all scale operations, then add the output neutral value. + sharedState.Stages[kTerrainBakerSecondaryTextureStage].texturePreOffset = texturePreOffset; sharedState.Stages[kTerrainBakerSecondaryTextureStage].textureScale = textureScale * cascadeUvDensity; + sharedState.Stages[kTerrainBakerSecondaryTextureStage].texturePostOffset = neutralDisplacement; // Programmable VS path if (drawCallState.usesVertexShader) { @@ -637,8 +662,9 @@ namespace dxvk { false, // opaqueMaterialDefaults.InvertedBlend, AlphaTestType::kAlways, 0,//opaqueMaterialDefaults.AlphaReferenceValue; - // Using the previous frame's displaceIn because all current frame draw calls are normalized to the previous frame's max. + // Using the previous frame's displaceIn/Out because all current frame draw calls are normalized to the previous frame's max. m_prevFrameMaxDisplaceIn / Material::Properties::displaceInFactor(), // opaqueMaterialDefaults.DisplaceIn + m_prevFrameMaxDisplaceOut / Material::Properties::displaceInFactor(), // opaqueMaterialDefaults.DisplaceOut Vector3(), // opaqueMaterialDefaults.subsurfaceTransmittanceColor 0.0f, // opaqueMaterialDefaults.subsurfaceMeasurementDistance Vector3(), // opaqueMaterialDefaults.subsurfaceSingleScatteringAlbedo @@ -713,6 +739,11 @@ namespace dxvk { args.rcpCascadeMapSize.y = 1.f / args.cascadeMapSize.y; args.maxCascadeLevel = m_bakingParams.numCascades - 1; + if (m_materialData.has_value()) { + args.displaceIn = m_materialData->getOpaqueMaterialData().getDisplaceIn(); + } else { + args.displaceIn = 0.0f; + } args.lastCascadeScale = m_bakingParams.lastCascadeScale; return args; @@ -754,12 +785,12 @@ namespace dxvk { ImGui::DragFloat("Anisotropy", &Material::Properties::roughnessAnisotropyObject(), 0.01f, -1.0f, 1.f, "%.3f", sliderFlags); ImGui::Text("\nDisplacement Settings"); - ImGui::DragFloat("Displace In Factor", &Material::Properties::displaceInFactorObject(), 0.01f, 0.01f, 100.f, "%.3f", sliderFlags); - ImGui::Text("Calculate the lowest safe Displace In Factor for the current \n" + ImGui::DragFloat("Displacement Factor", &Material::Properties::displaceInFactorObject(), 0.01f, 0.01f, 100.f, "%.3f", sliderFlags); + ImGui::Text("Calculate the lowest safe Displacement Factor for the current \n" "scene. Mod creators should run this in scenes across the \n" - "game, and use the highest returned value. See Displace In \n" + "game, and use the highest returned value. See Displacement \n" "Factor's tooltip for more info."); - if (ImGui::Button("Calculate Scene's Optimal Displace In Factor")) { + if (ImGui::Button("Calculate Scene's Optimal Displacement Factor")) { m_calculateDisplaceInFactorNextFrame = true; } @@ -839,6 +870,9 @@ namespace dxvk { m_prevFrameMaxDisplaceIn = m_currFrameMaxDisplaceIn; m_currFrameMaxDisplaceIn = 0.f; + m_prevFrameMaxDisplaceOut = m_currFrameMaxDisplaceOut; + m_currFrameMaxDisplaceOut = 0.f; + m_stagingTextureCache.clear(); // Destroy material data every frame so as not keep texture references around. diff --git a/src/dxvk/rtx_render/rtx_terrain_baker.h b/src/dxvk/rtx_render/rtx_terrain_baker.h index 150d538ce..b3826570e 100644 --- a/src/dxvk/rtx_render/rtx_terrain_baker.h +++ b/src/dxvk/rtx_render/rtx_terrain_baker.h @@ -106,10 +106,11 @@ namespace dxvk { RTX_OPTION("rtx.terrainBaker.material.properties", Vector3, emissiveColorConstant, Vector3(0.0f, 0.0f, 0.0f), "Emissive color constant. Should be a color in sRGB colorspace with gamma encoding."); RTX_OPTION("rtx.terrainBaker.material.properties", bool, enableEmission, false, "A flag to determine if emission is enabled."); RTX_OPTION("rtx.terrainBaker.material.properties", float, displaceInFactor, 1.f, - "The max depth the baked terrain can support will be larger than the max depth \n" + "The max depth and height the baked terrain can support will be larger than the max \n" "of any incoming draw call, which results in a loss of detail. When this is \n" "too low, the displacement will lack detail. When it is too high, the lowest \n" - "parts of the POM will flatten out."); + "and highest parts of the POM will flatten out. This affects both displaceIn \n" + "and displaceOut, despite the name."); }; }; @@ -158,6 +159,7 @@ namespace dxvk { const RtxMipmap::Resource& getTerrainTexture(Rc ctx, RtxTextureManager& textureManager, ReplacementMaterialTextureType::Enum textureType, uint32_t width, uint32_t height); void clearMaterialTexture(Rc ctx, ReplacementMaterialTextureType::Enum textureType); static bool isPSReplacementSupportEnabled(const DrawCallState& drawCallState); + VkClearColorValue getClearColor(ReplacementMaterialTextureType::Enum textureType); BakingParameters m_bakingParams; @@ -208,6 +210,9 @@ namespace dxvk { float m_currFrameMaxDisplaceIn = 0.f; float m_prevFrameMaxDisplaceIn = 0.f; + float m_currFrameMaxDisplaceOut = 0.f; + float m_prevFrameMaxDisplaceOut = 0.f; + // Set to true when m_materialData needs to be updated to reflect latest changes. bool m_needsMaterialDataUpdate = false; diff --git a/src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh b/src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh index ee560ecb0..01c14362e 100644 --- a/src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh +++ b/src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh @@ -958,7 +958,7 @@ void geometryResolverVertexOutputDebugView( OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial); float3 hitPos = float3(0.f); uint iterations = 0; - if (opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID && opaqueSurfaceMaterial.displaceIn > 0.0f) + if (opaqueSurfaceMaterial.hasValidDisplacement()) { SurfaceInteraction tempSurfaceInteraction = surfaceInteractionCreate(surface, rayInteraction, ray); const f16mat3 worldToTangent = f16mat3(tempSurfaceInteraction.interpolatedTangent, tempSurfaceInteraction.interpolatedBitangent, tempSurfaceInteraction.interpolatedNormal); @@ -977,7 +977,7 @@ void geometryResolverVertexOutputDebugView( const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[uint(rayInteraction.surfaceIndex)]; OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial); float3 hitPos = float3(0.f); - if (opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID && opaqueSurfaceMaterial.displaceIn > 0.0f) + if (opaqueSurfaceMaterial.hasValidDisplacement()) { SurfaceInteraction tempSurfaceInteraction = surfaceInteractionCreate(surface, rayInteraction, ray); const f16mat3 worldToTangent = f16mat3(tempSurfaceInteraction.interpolatedTangent, tempSurfaceInteraction.interpolatedBitangent, tempSurfaceInteraction.interpolatedNormal); @@ -996,7 +996,7 @@ void geometryResolverVertexOutputDebugView( { const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[uint(rayInteraction.surfaceIndex)]; OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial); - if (opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID) { + if (opaqueSurfaceMaterial.hasValidDisplacement()) { heightMapValue = 1.f - pomSampleHeight(opaqueSurfaceMaterial.heightTextureIndex, opaqueSurfaceMaterial.samplerIndex, surfaceInteraction.textureCoordinates); } } diff --git a/src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh b/src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh index 874c8fcd1..539c727d7 100644 --- a/src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh +++ b/src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh @@ -1005,41 +1005,37 @@ void integrateIndirectPath( if (opaqueSurfaceMaterial.heightTextureIndex == BINDING_INDEX_INVALID) { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.5f, 0.f, 0.f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(1.f, 1.f, 0.f)); } } else { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 0.5f, 0.f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(1.f, 0.5f, 0.f)); } } else { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 0.f, 0.5f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(1.f, 0.f, 0.f)); } } if (primarySurfaceIndex != BINDING_INDEX_INVALID) { const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[primarySurfaceIndex]; - if (memoryPolymorphicSurfaceMaterial.isOpaque()) + if (memoryPolymorphicSurfaceMaterial.hasValidDisplacement()) { OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial); - if (opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID) - { - MinimalSurfaceInteraction minimalSurfaceInteraction = minimalSurfaceInteractionReadFromGBuffer( - pixelCoordinate, indirectPathTextures.PrimaryWorldPositionWorldTriangleNormal); - - const float pomThroughput = opaqueSurfaceMaterialInteractionCalcHeightThroughput( - minimalSurfaceInteraction, pathState.direction, opaqueSurfaceMaterial.heightTextureIndex, - opaqueSurfaceMaterial.samplerIndex, SharedTextureCoord[pixelCoordinate], opaqueSurfaceMaterial.displaceIn - ); + MinimalSurfaceInteraction minimalSurfaceInteraction = minimalSurfaceInteractionReadFromGBuffer( + pixelCoordinate, indirectPathTextures.PrimaryWorldPositionWorldTriangleNormal); - accumulateThroughput(pathState, pomThroughput); - pathState.continuePath = any(pathState.throughput > 0.001h); - pathState.continueResolving = pathState.continuePath; - } + const float pomThroughput = opaqueSurfaceMaterialInteractionCalcHeightThroughput( + minimalSurfaceInteraction, pathState.direction, opaqueSurfaceMaterial, SharedTextureCoord[pixelCoordinate] + ); + + accumulateThroughput(pathState, pomThroughput); + pathState.continuePath = any(pathState.throughput > 0.001h); + pathState.continueResolving = pathState.continuePath; } } } diff --git a/src/dxvk/shaders/rtx/algorithm/resolve.slangh b/src/dxvk/shaders/rtx/algorithm/resolve.slangh index 301dfe853..e13efeba5 100644 --- a/src/dxvk/shaders/rtx/algorithm/resolve.slangh +++ b/src/dxvk/shaders/rtx/algorithm/resolve.slangh @@ -444,7 +444,7 @@ void resolveVertex( if (surface.isFullyOpaque) { // Cache if this surface is a POM material for later use - pomOpaqueSurfaceEncountered = opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID && opaqueSurfaceMaterial.displaceIn > 0.h; + pomOpaqueSurfaceEncountered = opaqueSurfaceMaterial.hasValidDisplacement(); resolveVertexFinalHit(resolveVertexState); @@ -468,7 +468,7 @@ void resolveVertex( if (opacity >= cb.resolveOpaquenessThreshold) { // Cache if this surface is a POM material for later use - pomOpaqueSurfaceEncountered = opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID && opaqueSurfaceMaterial.displaceIn > 0.h; + pomOpaqueSurfaceEncountered = opaqueSurfaceMaterial.hasValidDisplacement(); resolveVertexFinalHit(resolveVertexState); return; diff --git a/src/dxvk/shaders/rtx/algorithm/visibility.slangh b/src/dxvk/shaders/rtx/algorithm/visibility.slangh index 2fee92665..a258121db 100644 --- a/src/dxvk/shaders/rtx/algorithm/visibility.slangh +++ b/src/dxvk/shaders/rtx/algorithm/visibility.slangh @@ -67,7 +67,6 @@ f16vec3 handleVisibilityVertex(Ray ray, RayHitInfo ra // initialize these values and help the compiler generate more optimal code paths. const bool requiresNormalsAndTangents = VisibilityMode & (visibilityModeEnableSubsurfaceMaterials|visibilityModeEnableTranslucentMaterials); { - surface.displaceIn = 0.f; surface.decalSortOrder = 0; surface.objectPickingValue = 0; surface.hashPacked = 0; @@ -458,51 +457,51 @@ VisibilityResult traceVisibilityRay( { if (primarySurfaceIndex != BINDING_INDEX_INVALID) { - const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[primarySurfaceIndex]; - if (memoryPolymorphicSurfaceMaterial.isOpaque()) - { - OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial, true); - - if (opaqueSurfaceMaterial.heightTextureIndex == BINDING_INDEX_INVALID) + const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[primarySurfaceIndex]; + if (memoryPolymorphicSurfaceMaterial.isOpaque()) + { + OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial, true); + + if (!opaqueSurfaceMaterial.hasValidDisplacement()) { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.5f, 0.f, 0.f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(0.0f, 1.f, 1.f)); + } + else if (opaqueSurfaceMaterial.heightTextureIndex == BINDING_INDEX_INVALID) + { + storeInDebugView(getDispatchRaysIndex().xy, float3(0.0f, 1.f, 0.66f)); } } else { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 0.5f, 0.f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 1.f, 0.33f)); } } else { - storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 0.f, 0.5f)); + storeInDebugView(getDispatchRaysIndex().xy, float3(0.f, 1.f, 0.f)); } } if (primarySurfaceIndex != BINDING_INDEX_INVALID) { const MemoryPolymorphicSurfaceMaterial memoryPolymorphicSurfaceMaterial = surfaceMaterials[primarySurfaceIndex]; - if (memoryPolymorphicSurfaceMaterial.isOpaque()) + if (memoryPolymorphicSurfaceMaterial.hasValidDisplacement()) { OpaqueSurfaceMaterial opaqueSurfaceMaterial = opaqueSurfaceMaterialCreate(memoryPolymorphicSurfaceMaterial, true); - if (opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID) + pomAttenuation = opaqueSurfaceMaterialInteractionCalcHeightThroughput( + minimalSurfaceInteraction, visibilityRay.direction, opaqueSurfaceMaterial, texCoords + ); + + if (pomAttenuation <= 0.001f) { - pomAttenuation = opaqueSurfaceMaterialInteractionCalcHeightThroughput( - minimalSurfaceInteraction, visibilityRay.direction, opaqueSurfaceMaterial.heightTextureIndex, - opaqueSurfaceMaterial.samplerIndex, texCoords, opaqueSurfaceMaterial.displaceIn - ); - - if (pomAttenuation <= 0.001f) - { - VisibilityResult result; - result.attenuation = pomAttenuation; - result.hasOpaqueHit = true; - result.rayDirection = visibilityRay.direction; - result.hitDistance = visibilityRay.tMax; // TODO should refactor POM code to return? - result.hasPOMHit = true; - return result; - } + VisibilityResult result; + result.attenuation = pomAttenuation; + result.hasOpaqueHit = true; + result.rayDirection = visibilityRay.direction; + result.hitDistance = visibilityRay.tMax; // TODO should refactor POM code to return? + result.hasPOMHit = true; + return result; } } } diff --git a/src/dxvk/shaders/rtx/concept/surface/surface.h b/src/dxvk/shaders/rtx/concept/surface/surface.h index 89d0514f1..ec1729641 100644 --- a/src/dxvk/shaders/rtx/concept/surface/surface.h +++ b/src/dxvk/shaders/rtx/concept/surface/surface.h @@ -448,12 +448,6 @@ struct Surface get { return data2.x; } set { data2.x = newValue; } } - - property float16_t displaceIn - { - get { return uint16BitsToHalf(data0b.z); } - set { data0b.z = float16BitsToUint16(newValue); } - } }; // Note: Minimal version of typical Surface Interaction for transmission across passes. diff --git a/src/dxvk/shaders/rtx/concept/surface/surface_interaction.slangh b/src/dxvk/shaders/rtx/concept/surface/surface_interaction.slangh index 72458d116..0bebcbb36 100644 --- a/src/dxvk/shaders/rtx/concept/surface/surface_interaction.slangh +++ b/src/dxvk/shaders/rtx/concept/surface/surface_interaction.slangh @@ -479,7 +479,7 @@ SurfaceInteraction surfaceInteractionCreate( const f16mat3 worldToTangent = f16mat3(surfaceInteraction.interpolatedTangent, surfaceInteraction.interpolatedBitangent, surfaceInteraction.interpolatedNormal); const f16vec3 viewDirTangentSpace = normalize(mul(worldToTangent, rayInteraction.viewDirection)); // viewDirection is hitPos to camera, so invert it before converting to a texcoord offset. - const vec2 maxPossiblePomOffset = viewDirTangentSpace.xy * (-1.f * surface.displaceIn / viewDirTangentSpace.z); + const vec2 maxPossiblePomOffset = viewDirTangentSpace.xy * (-1.f * cb.terrainArgs.displaceIn / viewDirTangentSpace.z); const vec2 textureCenterOffset = texcoordToOffsetFromTextureCenter(surfaceInteraction.textureCoordinates + maxPossiblePomOffset); const float maxTextureCenterOffset = length(textureCenterOffset); diff --git a/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh b/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh index f237a999d..b6e066b19 100644 --- a/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh +++ b/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh @@ -87,7 +87,7 @@ float4 pomGetPatchCorners(Texture2D texture, SamplerState sampler, float2 boxCen } // cast a ray in tangent space. return an intersection point in tangent point (or an exit point if z > 1.0f) -float3 pomTraceRay(Texture2D texture, SamplerState sampler, float3 origin, float3 direction, float2 textureGradientX, float2 textureGradientY, inout uint iterations) +float3 pomTraceRay(Texture2D texture, SamplerState sampler, float3 origin, float3 direction, float2 textureGradientX, float2 textureGradientY, inout uint iterations, float neutralHeight) { // Potential future improvements for outwards rays: // 1) calculate starting height the same way the box intersection code does instead of just sampling at a pixel. @@ -101,6 +101,8 @@ float3 pomTraceRay(Texture2D texture, SamplerState sampler, float3 origin, float const float dd_max_sq = max(dot(ddx_tex, ddx_tex), dot(ddy_tex, ddy_tex)); uint minMipLevel = max(0, min(numMipLevels, log2(sqrt(dd_max_sq)) + cb.totalMipBias)); + origin.xy = origin.xy + (direction.xy * (1.f - neutralHeight)) / direction.z; + // Starting halfway down the quadtree seems to generally lead to fewer iterations to resolve, especially for heights close to 1. // Outwards rays start at 0, since they will just descend to 0 if they start anywhere else. int level = isOutwardsRay ? 0 : max((numMipLevels - 1) / 2, minMipLevel); @@ -228,10 +230,10 @@ float pomSampleHeight(uint16_t idx, uint16_t samplerIndex, float2 texcoord) return 1.0f - textures[nonuniformEXT(uint(idx))].SampleLevel(samplers[nonuniformEXT(uint(samplerIndex))], texcoord, 0).r; } -float2 pomGetStep(float3 direction, float layerSize, float displaceIn) +float2 pomGetStep(float3 direction, float layerSize, float totalHeight) { // convert tangent space direction to texcoord - return direction.xy / direction.z * displaceIn * layerSize; + return direction.xy / direction.z * totalHeight * layerSize; } float pomGetNumSamples(float3 direction) @@ -249,12 +251,19 @@ float3 pomCalculateTexcoord( float3 viewDir, inout uint iterations) { + float totalHeight = opaqueSurfaceMaterial.displaceIn + opaqueSurfaceMaterial.displaceOut; + float neutralHeight = opaqueSurfaceMaterial.displaceIn / totalHeight; + // TODO if the ray origin is below the displaceOut height, bad results happen. + // need to account for that case. + float3 direction = -float3(viewDir.xy, viewDir.z / totalHeight); if (cb.pomMode == DisplacementMode::RaymarchPOM) { float numLayers = pomGetNumSamples(viewDir); float layerSize = rcp(numLayers); - float2 step = pomGetStep(viewDir, layerSize, opaqueSurfaceMaterial.displaceIn); + float2 step = pomGetStep(viewDir, layerSize, totalHeight); float2 currTexcoord = surfaceInteraction.textureCoordinates; + + currTexcoord = currTexcoord + (step * numLayers * (1.f - neutralHeight)); float currHeight = pomSampleHeight(opaqueSurfaceMaterial.heightTextureIndex, opaqueSurfaceMaterial.samplerIndex, currTexcoord); // raymarch to find intersection @@ -275,40 +284,41 @@ float3 pomCalculateTexcoord( float weight = saturate(depthN / (depthN - depthNminus1)); return float3(currTexcoord + lerp(0..xx, step, weight), currHeight); } else if (cb.pomMode == DisplacementMode::QuadtreePOM) { + return pomTraceRay( textures[nonuniformEXT(uint(opaqueSurfaceMaterial.heightTextureIndex))], samplers[nonuniformEXT(uint(opaqueSurfaceMaterial.samplerIndex))], float3(surfaceInteraction.textureCoordinates, 1.0f), - -float3(viewDir.xy, viewDir.z / opaqueSurfaceMaterial.displaceIn), + direction, surfaceInteraction.textureGradientX, surfaceInteraction.textureGradientY, - iterations); + iterations, neutralHeight); } return float3(surfaceInteraction.textureCoordinates, 1.f); } float pomSampleVisibility( - uint16_t heightTextureIdx, - uint16_t samplerIndex, - float2 texcoord, - float3 rayDirection, - float displaceIn) + OpaqueSurfaceMaterial opaqueSurfaceMaterial, + float2 texcoord, + float3 rayDirection) { + // Don't actually care about neutral height here - this function just checks if a ray from a texcoord successfully exits + // the geometry described by the heightmap. Doesn't (currently) matter how high that geometry is relative to the original surface. + float totalHeight = opaqueSurfaceMaterial.displaceIn + opaqueSurfaceMaterial.displaceOut; // NOTE: pomTraceRay could be used here, but testing found it was more expensive and had worse artifacts when // doing visibility testing. float2 currTexcoord = texcoord; - float currHeight = pomSampleHeight(heightTextureIdx, samplerIndex, currTexcoord); + float currHeight = pomSampleHeight(opaqueSurfaceMaterial.heightTextureIndex, opaqueSurfaceMaterial.samplerIndex, currTexcoord); float currDepth = currHeight; float numLayers = pomGetNumSamples(rayDirection); float layerSize = rcp(numLayers); - float2 step = pomGetStep(rayDirection, layerSize, displaceIn); - + float2 step = pomGetStep(rayDirection, layerSize, totalHeight); float visibility = 1.0f; while (currDepth > 0.0f) { currTexcoord += step; - currHeight = pomSampleHeight(heightTextureIdx, samplerIndex, currTexcoord); + currHeight = pomSampleHeight(opaqueSurfaceMaterial.heightTextureIndex, opaqueSurfaceMaterial.samplerIndex, currTexcoord); currDepth -= layerSize; if (currHeight < currDepth) @@ -323,10 +333,8 @@ float pomSampleVisibility( float opaqueSurfaceMaterialInteractionCalcHeightThroughput( MinimalSurfaceInteraction minimalSurfaceInteraction, f16vec3 rayDirection, - uint16_t heightTextureIdx, - uint16_t samplerIndex, - float2 texcoord, - float displaceIn) + OpaqueSurfaceMaterial opaqueSurfaceMaterial, + float2 texcoord) { // rebuild tangent space const f16mat3 worldToTangent = f16mat3( @@ -334,7 +342,7 @@ float opaqueSurfaceMaterialInteractionCalcHeightThroughput( minimalSurfaceInteraction.triangleBitangent, minimalSurfaceInteraction.triangleNormal); const f16vec3 lightDirTangentSpace = normalize(mul(worldToTangent, rayDirection)); - return pomSampleVisibility(heightTextureIdx, samplerIndex, texcoord, lightDirTangentSpace, displaceIn); + return pomSampleVisibility(opaqueSurfaceMaterial, texcoord, lightDirTangentSpace); } #endif @@ -429,7 +437,7 @@ OpaqueSurfaceMaterialInteraction opaqueSurfaceMaterialInteractionCreate( opaqueSurfaceMaterialInteraction.flags |= thinFilmEnabled ? OPAQUE_SURFACE_MATERIAL_INTERACTION_FLAG_USE_THIN_FILM_LAYER : 0; #ifdef OPAQUE_MATERIAL_USE_POM - if (cb.pomMode != DisplacementMode::Off && opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID && opaqueSurfaceMaterial.displaceIn > 0.0f) + if (cb.pomMode != DisplacementMode::Off && opaqueSurfaceMaterial.hasValidDisplacement()) { const mat3 worldToTexture = inverse(transpose(mat3(surfaceInteraction.rawTangent, surfaceInteraction.rawBitangent, surfaceInteraction.interpolatedNormal))); // TextureGradient x might be along the same direction as the view ray? make a debug view that shows the dot product between textureGradient.x and viewDirTangentSpace? @@ -444,7 +452,7 @@ OpaqueSurfaceMaterialInteraction opaqueSurfaceMaterialInteractionCreate( surfaceInteraction, viewDirTextureSpace, iterations).xy; - opaqueSurfaceMaterialInteraction.flags |= opaqueSurfaceMaterial.heightTextureIndex != BINDING_INDEX_INVALID ? OPAQUE_SURFACE_MATERIAL_INTERACTION_FLAG_HAS_HEIGHT_TEXTURE : 0; + opaqueSurfaceMaterialInteraction.flags |= OPAQUE_SURFACE_MATERIAL_INTERACTION_FLAG_HAS_HEIGHT_TEXTURE; } #endif diff --git a/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h b/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h index 857d707bd..52f8e2297 100644 --- a/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h +++ b/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h @@ -47,6 +47,10 @@ struct MemoryPolymorphicSurfaceMaterial // Note: First two bits of data are reserved for common polymorphic type return (data[0].x & surfaceMaterialTypeMask) == surfaceMaterialTypeOpaque; } + + bool hasValidDisplacement() { + return isOpaque() && (data[0].x & OPAQUE_SURFACE_MATERIAL_FLAG_HAS_DISPLACEMENT); + } }; struct OpaqueSurfaceMaterial @@ -54,16 +58,17 @@ struct OpaqueSurfaceMaterial // 0 - 3 // bitmask of OPAQUE_SURFACE_MATERIAL_FLAG_* bits uint16_t flags; - float16_t displaceIn; uint16_t samplerIndex; uint16_t albedoOpacityTextureIndex; + uint16_t subsurfaceMaterialIndex; // 4-7 f16vec4 albedoOpacityConstant; - // 8-10 + // 8-11 + float16_t displaceIn; + float16_t displaceOut; uint16_t heightTextureIndex; - uint16_t subsurfaceMaterialIndex; // note: thinFilmThicknessConstant should be between 0 and 1 float16_t thinFilmThicknessConstant; @@ -72,20 +77,19 @@ struct OpaqueSurfaceMaterial // If we add a new field that is used for visibility, it should go above this. // If it isn't used for visibility, it should go below and be overridden in opaqueSurfaceMaterialCreate(). - // 11 - uint16_t emissiveColorTextureIndex; // 12-15 + uint16_t emissiveColorTextureIndex; uint16_t roughnessTextureIndex; uint16_t metallicTextureIndex; uint16_t normalTextureIndex; - float16_t emissiveIntensity; // 16-19 f16vec3 emissiveColorConstant; - float16_t roughnessConstant; + float16_t emissiveIntensity; - // 20-22 + // 20-23 + float16_t roughnessConstant; float16_t metallicConstant; float16_t anisotropy; uint16_t tangentTextureIndex; @@ -93,7 +97,11 @@ struct OpaqueSurfaceMaterial // Todo: Fixed function blend state info here in the future (Actually this should go on a Legacy Material, or some sort of non-PBR Legacy Surface) // padding (to keep size matching with MemoryPolymorphicSurfaceMaterial) - uint16_t data[9]; + uint16_t data[8]; + + bool hasValidDisplacement() { + return flags & OPAQUE_SURFACE_MATERIAL_FLAG_HAS_DISPLACEMENT; + } }; struct TranslucentSurfaceMaterial diff --git a/src/dxvk/shaders/rtx/pass/raytrace_args.h b/src/dxvk/shaders/rtx/pass/raytrace_args.h index f39a0aac6..ed25645ef 100644 --- a/src/dxvk/shaders/rtx/pass/raytrace_args.h +++ b/src/dxvk/shaders/rtx/pass/raytrace_args.h @@ -54,7 +54,8 @@ struct TerrainArgs { uint maxCascadeLevel; float lastCascadeScale; - uint2 pad0; + float displaceIn; + uint pad0; }; struct NeeCacheArgs { diff --git a/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity.comp.slang b/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity.comp.slang index 495a36fb0..610d34e11 100644 --- a/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity.comp.slang +++ b/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity.comp.slang @@ -75,7 +75,7 @@ void main(uint2 thread_id : SV_DispatchThreadID) outSample.r = InputTexture.SampleLevel(LinearSampler, uv, 0.f).r; break; case ReplacementMaterialTextureType::Enum::Height: - outSample.r = InputTexture.SampleLevel(LinearSampler, uv, 0.f).r * cb.scale; + outSample.r = InputTexture.SampleLevel(LinearSampler, uv, 0.f).r * cb.scale + cb.offset; break; // RGB16 diff --git a/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity_binding_indices.h b/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity_binding_indices.h index ad8f9e867..25f5e46ad 100644 --- a/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity_binding_indices.h +++ b/src/dxvk/shaders/rtx/pass/terrain_baking/decode_and_add_opacity_binding_indices.h @@ -40,5 +40,6 @@ struct DecodeAndAddOpacityArgs { float normalIntensity; float scale; + float offset; ReplacementMaterialTextureType::Enum textureType; }; diff --git a/src/dxvk/shaders/rtx/utility/shared_constants.h b/src/dxvk/shaders/rtx/utility/shared_constants.h index 2d2e1ead6..350904783 100644 --- a/src/dxvk/shaders/rtx/utility/shared_constants.h +++ b/src/dxvk/shaders/rtx/utility/shared_constants.h @@ -42,7 +42,8 @@ static const uint8_t surfaceMaterialTypeMask = uint8_t(0x3u); #define OPAQUE_SURFACE_MATERIAL_FLAG_USE_THIN_FILM_LAYER (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(0)) #define OPAQUE_SURFACE_MATERIAL_FLAG_ALPHA_IS_THIN_FILM_THICKNESS (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(1)) #define OPAQUE_SURFACE_MATERIAL_FLAG_IGNORE_ALPHA_CHANNEL (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(2)) -#define OPAQUE_SURFACE_MATERIAL_FLAG_IS_RAYTRACED_RENDER_TARGET COMMON_MATERIAL_FLAG_TYPE_OFFSET(3) +#define OPAQUE_SURFACE_MATERIAL_FLAG_IS_RAYTRACED_RENDER_TARGET (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(3)) +#define OPAQUE_SURFACE_MATERIAL_FLAG_HAS_DISPLACEMENT (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(4)) #define OPAQUE_SURFACE_MATERIAL_INTERACTION_FLAG_HAS_HEIGHT_TEXTURE (1 << 0) diff --git a/tests/rtx/unit/test_pnext.cpp b/tests/rtx/unit/test_pnext.cpp index 117c26395..b2e984153 100644 --- a/tests/rtx/unit/test_pnext.cpp +++ b/tests/rtx/unit/test_pnext.cpp @@ -212,6 +212,17 @@ namespace pnext_test_app { throw dxvk::DxvkError { ERROR_INTRO "C++ wrapper test fail: pick_RequestObjectPicking doesn't call lambda callback" }; } } + + namespace test_apiVersionOrdering { + static_assert(REMIXAPI_VERSION_MAKE(0, 0, 0) < REMIXAPI_VERSION_MAKE(1, 0, 0)); + static_assert(REMIXAPI_VERSION_MAKE(0, 0, 0) < REMIXAPI_VERSION_MAKE(0, 1, 0)); + static_assert(REMIXAPI_VERSION_MAKE(0, 0, 0) < REMIXAPI_VERSION_MAKE(0, 0, 1)); + + static_assert(REMIXAPI_VERSION_MAKE(1, 0, 0) < REMIXAPI_VERSION_MAKE(1, 2, 3)); + static_assert(REMIXAPI_VERSION_MAKE(1, 0, 1) < REMIXAPI_VERSION_MAKE(2, 0, 0)); + static_assert(REMIXAPI_VERSION_MAKE(1, 2, 3) < REMIXAPI_VERSION_MAKE(2, 0, 0)); + static_assert(REMIXAPI_VERSION_MAKE(0, 4, 1) < REMIXAPI_VERSION_MAKE(0, 4, 2)); + } } int main() {