From 261f2d962bf507d0e6524030434a82b7138820c6 Mon Sep 17 00:00:00 2001 From: Jessica_Natalia Date: Thu, 20 Aug 2026 02:45:26 -0300 Subject: [PATCH] improving graphics improving graphics --- .gitignore | 1 + APPLY_OVERLAY_CLEANUP.bat | 19 + profiles/vcs/CMakeLists.txt | 39 + .../vcs/FORCE_V9_3_CLEAN_REBASE_OBJECTS.bat | 2 +- .../vcs/FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat | 2 +- .../vcs/FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat | 2 +- .../FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat | 2 +- profiles/vcs/THIRD_PARTY.md | 4 +- profiles/vcs/config/ProperShaders.ini | 87 +- profiles/vcs/config/VCSNative.ini | 16 +- .../vcs/generated/generated_unit_0113.cpp | 1 + .../vcs/generated/generated_unit_0133.cpp | 6 +- .../vcs/generated/generated_unit_0136.cpp | 11 +- .../vcs/generated/generated_unit_0186.cpp | 3 +- .../vcs/generated/generated_unit_0208.cpp | 10 +- .../v89_register_residency_manifest.json | 20 +- .../vcs/host/ge_cloudworks_present_shader.hpp | 386 ------ profiles/vcs/host/ge_gpu_backend.hpp | 13 +- profiles/vcs/host/ge_gpu_backend_dx12.cpp | 1163 ++++++++++++++++- profiles/vcs/host/ge_gpu_hdr_shaders.inc | 400 ------ profiles/vcs/host/ge_renderer.cpp | 11 +- profiles/vcs/host/main.cpp | 8 +- .../host/propershaders/PrivateShaderStubs.cpp | 11 + .../propershaders/ProperShadersBridge.cpp | 60 + .../propershaders/ProperShadersBridge.hpp | 28 + .../propershaders/ProperShadersConfig.cpp | 108 ++ .../propershaders/ProperShadersConfig.hpp | 110 ++ profiles/vcs/host/vcs_config.cpp | 110 +- profiles/vcs/host/vcs_config.hpp | 42 +- profiles/vcs/host/vcs_draw_distance_patch.cpp | 129 +- profiles/vcs/host/vcs_draw_distance_patch.hpp | 27 +- profiles/vcs/host/vcs_project2dfx.cpp | 5 - profiles/vcs/host/vcs_runtime_log.cpp | 6 +- .../vcs/shaders/hdr/LICENSE_SIMULATEHDR.txt | 57 - profiles/vcs/shaders/hdr/hdr_blur.frag | 35 - profiles/vcs/shaders/hdr/hdr_blur.frag.spv | Bin 1196 -> 0 bytes profiles/vcs/shaders/hdr/hdr_combine.frag | 35 - profiles/vcs/shaders/hdr/hdr_combine.frag.spv | Bin 1488 -> 0 bytes profiles/vcs/shaders/hdr/hdr_composite.frag | 147 --- .../vcs/shaders/hdr/hdr_composite.frag.spv | Bin 5280 -> 0 bytes profiles/vcs/shaders/hdr/hdr_extract.frag | 39 - profiles/vcs/shaders/hdr/hdr_extract.frag.spv | Bin 1440 -> 0 bytes profiles/vcs/shaders/hdr/hdr_fullscreen.vert | 17 - .../vcs/shaders/hdr/hdr_fullscreen.vert.spv | Bin 932 -> 0 bytes profiles/vcs/tests/check_correctness_0_1.py | 28 +- .../vcs/tests/check_v810_resident_regions.py | 2 +- .../vcs/tests/check_v89_register_residency.py | 6 +- profiles/vcs/tests/vcs_config_tests.cpp | 57 +- profiles/vcs/tools/vcs_codegen_main.cpp | 43 +- 49 files changed, 1893 insertions(+), 1415 deletions(-) create mode 100644 APPLY_OVERLAY_CLEANUP.bat delete mode 100644 profiles/vcs/host/ge_cloudworks_present_shader.hpp delete mode 100644 profiles/vcs/host/ge_gpu_hdr_shaders.inc create mode 100644 profiles/vcs/host/propershaders/PrivateShaderStubs.cpp create mode 100644 profiles/vcs/host/propershaders/ProperShadersBridge.cpp create mode 100644 profiles/vcs/host/propershaders/ProperShadersBridge.hpp create mode 100644 profiles/vcs/host/propershaders/ProperShadersConfig.cpp create mode 100644 profiles/vcs/host/propershaders/ProperShadersConfig.hpp delete mode 100644 profiles/vcs/shaders/hdr/LICENSE_SIMULATEHDR.txt delete mode 100644 profiles/vcs/shaders/hdr/hdr_blur.frag delete mode 100644 profiles/vcs/shaders/hdr/hdr_blur.frag.spv delete mode 100644 profiles/vcs/shaders/hdr/hdr_combine.frag delete mode 100644 profiles/vcs/shaders/hdr/hdr_combine.frag.spv delete mode 100644 profiles/vcs/shaders/hdr/hdr_composite.frag delete mode 100644 profiles/vcs/shaders/hdr/hdr_composite.frag.spv delete mode 100644 profiles/vcs/shaders/hdr/hdr_extract.frag delete mode 100644 profiles/vcs/shaders/hdr/hdr_extract.frag.spv delete mode 100644 profiles/vcs/shaders/hdr/hdr_fullscreen.vert delete mode 100644 profiles/vcs/shaders/hdr/hdr_fullscreen.vert.spv diff --git a/.gitignore b/.gitignore index dd603d0..644b97a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ SAVEDATA/ # Python __pycache__/ *.pyc +/profiles/vcs/host/propershaders/private diff --git a/APPLY_OVERLAY_CLEANUP.bat b/APPLY_OVERLAY_CLEANUP.bat new file mode 100644 index 0000000..903d8db --- /dev/null +++ b/APPLY_OVERLAY_CLEANUP.bat @@ -0,0 +1,19 @@ +@echo off +setlocal +cd /d "%~dp0" +echo Cleaning obsolete shader files moved into profiles\vcs\host\propershaders\private... +if exist "profiles\\vcs\\host\\ge_cloudworks_present_shader.hpp" del /f /q "profiles\\vcs\\host\\ge_cloudworks_present_shader.hpp" +if exist "profiles\\vcs\\host\\ge_gpu_hdr_shaders.inc" del /f /q "profiles\\vcs\\host\\ge_gpu_hdr_shaders.inc" +if exist "profiles\\vcs\\shaders\\hdr\\LICENSE_SIMULATEHDR.txt" del /f /q "profiles\\vcs\\shaders\\hdr\\LICENSE_SIMULATEHDR.txt" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_blur.frag" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_blur.frag" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_blur.frag.spv" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_blur.frag.spv" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_combine.frag" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_combine.frag" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_combine.frag.spv" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_combine.frag.spv" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_composite.frag" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_composite.frag" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_composite.frag.spv" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_composite.frag.spv" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_extract.frag" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_extract.frag" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_extract.frag.spv" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_extract.frag.spv" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_fullscreen.vert" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_fullscreen.vert" +if exist "profiles\\vcs\\shaders\\hdr\\hdr_fullscreen.vert.spv" del /f /q "profiles\\vcs\\shaders\\hdr\\hdr_fullscreen.vert.spv" +echo Cleanup complete. +endlocal diff --git a/profiles/vcs/CMakeLists.txt b/profiles/vcs/CMakeLists.txt index 05411e5..934dbf7 100644 --- a/profiles/vcs/CMakeLists.txt +++ b/profiles/vcs/CMakeLists.txt @@ -189,6 +189,41 @@ endif() set(VCS_GPU_BACKEND_SOURCE host/ge_gpu_backend_dx12.cpp) set(VCS_HDR_POST_SOURCE host/vcs_hdr_post_dx12_stub.cpp) + +# ProperShaders is an optional private extension layer. The public repository can +# omit host/propershaders/private entirely and still build a stock VCS renderer. +set(VCS_PROPER_SHADERS_SOURCES + host/propershaders/ProperShadersConfig.cpp + host/propershaders/ProperShadersBridge.cpp +) +set(VCS_PRIVATE_SHADER_DIR "${VCS_PROFILE_DIR}/host/propershaders/private") +set(VCS_PRIVATE_SHADER_REQUIRED + "${VCS_PRIVATE_SHADER_DIR}/PipeSources.hpp" + "${VCS_PRIVATE_SHADER_DIR}/PrivateShaders.cpp" + "${VCS_PRIVATE_SHADER_DIR}/BuildingPipe.cpp" + "${VCS_PRIVATE_SHADER_DIR}/SkinPipe.cpp" + "${VCS_PRIVATE_SHADER_DIR}/VehiclePipe.cpp" + "${VCS_PRIVATE_SHADER_DIR}/RealtimeShadows.cpp" + "${VCS_PRIVATE_SHADER_DIR}/VolumetricClouds.cpp") +set(VCS_PRIVATE_SHADER_COMPLETE TRUE) +foreach(VCS_PRIVATE_SHADER_FILE IN LISTS VCS_PRIVATE_SHADER_REQUIRED) + if(NOT EXISTS "${VCS_PRIVATE_SHADER_FILE}") + set(VCS_PRIVATE_SHADER_COMPLETE FALSE) + endif() +endforeach() +if(VCS_PRIVATE_SHADER_COMPLETE) + message(STATUS "VCS ProperShaders: private shader pack present") + list(APPEND VCS_PROPER_SHADERS_SOURCES + host/propershaders/private/PrivateShaders.cpp + host/propershaders/private/BuildingPipe.cpp + host/propershaders/private/SkinPipe.cpp + host/propershaders/private/VehiclePipe.cpp + host/propershaders/private/RealtimeShadows.cpp + host/propershaders/private/VolumetricClouds.cpp) +else() + message(STATUS "VCS ProperShaders: private shader pack absent; building stock-renderer stubs") + list(APPEND VCS_PROPER_SHADERS_SOURCES host/propershaders/PrivateShaderStubs.cpp) +endif() set(VCS_HOST_SOURCES host/vcs_tier2_superblocks.cpp host/vcs_bootstrap_paths.cpp @@ -210,6 +245,7 @@ set(VCS_HOST_SOURCES host/vcs_project2dfx_lights.cpp host/vcs_draw_distance_patch.cpp host/vcs_runtime_log.cpp + ${VCS_PROPER_SHADERS_SOURCES} ${VCS_HDR_POST_SOURCE} host/ge_renderer.cpp ${VCS_GPU_BACKEND_SOURCE} @@ -337,6 +373,7 @@ if(PSPRECOMP_BUILD_PROFILE_TESTS) add_executable(vcs_config_tests tests/vcs_config_tests.cpp host/vcs_config.cpp + host/propershaders/ProperShadersConfig.cpp host/vcs_camera_input.cpp host/vcs_vehicle_input.cpp) target_include_directories(vcs_config_tests PRIVATE host) @@ -374,6 +411,7 @@ if(WIN32) host/dx12_presenter.cpp host/vcs_runtime_log.cpp host/vcs_config.cpp + host/propershaders/ProperShadersConfig.cpp host/vcs_camera_input.cpp host/vcs_vehicle_input.cpp) target_include_directories(vcs_dx12_probe PRIVATE host "${PROJECT_SOURCE_DIR}/include") @@ -385,6 +423,7 @@ if(WIN32) host/ge_gpu_backend_dx12.cpp host/vcs_runtime_log.cpp host/vcs_config.cpp + ${VCS_PROPER_SHADERS_SOURCES} host/vcs_camera_input.cpp host/vcs_vehicle_input.cpp) target_include_directories(vcs_dx12_ge_probe PRIVATE host "${PROJECT_SOURCE_DIR}/include") diff --git a/profiles/vcs/FORCE_V9_3_CLEAN_REBASE_OBJECTS.bat b/profiles/vcs/FORCE_V9_3_CLEAN_REBASE_OBJECTS.bat index 572f2f8..899d813 100644 --- a/profiles/vcs/FORCE_V9_3_CLEAN_REBASE_OBJECTS.bat +++ b/profiles/vcs/FORCE_V9_3_CLEAN_REBASE_OBJECTS.bat @@ -15,7 +15,7 @@ if not exist "%BUILD%" ( powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ^ "$build=[IO.Path]::GetFullPath('%BUILD%');" ^ "$names=@('vcs_profile.cpp.obj','savedata_utility_ui.cpp.obj','ge_renderer.cpp.obj','display_window.cpp.obj','main.cpp.obj','vcs_config.cpp.obj','generated_unit_0127.cpp.obj','generated_unit_0172.cpp.obj');" ^ - "$found=Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue ^| Where-Object { $names -contains $_.Name };" ^ + "$found=@(Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue); $found=@($found.Where({$names -contains $_.Name}));" ^ "Write-Host ('[V9.3] stale objects found: ' + $found.Count);" ^ "foreach($f in $found){Write-Host (' deleting: ' + $f.FullName); Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop};" ^ "$src=@('%REPO%\profiles\vcs\host\vcs_profile.cpp','%REPO%\profiles\vcs\host\savedata_utility_ui.cpp','%REPO%\profiles\vcs\host\ge_renderer.cpp','%REPO%\profiles\vcs\host\display_window.cpp','%REPO%\profiles\vcs\host\main.cpp','%REPO%\profiles\vcs\host\vcs_config.cpp','%REPO%\profiles\vcs\generated\generated_unit_0127.cpp','%REPO%\profiles\vcs\generated\generated_unit_0172.cpp');" ^ diff --git a/profiles/vcs/FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat b/profiles/vcs/FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat index ed922b9..c0c8d0a 100644 --- a/profiles/vcs/FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat +++ b/profiles/vcs/FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat @@ -15,7 +15,7 @@ if not exist "%BUILD%" ( powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ^ "$build=[IO.Path]::GetFullPath('%BUILD%');" ^ "$names=@('vcs_profile.cpp.obj','savedata_utility_ui.cpp.obj');" ^ - "$found=Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue ^| Where-Object { $names -contains $_.Name };" ^ + "$found=@(Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue); $found=@($found.Where({$names -contains $_.Name}));" ^ "Write-Host ('[V9.4] stale objects found: ' + $found.Count);" ^ "foreach($f in $found){Write-Host (' deleting: ' + $f.FullName); Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop};" ^ "$src=@('%REPO%\profiles\vcs\host\vcs_profile.cpp','%REPO%\profiles\vcs\host\savedata_utility_ui.cpp');" ^ diff --git a/profiles/vcs/FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat b/profiles/vcs/FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat index 710ae31..9bbd64d 100644 --- a/profiles/vcs/FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat +++ b/profiles/vcs/FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat @@ -15,7 +15,7 @@ if not exist "%BUILD%" ( powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ^ "$build=[IO.Path]::GetFullPath('%BUILD%');" ^ "$names=@('ge_gpu_backend_dx12.cpp.obj','savedata_utility_ui.cpp.obj');" ^ - "$found=Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue ^| Where-Object { $names -contains $_.Name };" ^ + "$found=@(Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue); $found=@($found.Where({$names -contains $_.Name}));" ^ "Write-Host ('[V9.5] stale objects found: ' + $found.Count);" ^ "foreach($f in $found){Write-Host (' deleting: ' + $f.FullName); Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop};" ^ "$src=@('%REPO%\profiles\vcs\host\ge_gpu_backend_dx12.cpp','%REPO%\profiles\vcs\host\savedata_utility_ui.cpp');" ^ diff --git a/profiles/vcs/FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat b/profiles/vcs/FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat index 4e046df..a0c0dea 100644 --- a/profiles/vcs/FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat +++ b/profiles/vcs/FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat @@ -15,7 +15,7 @@ if not exist "%BUILD%" ( powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ^ "$build=[IO.Path]::GetFullPath('%BUILD%');" ^ "$names=@('vcs_profile.cpp.obj');" ^ - "$found=Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue ^| Where-Object { $names -contains $_.Name };" ^ + "$found=@(Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue); $found=@($found.Where({$names -contains $_.Name}));" ^ "Write-Host ('[V9.6] stale objects found: ' + $found.Count);" ^ "foreach($f in $found){Write-Host (' deleting: ' + $f.FullName); Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop};" ^ "$src='%REPO%\profiles\vcs\host\vcs_profile.cpp';" ^ diff --git a/profiles/vcs/THIRD_PARTY.md b/profiles/vcs/THIRD_PARTY.md index 30c1db2..ff25db8 100644 --- a/profiles/vcs/THIRD_PARTY.md +++ b/profiles/vcs/THIRD_PARTY.md @@ -5,7 +5,9 @@ The VCS profile includes components that are distributed under licenses separate - FFmpeg runtime libraries and import libraries: LGPL 2.1 or later. The bundled notice is `third_party/ffmpeg/COPYING.LGPLv2.1`. - SMAA shader resources: see `third_party/smaa/LICENSE.txt`. - Project2DFX-derived VCS LOD-light data/behavior reference by ThirteenAG: MIT. See `third_party/project2dfx/LICENSE.txt` and `third_party/project2dfx/ATTRIBUTION.md`. -- HDR shader material under `shaders/hdr`: see `shaders/hdr/LICENSE_SIMULATEHDR.txt`. +- Optional/private HDR shader material, when that local pack is present, lives + under `host/propershaders/private/legacy/hdr`; its license is kept beside it + as `LICENSE_SIMULATEHDR.txt`. - CloudWorks Alpha 4.0 volumetric-cloud density/noise model by Brian Tu (RTU): CC BY-NC-SA 3.0. See `third_party/cloudworks/ATTRIBUTION.md`. diff --git a/profiles/vcs/config/ProperShaders.ini b/profiles/vcs/config/ProperShaders.ini index 51e9187..0918962 100644 --- a/profiles/vcs/config/ProperShaders.ini +++ b/profiles/vcs/config/ProperShaders.ini @@ -1,7 +1,79 @@ -; CloudWorks/ProperShaders port for VCSNative. Set Enabled=false to restore the -; unmodified game sky. Parameters marked PLACEHOLDER are the exact inputs that -; ProperShaders obtains from San Andreas timecycle/weather; they stay fixed here -; until their VCS guest-side sources are connected. +; VCSNative optional shader extension layer. +; +; The host renderer and the stock game do NOT depend on the private shader pack. +; If profiles/vcs/host/propershaders/private is absent when CMake configures the +; project, VCSNative builds the public stubs and renders the original game. +; +; Keep all optional/custom shader configuration in this file. VCSNative.ini is +; reserved for the core renderer/runtime. + +[ProperShaders] +Enabled=true + +; Dedicated material pipes. Each pipe has its own private .cpp implementation. +; WorldPipe (the Building pipe internally) receives ordinary world/building/object geometry. +[WorldPipe] +Enabled=true +DebugColor=false +DebugR=0.05 +DebugG=1.00 +DebugB=0.05 + +; SkinPipe is selected from the actual PSP GE skin-weight vertex layout. +[SkinPipe] +Enabled=true +DebugColor=false +DebugR=1.00 +DebugG=0.05 +DebugB=1.00 + +; VehiclePipe learns recolourable VCS vehicle materials from CLUT mutations. +[VehiclePipe] +Enabled=true +PaletteClassifier=true +DebugColor=false +DebugR=0.05 +DebugG=0.55 +DebugB=1.00 + +; Modern DX12 directional shadow map with PCF, sampled directly by each +; World/Skin/Vehicle material shader. The old fullscreen contact composite is not used. +[RealtimeShadows] +; The directional map is now sampled by the actual World/Skin/Vehicle material +; shaders. There is no fullscreen/contact-shadow composite in this path. +Enabled=true +; Stable camera-centred directional shadow map. 2048 is the default quality. +MapResolution=2048 +WorldRadius=140.0 +DepthRange=420.0 +MapBias=0.0015 +PcfRadius=1 +; Keep shadows and CloudWorks driven by the same sun direction by default. +UseCloudSunDirection=true +LightDirectionX=0.38 +LightDirectionY=-0.28 +LightDirectionZ=0.88 +Strength=0.58 +; Shadow debug views (restart required because material variants are compiled at startup): +; 0 = normal lighting/shadows +; 1 = raw directional shadow map fullscreen (caster/depth proof) +; 2 = receiver shadow mask: white=shadow, black=lit +; 3 = light projection/coverage: RG=shadow UV, blue=inside map, red=outside +; 4 = sampled shadow-map depth on receivers (grayscale) +; 5 = receiver depth in light space (grayscale) +; 6 = compare result: red=blocked, green=lit +DebugMode=0 +; Legacy contact-shadow setting retained for config compatibility; unused by the material path. +ContactShadows=false +ContactStrength=0.22 +Steps=12 +MaxDistancePixels=28.0 +Thickness=0.0025 +LightScreenX=-0.55 +LightScreenY=0.75 + +; CloudWorks/ProperShaders volumetric-cloud port. It now lives in the same +; optional private shader module instead of the core GE backend. [VolumetricClouds] Enabled=true DownscaleDiv=2 @@ -37,7 +109,12 @@ FogColorG=0.68 FogColorB=0.78 FogStart=4500.0 -; ProperShaders temporal resolve controls. +; Temporal resolve controls. TemporalBlend=0.50 TemporalDenoise=1.0 TemporalClamp=1.0 + +; Reserved for the old SimulateHDR port. Its DX12 implementation is currently +; disabled, but its settings live here rather than VCSNative.ini. +[SimulateHDR] +Enabled=false diff --git a/profiles/vcs/config/VCSNative.ini b/profiles/vcs/config/VCSNative.ini index 576e91b..e8cf517 100644 --- a/profiles/vcs/config/VCSNative.ini +++ b/profiles/vcs/config/VCSNative.ini @@ -67,17 +67,13 @@ AspectRatio=auto Enabled=false PerformanceLog=false -[SimulateHDR] -Enabled=false - [DrawDistance] -; Correctness 0.1: implementation is complete but remains opt-in so normal -; gameplay/performance can be compared directly against the clean GitHub baseline. -; Change only Enabled=true to test the prefilled 1.50x extension. -Enabled=false -World=1.50 -Vehicles=1.50 -NPCs=1.50 +; 1.0 = original PSP distance. Objects affects world model-info + far clip; +; Vehicles/NPCs patch the exact generated guest culling constants as well. +Enabled=true +Objects=2.00 +Vehicles=2.00 +NPCs=2.00 [Frontend] ; V9 load-only startup: the removed BootToGameMenu path can no longer be diff --git a/profiles/vcs/generated/generated_unit_0113.cpp b/profiles/vcs/generated/generated_unit_0113.cpp index d9b027b..855151d 100644 --- a/profiles/vcs/generated/generated_unit_0113.cpp +++ b/profiles/vcs/generated/generated_unit_0113.cpp @@ -8769,6 +8769,7 @@ L_089CB33C: } L_089CB38C: if (vcs::g_draw_distance_runtime_scales.npcs > 1.0f) { + ++vcs::g_draw_distance_runtime_telemetry.npc_constant_hits; const float dd_scale = vcs::g_draw_distance_runtime_scales.npcs; ctx.gpr[19] = ctx.gpr[29] + static_cast(64); ctx.gpr[30] = ctx.gpr[29] + static_cast(16); diff --git a/profiles/vcs/generated/generated_unit_0133.cpp b/profiles/vcs/generated/generated_unit_0133.cpp index 05b8490..38dd2d4 100644 --- a/profiles/vcs/generated/generated_unit_0133.cpp +++ b/profiles/vcs/generated/generated_unit_0133.cpp @@ -139,7 +139,7 @@ static const std::uint16_t kEntryIds_recomp_unit_0133[4093] = { }; void recomp_unit_0133_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) { // PSPRECOMP_AOT_REGCACHE_BEGIN -// PSPRECOMP_AOT_REGCACHE_META gprs=4,29,5,31,16,6 fprs=12,13,14,15 gpr_occ=3817 fpr_occ=847 gpr_total=5332 fpr_total=1295 +// PSPRECOMP_AOT_REGCACHE_META gprs=4,29,5,31,16,6 fprs=12,13,14,15 gpr_occ=3817 fpr_occ=848 gpr_total=5332 fpr_total=1296 std::uint32_t aot_gpr_4 = ctx.gpr[4]; std::uint32_t aot_gpr_29 = ctx.gpr[29]; std::uint32_t aot_gpr_5 = ctx.gpr[5]; @@ -6514,6 +6514,10 @@ L_08A1AD64: ctx.pc = jump_target; AOT_REGCACHE_SYNC_OUT(); return; L_08A1AD6C: + if (vcs::g_draw_distance_runtime_scales.world > 1.0f) { + ++vcs::g_draw_distance_runtime_telemetry.far_clip_set_hits; + aot_fpr_12 *= vcs::g_draw_distance_runtime_scales.world; + } jump_target = aot_gpr_31; aot_mem.aot_direct_store32(ctx.gpr[28] + static_cast(7796), std::bit_cast(aot_fpr_12)); local_pc = jump_target; diff --git a/profiles/vcs/generated/generated_unit_0136.cpp b/profiles/vcs/generated/generated_unit_0136.cpp index 50c84bd..01ae116 100644 --- a/profiles/vcs/generated/generated_unit_0136.cpp +++ b/profiles/vcs/generated/generated_unit_0136.cpp @@ -139,7 +139,7 @@ static const std::uint16_t kEntryIds_recomp_unit_0136[4092] = { }; void recomp_unit_0136_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) { // PSPRECOMP_AOT_REGCACHE_BEGIN -// PSPRECOMP_AOT_REGCACHE_META gprs=4,29,31,16,17,18 fprs=12,20,22,24 gpr_occ=3036 fpr_occ=858 gpr_total=4359 fpr_total=1266 +// PSPRECOMP_AOT_REGCACHE_META gprs=4,29,31,16,17,18 fprs=12,20,22,24 gpr_occ=3036 fpr_occ=860 gpr_total=4359 fpr_total=1268 std::uint32_t aot_gpr_4 = ctx.gpr[4]; std::uint32_t aot_gpr_29 = ctx.gpr[29]; std::uint32_t aot_gpr_31 = ctx.gpr[31]; @@ -1109,11 +1109,16 @@ L_08A24120: if (([&]() { AOT_REGCACHE_SYNC_OUT(); const bool aot_regcache_same_ = (rt.invoke_chained_trusted_direct<&recomp_unit_0133_entry, 133u, 449u, 0x08A1AD6Cu>(ctx, &aot_mem)); if (aot_regcache_same_) AOT_REGCACHE_SYNC_IN(); else aot_regcache_valid = false; return aot_regcache_same_; }()) && ctx.pc == 0x08A24128u) goto L_08A24128; AOT_REGCACHE_SYNC_OUT(); return; L_08A24128: + // Proven VCS actor-LOD path: the PSP WidescreenFix replaces the call that + // normally follows this store. It writes s0+0x7A0 from s0+0x7A8 times the + // requested multiplier and returns that multiplier in f0; the caller then + // applies f0 to s0+0x7A8. Do the same directly in the recomp AOT path. if (vcs::g_draw_distance_runtime_scales.entity > 1.0f) { - const float dd_base = std::bit_cast(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast(1960))); + ++vcs::g_draw_distance_runtime_telemetry.actor_lod_hits; const float dd_scale = vcs::g_draw_distance_runtime_scales.entity; + aot_fpr_12 = std::bit_cast(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast(1960))); aot_mem.aot_direct_store32(aot_gpr_16 + static_cast(1952), - std::bit_cast(dd_base * dd_scale)); + std::bit_cast(aot_fpr_12 * dd_scale)); ctx.fpr[0] = dd_scale; goto L_08A24138; } diff --git a/profiles/vcs/generated/generated_unit_0186.cpp b/profiles/vcs/generated/generated_unit_0186.cpp index e1cc291..6781738 100644 --- a/profiles/vcs/generated/generated_unit_0186.cpp +++ b/profiles/vcs/generated/generated_unit_0186.cpp @@ -139,7 +139,7 @@ static const std::uint16_t kEntryIds_recomp_unit_0186[4093] = { }; void recomp_unit_0186_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) { // PSPRECOMP_AOT_REGCACHE_BEGIN -// PSPRECOMP_AOT_REGCACHE_META gprs=4,5,6,31,29,16 fprs=12,14,13,20 gpr_occ=3869 fpr_occ=363 gpr_total=5970 fpr_total=507 +// PSPRECOMP_AOT_REGCACHE_META gprs=4,5,6,31,29,16 fprs=12,14,13,20 gpr_occ=3869 fpr_occ=363 gpr_total=5971 fpr_total=507 std::uint32_t aot_gpr_4 = ctx.gpr[4]; std::uint32_t aot_gpr_5 = ctx.gpr[5]; std::uint32_t aot_gpr_6 = ctx.gpr[6]; @@ -2252,6 +2252,7 @@ L_08AEC904: goto L_08AEC918; } L_08AEC918: + vcs::draw_distance_world_table_ready(rt, ctx.gpr[28]); { std::uint32_t aot_run_words[4]{}; aot_mem.aot_direct_load32_block(aot_gpr_29 + static_cast(0), aot_run_words); aot_gpr_16 = aot_run_words[0]; diff --git a/profiles/vcs/generated/generated_unit_0208.cpp b/profiles/vcs/generated/generated_unit_0208.cpp index c37f84f..75cf06c 100644 --- a/profiles/vcs/generated/generated_unit_0208.cpp +++ b/profiles/vcs/generated/generated_unit_0208.cpp @@ -139,7 +139,7 @@ static const std::uint16_t kEntryIds_recomp_unit_0208[4093] = { }; void recomp_unit_0208_entry(Runtime &rt, AllegrexContext &ctx, std::uint16_t direct_entry_id, GuestMemory::AotFastView &aot_mem) { // PSPRECOMP_AOT_REGCACHE_BEGIN -// PSPRECOMP_AOT_REGCACHE_META gprs=4,5,6,29,31,16 fprs=12,13,20,22 gpr_occ=3954 fpr_occ=758 gpr_total=5152 fpr_total=1078 +// PSPRECOMP_AOT_REGCACHE_META gprs=4,5,6,29,31,16 fprs=12,13,20,22 gpr_occ=3954 fpr_occ=759 gpr_total=5152 fpr_total=1079 std::uint32_t aot_gpr_4 = ctx.gpr[4]; std::uint32_t aot_gpr_5 = ctx.gpr[5]; std::uint32_t aot_gpr_6 = ctx.gpr[6]; @@ -4534,6 +4534,13 @@ L_08B45AA4: aot_fpr_13 = std::bit_cast(aot_gpr_4); { const bool branch_taken = 0u == 0u; { const float fs = aot_fpr_12; const float ft = aot_fpr_13; if ((std::isinf(fs) && ft == 0.0f) || (std::isinf(ft) && fs == 0.0f)) aot_fpr_12 = std::bit_cast(0x7FC00000u); else aot_fpr_12 = fs * ft; } + // This is the dominant vehicle-distance path. The old experiment only + // modified L_08B45AC0 below; this unconditional branch normally jumps + // straight to AC8, so most vehicles never saw the configured multiplier. + if (vcs::g_draw_distance_runtime_scales.vehicles > 1.0f) { + ++vcs::g_draw_distance_runtime_telemetry.vehicle_dynamic_hits; + aot_fpr_12 *= vcs::g_draw_distance_runtime_scales.vehicles; + } if (branch_taken) { goto L_08B45AC8; } @@ -4541,6 +4548,7 @@ L_08B45AA4: } L_08B45AC0: if (vcs::g_draw_distance_runtime_scales.vehicles > 1.0f) { + ++vcs::g_draw_distance_runtime_telemetry.vehicle_fallback_hits; aot_fpr_12 = 60.0f * vcs::g_draw_distance_runtime_scales.vehicles; goto L_08B45AC8; } diff --git a/profiles/vcs/generated/v89_register_residency_manifest.json b/profiles/vcs/generated/v89_register_residency_manifest.json index 6093122..e532920 100644 --- a/profiles/vcs/generated/v89_register_residency_manifest.json +++ b/profiles/vcs/generated/v89_register_residency_manifest.json @@ -3,9 +3,9 @@ "files": 234, "changed": 230, "gpr_occurrences": 914095, - "fpr_occurrences": 146809, - "gpr_total": 1266110, - "fpr_total": 198361, + "fpr_occurrences": 146813, + "gpr_total": 1266111, + "fpr_total": 198365, "resident_weight": 6, "units": [ { @@ -2805,9 +2805,9 @@ 15 ], "gpr_occurrences": 3817, - "fpr_occurrences": 847, + "fpr_occurrences": 848, "gpr_total": 5332, - "fpr_total": 1295 + "fpr_total": 1296 }, { "file": "generated_unit_0134.cpp", @@ -2868,9 +2868,9 @@ 24 ], "gpr_occurrences": 3036, - "fpr_occurrences": 858, + "fpr_occurrences": 860, "gpr_total": 4359, - "fpr_total": 1266 + "fpr_total": 1268 }, { "file": "generated_unit_0137.cpp", @@ -3915,7 +3915,7 @@ ], "gpr_occurrences": 3869, "fpr_occurrences": 363, - "gpr_total": 5970, + "gpr_total": 5971, "fpr_total": 507 }, { @@ -4376,9 +4376,9 @@ 22 ], "gpr_occurrences": 3954, - "fpr_occurrences": 758, + "fpr_occurrences": 759, "gpr_total": 5152, - "fpr_total": 1078 + "fpr_total": 1079 }, { "file": "generated_unit_0209.cpp", diff --git a/profiles/vcs/host/ge_cloudworks_present_shader.hpp b/profiles/vcs/host/ge_cloudworks_present_shader.hpp deleted file mode 100644 index 57873f1..0000000 --- a/profiles/vcs/host/ge_cloudworks_present_shader.hpp +++ /dev/null @@ -1,386 +0,0 @@ -#pragma once - -namespace vcs { - -// Direct SM5.1 port of the CloudWorks implementation used by ProperShaders. -// Density profiles, adaptive integration, sun shadow march, Beer-Lambert -// extinction, Mie/Rayleigh lighting and front-to-back layer composition retain -// the source equations and constants. The only platform adaptation is the -// fullscreen input/ray contract used by VCSNative's DX12 backend. -// CloudWorks: Brian Tu (RTU), CC BY-NC-SA 3.0. -inline constexpr char kCloudWorksPresentShaderHlsl[] = R"CLOUD_HLSL( -// Cloud pass descriptor contract: -// t0 = sparse march texture for CloudTemporalResolvePS, or resolved cloud -// history for CloudCompositePS/PresentPS -// t1 = previous resolved cloud history for CloudTemporalResolvePS -// s0 = clamp sampler (linear for resolve/composite) -Texture2D CloudTexture0 : register(t0); -Texture2D CloudTexture1 : register(t1); -SamplerState CloudSampler : register(s0); - -cbuffer CloudState : register(b0) { - float3 CloudRayRight; float g_Time; - float3 CloudRayUp; float randomSeed; - float3 CloudRayForward; float g_Opacity; - float3 CloudCameraPosition; uint g_Settings; - float3 g_CloudCoverage; float g_CloudSpeed; - float3 vSunLightDir; float fDayProgression; - float3 g_vSunColor; float g_AtmDense; - float3 g_vCloudBaseColor; float g_Mist; - float3 g_FogColor; float g_FogDens; - float g_Brightness; float3 CloudPadding; -}; - -// Temporal pass contract (20 DWORDs). Together with CloudState's 40 DWORDs, -// a two-SRV descriptor table and one sampler table this consumes 62 of the -// D3D12 root signature's 64 DWORD budget. -// -// Translation invalidates history on the CPU. The previous ray basis below -// therefore only has to reproject camera rotation; it is the same world-ray -// basis used by CloudState and avoids importing matrix-layout conventions from -// either the PSP GE or D3D9 ProperShaders implementation. -cbuffer CloudTemporalState : register(b2) { - float3 PrevCloudRayRight; float CloudHistoryValid; - float3 PrevCloudRayUp; float CloudTemporalBlend; - float3 PrevCloudRayForward; float CloudSpatialMix; - float2 CloudTexelSize; float2 CloudSubPixel; - float CloudFullResolutionMarch; - float CloudClampExpand; - float2 CloudTemporalPadding; -}; - -struct PresentVertexOutput { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; -PresentVertexOutput PresentVS(uint id : SV_VertexID) { - PresentVertexOutput o; - if (id == 0u) { o.position=float4(-1,-1,0,1); o.uv=float2(0,1); } - else if (id == 1u) { o.position=float4(-1,3,0,1); o.uv=float2(0,-1); } - else { o.position=float4(3,-1,0,1); o.uv=float2(2,1); } - return o; -} -float4 PresentPS(PresentVertexOutput i) : SV_TARGET { - return CloudTexture0.SampleLevel(CloudSampler, i.uv, 0.0); -} - -static const float VC_HASH_MUL = 1332.03398875; -float hash(float n) { return frac(sin(n / 1873.1873 + randomSeed) * VC_HASH_MUL); } -float noise2d(float3 p) { - float3 fr=floor(p), ft=frac(p); float n=1153*fr.x+2381*fr.y+p.z; - float nr=n+1153, nd=n+2381, no=nr+2381; - return lerp(lerp(hash(n),hash(nr),ft.x),lerp(hash(nd),hash(no),ft.x),ft.y); -} -float noise3d(float3 p) { - float3 fr=floor(p), ft=frac(p); float n=1153*fr.x+2381*fr.y+fr.z; - float nr=n+1153, nd=n+2381, no=nr+2381; - float v=lerp(hash(n),hash(n+1),ft.z), vr=lerp(hash(nr),hash(nr+1),ft.z); - float vd=lerp(hash(nd),hash(nd+1),ft.z), vo=lerp(hash(no),hash(no+1),ft.z); - return lerp(lerp(v,vr,ft.x),lerp(vd,vo,ft.x),ft.y); -} -float map(float x,float a,float b,float c,float d) { return (x-b)/(a-b)*(c-d)+d; } -float clampMap(float x,float a,float b,float c,float d) { return saturate((x-b)/(a-b))*(c-d)+d; } - -static const float atmosphereStep=15.0, lightStep=3.0, fix=0.00001; -static const float3 sunLightStrength=685.0*float3(1.0,0.96,0.949); -static const float LightingDecay=300.0, rayleighStrength=1.85, rayleighDecay=900.0; -static const float3 waveLengthFactor=float3(6.5*6.5*6.5*6.5,5.4*5.4*5.4*5.4,4.5*4.5*4.5*4.5); -static const float3 scatteringFactor=waveLengthFactor/rayleighDecay; -static const float earthRadius=6.421, groundHeight=6.371, game2atm=1000000.0; -static const float3 AtmOrigin=float3(0,0,groundHeight); -static const float4 earth=float4(0,0,0,earthRadius); -float3 Game2Atm(float3 p) { return p/game2atm+AtmOrigin; } -float3 Game2Atm_Alt(float3 p) { return float3(0,0,p.z/game2atm)+AtmOrigin; } -float4 sphereCast(float3 origin,float3 ray,float4 sphere,float steps,out float3 begin) { - begin=origin; - float3 p=origin-sphere.xyz; float r=length(p), d=length(cross(p,ray)); - if(d>sphere.w+fix){begin=0;return 0;} float sr=sqrt(sphere.w*sphere.w-d*d),dr=-dot(p,ray); - float3 pc=origin+ray*dr,pf=pc+ray*sr,pb=pc-ray*sr; float sl; - if(r>sphere.w){begin=pb;sl=sr*2/steps;}else{begin=origin;sl=length(pf-origin)/steps;} - return float4(ray*sl,sl); -} -float2 Density(float3 pos,float4 sphere,float strength,float condense) { - float atmDensity=3.0+g_AtmDense,fogDensity=0.25+g_Mist; - float r=groundHeight,h=length(pos-sphere.xyz)-r,ep=exp(-(sphere.w-r)*condense); - float fog=fogDensity*(1.0/(1200.0*h+0.5)-0.04)/1.96; - if(h<0)return float2(strength,fogDensity); - return float2((exp(-h*condense)-ep)/(1.0-ep)*strength,fog); -} -float3 rayleighScattering(float c){return (1+c*c)*rayleighStrength/waveLengthFactor;} -float MiePhase(float c){return 1.0+1.6*exp(20.0*(c-1.0));} -float MieScattering(float c){return (0.125+g_Mist*0.1)*MiePhase(c);} -float3 LightDecay(float r,float m){return exp(-r/scatteringFactor-m*100.0);} -float3 SunLight(float3 light,float3 position,float3 lightDirection,float4 sphere){ - float3 smp=position;float4 sms=sphereCast(position,lightDirection,sphere,lightStep,smp);float2 dl=0; - [unroll]for(int j=0;j<3;j++){smp+=sms.xyz/2;dl+=Density(smp,sphere,3.0+g_AtmDense,1.0)*sms.w;smp+=sms.xyz/2;} - return light*LightDecay(dl.x,dl.y)/LightingDecay; -} -float3 LightSource(float3 active,float day,float3 color,out float3 source){ - source=active; - if(day<-0.2)return sunLightStrength*smoothstep(0.1,0.3,-day)*color; - return sunLightStrength*smoothstep(-0.2,-0.1,day)*color; -} -float3 AtmosphereScattering(float3 background,float3 marchPos,float4 marchStep,float3 ray, - float3 lightStrength,float3 lightDirection,float strength,float4 sphere){ - float3 intensity=0;float ang=dot(ray,lightDirection),mie=MieScattering(ang);float3 raylei=rayleighScattering(ang); - if(marchStep.w>0.015)marchStep/=marchStep.w/0.015;float2 dv=0; - [loop]for(int i=0;i<15;i++){float3 smp=marchPos;float4 sms=sphereCast(marchPos,lightDirection,sphere,lightStep,smp); - float2 sampling=Density(marchPos,sphere,3.0+g_AtmDense,1.0)*marchStep.w;dv+=sampling/2;float2 dl=dv; - [unroll]for(int j=0;j<3;j++){smp+=sms.xyz;dl+=Density(smp,sphere,3.0+g_AtmDense,1.0)*sms.w;} - intensity+=LightDecay(dl.x,dl.y)*(raylei*sampling.x+mie*sampling.y);dv+=sampling/2;marchPos+=marchStep.xyz;} - return lightStrength*intensity*strength+background*LightDecay(dv.x,dv.y); -} -float3 atmosphere_scattering(float strength,float3 color,float3 camera,float3 ray,float distance,float3 sunDirection,float4 sphere){ - if(distance<200)return color;float fade=smoothstep(200,300,distance);float4 step=0; - step.w=15.0*distance/atmosphereStep/game2atm;step.xyz=ray*step.w;float3 lightDir=sunDirection; - float3 light=LightSource(sunDirection,fDayProgression,1.0,lightDir); - float3 scattered=AtmosphereScattering(color,Game2Atm_Alt(camera),step,ray,light,lightDir,1.0,sphere); - return lerp(color,scattered,fade); -} -)CLOUD_HLSL" -R"CLOUD_HLSL( - -struct CloudBaseColor {float3 BaseColor;float3 BaseColor_Day;float3 BaseColor_Sunset;}; -struct CloudProfile { - float4 march;float2 cutoff;float2 volumeBox;float4 shape;float brightness;float3 range; - float2 solidness;float2 densityChunk;float4 shadow;float4 distortion;float fade; - float3 densityDetail;float3 scaleChunk;float3 scaleDetail;float3 cloudShift; - float3 offsetA;float3 offsetB;float3 offsetC;float3 offsetD; -}; -float gameTime(){return 1000.0+g_Time*g_CloudSpeed/100.0;} -float3 PosOnPlane(float3 o,float3 d,float h,inout float dist){dist=(h-o.z)/d.z;return o+d*dist;} -float4 CloudShape(float z,float4 shape,float3 range){float soft=map(z,shape.y,shape.x,range.z,range.y); - return float4(smoothstep(shape.z,lerp(shape.y,shape.z,shape.w),z)*smoothstep(shape.x,lerp(shape.y,shape.x,shape.w),z),range.x+soft,range.x-soft,soft);} -float3 DistortionVec(float lump,float4 d){return float3(cos(lump*d.x)*d.y,0,-lump*d.z);} -float Chunk(float3 pos,float2 density,float3 scale,float3 shift,float3 oA,float3 oB,float cs){ - pos.z/=scale.z;pos+=shift*pos.z;float3 pA=(pos+oA)*scale.x,pB=(pos+oB)*scale.y; - return noise3d(pA)*(noise3d(pB)*density.y+density.x)*cs;} -float DetailA(float3 pos,float3 density,float3 scale,float3 oC,float3 dist){return density.x*noise3d((pos+oC+dist)*scale.x);} -float DetailB(float lump,float3 pos,float3 density,float3 scale,float4 dp,float3 oC,float3 oD,float cs){ - float3 d=DistortionVec(lump,dp),pD=pos+oD;float dens=DetailA(pos,density,scale,oC,d);d.z-=dens*dp.w; - dens+=density.y*noise3d((pD+d/3)*scale.y);dens+=dens*density.z*noise3d((pD+d*8)*scale.z);return dens;} -float GetDensity(float df,float height,float low,float high,float2 vb,float2 sol){return clampMap(df,low,high,0,clampMap(height,vb.y,vb.x,sol.y,sol.x));} -float ShadowMarching(float dens,float3 p,CloudProfile a,float3 threshold,float3 sunDir){ - uint shadowSteps=(g_Settings>>8)&15u; - if(dens<=0.025)return dens*a.shadow.x; - // ProperShaders used four large samples for the DX9/SM3 instruction budget. - // DX12 can split the same physical shadow-march reach into up to eight - // samples, removing the dark density contours without changing its extent. - float stepLen=a.shadow.x*(8.0/max((float)shadowSteps,1.0)); - float limit=2.0/a.shadow.w/stepLen,d=0;float4 st=float4(sunDir*stepLen,stepLen); - [loop]for(uint i=0;i<8;i++){if(i>=shadowSteps||d>=limit||p.z>=a.volumeBox.x||p.z<=a.volumeBox.y)break; - p+=st.xyz;float4 cs=CloudShape(p.z,a.shape,threshold);float d1=Chunk(p,a.densityChunk,a.scaleChunk,a.cloudShift,a.offsetA,a.offsetB,cs.x); - float d2=DetailA(p,a.densityDetail,a.scaleDetail,a.offsetC,DistortionVec(d1,a.distortion))*a.shadow.y; - d+=GetDensity(d1*d2+d1,p.z,cs.z-a.shadow.z,cs.y,a.volumeBox,a.solidness);} - return d*a.shadow.w*st.w;} - -float4 CloudAtRay(CloudProfile a,CloudBaseColor b,float3 dir,float3 cam,float3 light,float3 lightDir,float time,inout float distance){ - float4 d=float4(0,0,0,a.march.y);if(abs(dir.z)<1e-6)return float4(0,0,0,1); - float3 p=PosOnPlane(cam,dir,clamp(cam.z,a.volumeBox.y+0.001,a.volumeBox.x-0.001),d.x);d.y=d.x; - if(d.x>=0&&distance>d.x){a.range.x=1/a.range.x;float3 fx=float3(0,0,1);float last=0,pdf=0; - [loop]for(int i=0;i<64;i++){if(fx.z<=0||p.z>a.volumeBox.x||p.z=(int)a.march.w||d.x-d.w>=distance||d.x>=a.fade)break; - float3 cs=CloudShape(p.z,a.shape,a.range).xyz;float d1=Chunk(p,a.densityChunk,a.scaleChunk,a.cloudShift,a.offsetA,a.offsetB,cs.x); - float d2=DetailB(d1,p,a.densityDetail,a.scaleDetail,a.distortion,a.offsetC,a.offsetD,cs.x);float df=d1*d2+d1; - if(df>cs.z){float dens=GetDensity(df,p.z,cs.z,cs.y,a.volumeBox,a.solidness);float cd=(dens+last)*a.march.x/2;last=dens; - if(d.x>=distance)cd*=d.z/d.w;if(cd>0)d.y=d.y*(1-fx.z)+fx.z*d.x;fx.y+=cd;fx.z=(exp(-fx.y)-a.cutoff.y)/(1-a.cutoff.y);d.z=distance-d.x; - if(fx.y<2.3)fx.x+=cd*exp(-ShadowMarching(cd,p,a,a.range,lightDir)-fx.y);} - d.w=clampMap(2*df-pdf,cs.z*0.85,a.cutoff.x,a.march.x,a.march.y);d.w*=clampMap(d.x,0,a.fade,1,a.march.z); - // The stochastic step offset is intentional. ProperShaders averages it in - // the reprojected temporal history instead of exposing one undersampled - // march directly on screen. - d.w+=noise2d(p+g_Time)*a.march.x;pdf=df;p+=dir*d.w;d.x+=d.w;} - if(fx.z<1){fx=saturate(fx);float3 z=float3(0,0,cam.z);float3 cbright=SunLight(light,Game2Atm(z+dir*d.y),lightDir,earth)*a.brightness; - float3 C=cbright*fx.x*MiePhase(dot(lightDir,dir))+(b.BaseColor*g_vCloudBaseColor)*(1-fx.z); - C=atmosphere_scattering(1-fx.z,C,Game2Atm(z),dir,d.y/game2atm,lightDir,earth); - distance=distance*fx.z+d.y*(1-fx.z);return float4(C,fx.z);}} - return float4(0,0,0,1);} - -float4 CloudAtRayHighLite(CloudBaseColor b,float3 dir,float3 cam,float3 light,float3 lightDir,float time,inout float distance){ - const float top=3600.0,bottom=3500.0;float4 d=float4(0,0,0,75); - float3 p=PosOnPlane(cam,dir,clamp(cam.z,bottom+.001,top-.001),d.x);d.y=d.x; - if(d.x>=0&&distance>d.x){ - float coverage=g_CloudCoverage.z;float3 range=float3(1+coverage*.3,.2,.35); - float grow=noise3d(float3(3800,bottom,time/2000))*.45+.65; - range.x*=grow*(1-coverage)+coverage;range.x=1/range.x; - const float2 volumeBox=float2(top,bottom),solidness=float2(.25,0),densityChunk=float2(.4,.3); - const float3 scaleChunk=float3(.00016,.0008,1.5),densityDetail=float3(.2,.1,.6),scaleDetail=float3(.004,.006667,.02); - const float4 distortion=float4(2.5,15000,0,0); - float3 oA=float3(1.3,-1.8,0)*-time,oB=float3(1.6,.8,0)*-time; - float3 oC=float3(2.5,.2,.5)*-time,oD=float3(3,.1,-.1)*-time; - float3 fx=float3(0,0,1);float last=0,pdf=0; - [loop]for(int i=0;i<32;i++){ - if(fx.z<=0||p.z>top||p.z=distance||d.x>=60000)break; - float3 cs=CloudShape(p.z,float4(3800,3520,3450,0),range).xyz; - float d1=Chunk(p,densityChunk,scaleChunk,0,oA,oB,cs.x); - float d2=DetailB(d1,p,densityDetail,scaleDetail,distortion,oC,oD,cs.x);float df=d1*d2+d1; - if(df>cs.z){float dens=GetDensity(df,p.z,cs.z,cs.y,volumeBox,solidness);float cd=(dens+last)*2.5;last=dens; - if(d.x>=distance)cd*=d.z/d.w;if(cd>0)d.y=d.y*(1-fx.z)+fx.z*d.x; - fx.y+=cd;fx.z=(exp(-fx.y)-.2)/.8;d.z=distance-d.x;if(fx.y<2.3)fx.x+=cd*exp(-fx.y);} - d.w=clampMap(2*df-pdf,cs.z*.85,0,5,75);d.w*=clampMap(d.x,0,2000000,1,500); - d.w+=noise2d(p+g_Time)*5;pdf=df;p+=dir*d.w;d.x+=d.w;} - if(fx.z<1){fx=saturate(fx);float3 cbright=light*(.5/LightingDecay); - float3 C=cbright*fx.x*MiePhase(dot(lightDir,dir))+(b.BaseColor*g_vCloudBaseColor)*(1-fx.z); - distance=distance*fx.z+d.y*(1-fx.z);return float4(C,fx.z);}} - return float4(0,0,0,1);} - -CloudProfile BuildProfile0(float time,float coverage){CloudProfile p=(CloudProfile)0; - p.march=float4(5,80,8,64);p.cutoff=float2(0,.2);p.volumeBox=float2(900,500);p.shape=float4(900,650,0,0);p.brightness=.5; - p.range=float3(.9+coverage*.16,.1,.2+coverage*.4);p.solidness=float2(5,0)*coverage;p.densityChunk=float2(.3,.5); - p.shadow=float4(60,1.75,.1,.03);p.distortion=float4(1.6,60,8,16);p.fade=6000;p.densityDetail=float3(.3,.2,.6); - p.scaleChunk=float3(.0008,.005,1);p.scaleDetail=float3(.02,.04,.1);p.cloudShift=float3(-.5,0,0); - p.offsetA=float3(1.8,-1,0)*-time;p.offsetB=float3(2,.2,0)*-time;p.offsetC=float3(3,0,.5)*-time;p.offsetD=float3(3.5,0,-.1)*-time; - float grow=noise3d(float3(p.shape.x,p.volumeBox.y,time/2000))*.45+.65;p.range.x*=grow*(1-coverage)+coverage;return p;} -CloudProfile BuildProfile1(float time,float coverage){CloudProfile p=(CloudProfile)0; - p.march=float4(12,70,8,64);p.cutoff=float2(0,.2);p.volumeBox=float2(1900,1500);p.shape=float4(2100,1650,0,0);p.brightness=.5; - p.range=float3(.85+coverage*.78,0,.3+coverage*.16);p.solidness=float2(.35,.1)*coverage;p.densityChunk=float2(.25,.6); - p.shadow=float4(30,1,.15,.1);p.distortion=float4(6,50,100,50);p.fade=20000;p.densityDetail=float3(.5,.25,.5); - p.scaleChunk=float3(.0008,.004,1.5);p.scaleDetail=float3(.0142857,.0285714,.08);p.cloudShift=0; - p.offsetA=float3(1.5,-1.2,0)*-time;p.offsetB=float3(1.9,.5,0)*-time;p.offsetC=float3(2.5,0,.5)*-time;p.offsetD=float3(3,.1,-.1)*-time; - float grow=noise3d(float3(p.shape.x,p.volumeBox.y,time/2000))*.45+.65;p.range.x*=grow*(1-coverage)+coverage;return p;} -CloudProfile BuildProfile2(float time,float coverage){CloudProfile p=(CloudProfile)0; - p.march=float4(5,75,500,50);p.cutoff=float2(0,.2);p.volumeBox=float2(3600,3500);p.shape=float4(3800,3520,3450,0);p.brightness=.5; - p.range=float3(1+coverage*.9,.2,.35);p.solidness=float2(.25,0);p.densityChunk=float2(.4,.3); - p.shadow=float4(50,1.5,.02,.1);p.distortion=float4(2.5,15000,0,0);p.fade=2000000;p.densityDetail=float3(.2,.1,.6); - p.scaleChunk=float3(.00016,.0008,1.5);p.scaleDetail=float3(.004,.006667,.02);p.cloudShift=0; - p.offsetA=float3(1.3,-1.8,0)*-time;p.offsetB=float3(1.6,.8,0)*-time;p.offsetC=float3(2.5,.2,.5)*-time;p.offsetD=float3(3,.1,-.1)*-time; - float grow=noise3d(float3(p.shape.x,p.volumeBox.y,time/2000))*.45+.65;p.range.x*=grow*(1-coverage)+coverage;return p;} -CloudBaseColor GetCloudsColor(float3 sunDir){CloudBaseColor b=(CloudBaseColor)0;b.BaseColor=.2;b.BaseColor_Day=.2;b.BaseColor_Sunset=.2; - float night=smoothstep(.3,.1,fDayProgression),day=smoothstep(-.03,.05,fDayProgression),sunset=night*day; - b.BaseColor+=lerp(b.BaseColor_Day,b.BaseColor_Sunset,sunset)*day;return b;} -float4 RenderClouds(float3 dir,float3 cam){float time=gameTime();CloudBaseColor base=GetCloudsColor(vSunLightDir); - float3 lightDir=normalize(vSunLightDir);float3 light=LightSource(lightDir,fDayProgression,g_vSunColor,lightDir);float distance=100000; - float4 result=CloudAtRay(BuildProfile0(time,g_CloudCoverage.x),base,dir,cam,light,lightDir,time,distance); - uint layers=g_Settings&3u;if(layers>=3u&&result.w>.01){float4 mid=CloudAtRay(BuildProfile1(time,g_CloudCoverage.y),base,dir,cam,light,lightDir,time,distance);result.rgb+=mid.rgb*result.w;result.w*=mid.w;} - if(layers>=2u&&result.w>.01){float4 high=CloudAtRayHighLite(base,dir,cam,light,lightDir,time,distance);result.rgb+=high.rgb*result.w;result.w*=high.w;} - return result;} -float3 WorldRay(float2 uv){float2 ndc=float2(uv.x*2-1,1-uv.y*2);return normalize(CloudRayForward+CloudRayRight*ndc.x+CloudRayUp*ndc.y);} - -static const float VC_TEMPORAL_DIV=2.0; -static const float VC_INV_TEMPORAL_DIV=0.5; -static const float VC_RAIL_FLOOR=4.0; - -// A sparse march target contains one texel for each 2x2 block of the resolved -// cloud history. SV_POSITION identifies the sparse texel exactly; deriving UV -// from it avoids interpolation and half-pixel disagreements between APIs. -float2 CloudMarchUV(float2 position){ - float2 pixel=floor(position); - float2 sparseUV=(pixel*VC_TEMPORAL_DIV+CloudSubPixel+0.5)*CloudTexelSize; - float2 fullUV=(pixel+0.5)*CloudTexelSize; - return lerp(sparseUV,fullUV,saturate(CloudFullResolutionMarch)); -} - -// History stores CloudWorks' native representation: premultiplied scattered -// light in rgb and TRANSMITTANCE in alpha (clear sky is 0,0,0,1). -float4 CloudMarchPS(PresentVertexOutput i):SV_TARGET { - if((g_Settings&0x10000u)==0u)return float4(0,0,0,1); - float3 dir=WorldRay(CloudMarchUV(i.position.xy)); - return RenderClouds(dir,CloudCameraPosition); -} - -// Kept as an entry-point alias while the backend moves from its former direct -// cloud target to the full/sparse march paths. -float4 CloudTargetPS(PresentVertexOutput i):SV_TARGET { - return CloudMarchPS(i); -} - -// Reproject a current world direction into the previous camera's ray basis. -// If d = q.x*R + q.y*U + q.z*F, previous NDC is q.xy/q.z. Cramer's rule keeps -// this exact for asymmetric projections and avoids a separate 4x4 matrix. -float3 PreviousCloudNdc(float3 dir){ - float det=dot(PrevCloudRayRight,cross(PrevCloudRayUp,PrevCloudRayForward)); - float safeDet=(abs(det)>1e-8)?det:((det<0)?-1e-8:1e-8); - float3 q=float3( - dot(dir,cross(PrevCloudRayUp,PrevCloudRayForward)), - dot(PrevCloudRayRight,cross(dir,PrevCloudRayForward)), - dot(PrevCloudRayRight,cross(PrevCloudRayUp,dir)))/safeDet; - return float3(q.xy/max(q.z,1e-8),q.z); -} - -float CloudSlabEntry(float cameraZ,float rayZ,float bottom,float top){ - if(abs(rayZ)<1e-6)return 50000.0; - if(cameraZ>=bottom&&cameraZ<=top)return 0.0; - float a=(bottom-cameraZ)/rayZ,b=(top-cameraZ)/rayZ; - float entry=min(a,b),leave=max(a,b); - return leave<0.0?50000.0:max(entry,0.0); -} - -// Reproject translation as well as rotation. CloudPadding carries the previous -// camera position. A world point on the nearest enabled physical cloud slab is -// stable under the orbiting VCS third-person camera, unlike a direction-only -// reprojection that forces the history to reset whenever the camera moves. -float3 PreviousCloudDirection(float2 uv){ - float3 dir=WorldRay(uv); - float distance=CloudSlabEntry(CloudCameraPosition.z,dir.z,500.0,900.0); - if((g_Settings&3u)>=2u) - distance=min(distance,CloudSlabEntry(CloudCameraPosition.z,dir.z,3500.0,3600.0)); - if((g_Settings&3u)>=3u) - distance=min(distance,CloudSlabEntry(CloudCameraPosition.z,dir.z,1500.0,1900.0)); - distance=min(max(distance,1.0),50000.0); - float3 worldPoint=CloudCameraPosition+dir*distance; - return normalize(worldPoint-CloudPadding); -} - -// Literal SM5 adaptation of ProperShaders' PS_TemporalResolve. The march fills -// one Bayer slot per 2x2 block; every other pixel keeps its own reprojected -// history and leaks slightly toward the smooth current-frame reconstruction. -float4 CloudTemporalResolvePS(PresentVertexOutput i):SV_TARGET { - float2 pixel=floor(i.position.xy); - float2 uv=(pixel+0.5)*CloudTexelSize; - float2 marchTexel=CloudTexelSize*VC_TEMPORAL_DIV; - - float2 block=floor(pixel*VC_INV_TEMPORAL_DIV); - float2 slot=pixel-block*VC_TEMPORAL_DIV; - float2 blockUV=(block+0.5)*marchTexel; - float4 c=CloudTexture0.SampleLevel(CloudSampler,blockUV,0.0); - - float2 freshDelta=abs(slot-CloudSubPixel); - float fresh=step(freshDelta.x+freshDelta.y,0.5); - - float2 spatialUV=uv-(CloudSubPixel-(VC_TEMPORAL_DIV-1.0)*0.5)*CloudTexelSize; - float2 sp=spatialUV/marchTexel-0.5; - float2 sf=frac(sp); - float2 sb=(floor(sp)+0.5)*marchTexel; - float4 t00=CloudTexture0.SampleLevel(CloudSampler,sb,0.0); - float4 t10=CloudTexture0.SampleLevel(CloudSampler,sb+float2(marchTexel.x,0),0.0); - float4 t01=CloudTexture0.SampleLevel(CloudSampler,sb+float2(0,marchTexel.y),0.0); - float4 t11=CloudTexture0.SampleLevel(CloudSampler,sb+marchTexel,0.0); - float4 spatial=lerp(lerp(t00,t10,sf.x),lerp(t01,t11,sf.x),sf.y); - - float3 previous=PreviousCloudNdc(PreviousCloudDirection(uv)); - float2 prevUV=float2(previous.x*0.5+0.5,0.5-previous.y*0.5); - float2 inside=step(float2(0,0),prevUV)*step(prevUV,float2(1,1)); - float valid=CloudHistoryValid*inside.x*inside.y*step(1e-8,previous.z); - float4 hist=CloudTexture1.SampleLevel(CloudSampler,prevUV,0.0); - - // Loose safety rail from ProperShaders. RGB and transmittance must move as a - // single value; clamping channels independently creates black pinholes. - float4 mn=min(min(min(t00,t10),min(t01,t11)),c); - float4 mx=max(max(max(t00,t10),max(t01,t11)),c); - float4 mid=(mn+mx)*0.5; - float3 spanRGB=mx.rgb-mn.rgb; - float span=max(max(spanRGB.r,spanRGB.g),max(spanRGB.b,mx.a-mn.a)); - float ext=max(span*CloudClampExpand,VC_RAIL_FLOOR); - float4 dev=abs(hist-mid)-ext; - float over=max(max(dev.r,dev.g),max(dev.b,dev.a)); - hist=lerp(hist,mid,saturate(over)); - - float4 src=lerp(spatial,c,fresh); - float w=lerp(1.0-CloudSpatialMix,CloudTemporalBlend,fresh)*valid; - return lerp(src,hist,w); -} - -// Manual four-tap bilinear upscale matches ProperShaders even if the bound -// sampler is point-filtered. The backend blends this premultiplied result over -// the world target with ONE / INV_SRC_ALPHA. -float4 CloudCompositePS(PresentVertexOutput i):SV_TARGET { - float2 texelPos=i.uv/CloudTexelSize-0.5; - float2 f=frac(texelPos); - float2 base=(floor(texelPos)+0.5)*CloudTexelSize; - float4 c00=CloudTexture0.SampleLevel(CloudSampler,base,0.0); - float4 c10=CloudTexture0.SampleLevel(CloudSampler,base+float2(CloudTexelSize.x,0),0.0); - float4 c01=CloudTexture0.SampleLevel(CloudSampler,base+float2(0,CloudTexelSize.y),0.0); - float4 c11=CloudTexture0.SampleLevel(CloudSampler,base+CloudTexelSize,0.0); - float4 clouds=lerp(lerp(c00,c10,f.x),lerp(c01,c11,f.x),f.y); - float alpha=saturate((1.0-clouds.a)*g_Opacity); - return float4(clouds.rgb*g_Opacity*g_Brightness,alpha); -} -)CLOUD_HLSL"; - -} // namespace vcs diff --git a/profiles/vcs/host/ge_gpu_backend.hpp b/profiles/vcs/host/ge_gpu_backend.hpp index ab73559..c49c9aa 100644 --- a/profiles/vcs/host/ge_gpu_backend.hpp +++ b/profiles/vcs/host/ge_gpu_backend.hpp @@ -14,10 +14,18 @@ enum class GeGpuBackendKind : std::uint8_t { DirectX12, }; +enum class GeShaderPipe : std::uint8_t { + Native = 0, + Building = 1, + Skin = 2, + Vehicle = 3, +}; + struct GeGpuDrawDescriptor { std::uint32_t primitive{}; std::uint32_t vertex_count{}; std::uint32_t vertex_type{}; + GeShaderPipe shader_pipe{GeShaderPipe::Native}; // GE render target for this draw. Stage 33 uses it to exclude prelight, // reflection and other offscreen passes from the presented framebuffer. std::uint32_t framebuffer_address{}; @@ -122,6 +130,9 @@ struct GeGpuDrawDescriptor { // triangles. The backend folds viewport/composition into model_to_clip once per // draw and pushes the final constants to the vertex shader. struct GeGpuHardwareTransform { + // Kept separately from model_to_clip for private material/shadow passes. + // Lit geometry that has already been transformed to world space stores identity. + std::array model_to_world{}; std::array model_to_clip{}; std::array model_to_view_z{}; float viewport_scale_x{}; @@ -430,7 +441,7 @@ void ge_gpu_backend_record_draw(const GeGpuDrawDescriptor &draw) noexcept; // The matrices are observed with the draw, before the GE state advances. void ge_gpu_backend_observe_camera(const std::array &view, const std::array &projection, - const std::array &viewport, + const std::array &viewport, const std::array &camera_position, const GeGpuDrawDescriptor &draw, std::uint32_t vertex_weight) noexcept; diff --git a/profiles/vcs/host/ge_gpu_backend_dx12.cpp b/profiles/vcs/host/ge_gpu_backend_dx12.cpp index fb4bb2d..75dc3eb 100644 --- a/profiles/vcs/host/ge_gpu_backend_dx12.cpp +++ b/profiles/vcs/host/ge_gpu_backend_dx12.cpp @@ -1,7 +1,8 @@ #include "ge_gpu_backend.hpp" #include "ge_cloud_camera_math.hpp" -#include "ge_cloudworks_present_shader.hpp" +#include "propershaders/ProperShadersBridge.hpp" #include "vcs_config.hpp" +#include "vcs_draw_distance_patch.hpp" #include "vcs_runtime_log.hpp" #include @@ -10,9 +11,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -150,10 +153,10 @@ static_assert(sizeof(Dx12IndirectDrawIndexedCommand) == 200u); struct CloudCameraCandidate { std::array view{}; std::array projection{}; - // scale X/Y, center X/Y and offset X/Y from the GE viewport. Keeping this - // with the exact draw camera lets the present shader reconstruct the same - // rays that the native geometry path rasterized. - std::array viewport{}; + // scale X/Y/Z, center X/Y/Z and offset X/Y from the GE viewport. Keeping + // this with the exact draw camera lets private full-screen effects rebuild + // the same effective projection/depth range used by native geometry. + std::array viewport{}; std::array camera_position{}; std::uint32_t target{}; std::uint64_t weight{}; @@ -231,8 +234,15 @@ struct Dx12FramebufferTarget { std::uint32_t dsv_index{}; std::uint32_t srv_index{}; std::uint32_t feedback_srv_index{}; + // Optional shader-readable view of the native world depth buffer. Two + // contiguous descriptors are reserved because the shared fullscreen root + // signature exposes t0..t1. Only the private realtime-shadow module uses + // them; public builds keep this at zero and pay no descriptor/resource + // state cost beyond the normal DSV. + std::uint32_t depth_srv_base{}; D3D12_RESOURCE_STATES color_state{D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE}; D3D12_RESOURCE_STATES msaa_state{D3D12_RESOURCE_STATE_RENDER_TARGET}; + D3D12_RESOURCE_STATES depth_state{D3D12_RESOURCE_STATE_DEPTH_WRITE}; D3D12_RESOURCE_STATES feedback_state{D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE}; std::uint64_t last_render_epoch{}; }; @@ -251,6 +261,15 @@ struct CloudRenderTarget { D3D12_RESOURCE_STATES state{D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE}; }; +struct ShadowMapTarget { + ComPtr depth; + std::uint32_t dsv_index{}; + std::uint32_t srv_index{}; + std::uint32_t resolution{}; + std::uint32_t caster_draws{}; + D3D12_RESOURCE_STATES state{D3D12_RESOURCE_STATE_DEPTH_WRITE}; +}; + struct Dx12GeState { GeGpuBackendReport report{}; bool enabled{}; @@ -309,11 +328,25 @@ struct Dx12GeState { UINT64 next_fence{1u}; ComPtr root_signature; ComPtr cloud_root_signature; + ComPtr realtime_shadow_caster_root_signature; + ComPtr realtime_shadow_composite_root_signature; ComPtr indirect_draw_signature; ComPtr indirect_draw_indexed_signature; ComPtr vertex_shader; ComPtr packed_0115_vertex_shader; ComPtr pixel_shader; + ComPtr building_vertex_shader; + ComPtr building_packed_0115_vertex_shader; + ComPtr building_pixel_shader; + ComPtr skin_vertex_shader; + ComPtr skin_packed_0115_vertex_shader; + ComPtr skin_pixel_shader; + ComPtr vehicle_vertex_shader; + ComPtr vehicle_packed_0115_vertex_shader; + ComPtr vehicle_pixel_shader; + ComPtr realtime_shadow_caster_vertex_shader; + ComPtr realtime_shadow_caster_packed_vertex_shader; + ComPtr realtime_shadow_pixel_shader; std::unordered_map> pipelines; std::unordered_map textures; // Hot draw streams reuse the same texture for long runs. Avoid repeating an @@ -341,6 +374,9 @@ struct Dx12GeState { ComPtr cloud_target_pipeline; ComPtr cloud_resolve_pipeline; ComPtr cloud_composite_pipeline; + ComPtr realtime_shadow_caster_pipeline; + ComPtr realtime_shadow_caster_packed_pipeline; + ComPtr realtime_shadow_pipeline; ComPtr present_vertex_shader; ComPtr present_pixel_shader; ComPtr cloud_target_pixel_shader; @@ -348,6 +384,10 @@ struct Dx12GeState { ComPtr cloud_composite_pixel_shader; std::array cloud_history; CloudRenderTarget cloud_march; + ShadowMapTarget realtime_shadow_map; + // Optional private effects must never decide whether the native GE backend + // itself lives. This latch becomes true only after shadow HLSL compiled. + bool realtime_shadows_available{}; std::array cloud_resolve_srv_base{}; std::uint32_t cloud_history_index{}; std::uint32_t cloud_temporal_frame{}; @@ -760,6 +800,30 @@ DXGI_FORMAT requested_depth_format(std::uint32_t bits) noexcept { return DXGI_FORMAT_D32_FLOAT; } +DXGI_FORMAT typeless_depth_resource_format(DXGI_FORMAT dsv_format) noexcept { + switch (dsv_format) { + case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_TYPELESS; + case DXGI_FORMAT_D24_UNORM_S8_UINT: return DXGI_FORMAT_R24G8_TYPELESS; + case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_TYPELESS; + default: return dsv_format; + } +} + +DXGI_FORMAT depth_srv_format(DXGI_FORMAT dsv_format) noexcept { + switch (dsv_format) { + case DXGI_FORMAT_D16_UNORM: return DXGI_FORMAT_R16_UNORM; + case DXGI_FORMAT_D24_UNORM_S8_UINT: return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + case DXGI_FORMAT_D32_FLOAT: return DXGI_FORMAT_R32_FLOAT; + default: return DXGI_FORMAT_UNKNOWN; + } +} + +bool realtime_shadows_requested() noexcept { + const auto &proper = vcs_configuration().proper_shaders; + return proper.enabled && proper.realtime_shadows.enabled && + proper_shaders_private_available(); +} + bool format_supports_depth(ID3D12Device *device, DXGI_FORMAT format) noexcept { if (device == nullptr) return false; D3D12_FEATURE_DATA_FORMAT_SUPPORT support{}; @@ -1097,17 +1161,20 @@ std::uint64_t pipeline_key(const GeGpuDrawDescriptor &draw) noexcept { key |= static_cast(static_cast(blend.op_alpha) & 0x7u) << 30u; key |= static_cast(color_write_mask(draw) & 0xFu) << 33u; key |= static_cast(blend.uses_constant ? 1u : 0u) << 37u; + key |= static_cast(static_cast(draw.shader_pipe) & 3u) << 38u; return key; } bool hardware_transform_equal(const GeGpuHardwareTransform &a, const GeGpuHardwareTransform &b) noexcept { - // Only fields consumed by make_transform_constants() participate. The - // trailing accounting counters deliberately do not: two adjacent PRIMs can + // Fields consumed by the native draw constants *or* the private shadow + // caster participate. The trailing accounting counters deliberately do not: + // two adjacent PRIMs can // have different logical batching statistics while still sharing identical // shader constants. Explicit field comparison avoids relying on struct // padding with memcmp. - return a.model_to_clip == b.model_to_clip && + return a.model_to_world == b.model_to_world && + a.model_to_clip == b.model_to_clip && a.model_to_view_z == b.model_to_view_z && a.viewport_scale_x == b.viewport_scale_x && a.viewport_scale_y == b.viewport_scale_y && @@ -1312,6 +1379,105 @@ bool append_or_merge_batch(Dx12GeState &s, Dx12Batch batch) { return false; } + +void promote_vehicle_batch_groups(Dx12GeState &s) noexcept { + const auto &proper = vcs_configuration().proper_shaders; + if (!proper.enabled || !proper.vehicle_pipe.enabled || + !proper_shaders_private_available() || s.batches.empty()) + return; + + // The palette classifier gives us high-confidence anchor materials, but a + // RenderWare vehicle is an atomic/group with wheels, glass, lights and trim + // that often use non-paletted textures. Promote the *contiguous entity run* + // around each anchor. Spatial/model-transform checks keep static world + // batches from being swallowed by a nearby car. + std::vector anchors; + anchors.reserve(32u); + for (std::size_t i = 0u; i < s.batches.size(); ++i) { + if (s.batches[i].draw.shader_pipe == GeShaderPipe::Vehicle) + anchors.push_back(i); + } + if (anchors.empty()) return; + + const auto origin = [](const Dx12Batch &batch) { + return std::array{batch.transform.model_to_world[12], + batch.transform.model_to_world[13], + batch.transform.model_to_world[14]}; + }; + const auto finite_origin = [](const std::array &v) { + return std::isfinite(v[0]) && std::isfinite(v[1]) && std::isfinite(v[2]); + }; + const auto distance2 = [](const std::array &a, + const std::array &b) { + const float x = a[0] - b[0], y = a[1] - b[1], z = a[2] - b[2]; + return x * x + y * y + z * z; + }; + const auto approximately_identity_origin = [](const std::array &v) { + return std::fabs(v[0]) < 1.0e-4f && std::fabs(v[1]) < 1.0e-4f && + std::fabs(v[2]) < 1.0e-4f; + }; + + std::uint32_t promoted = 0u; + constexpr std::size_t kMaxNeighbourDraws = 18u; + constexpr float kMaxVehiclePartDistance2 = 14.0f * 14.0f; + for (const std::size_t anchor_index : anchors) { + if (anchor_index >= s.batches.size()) continue; + const Dx12Batch &anchor = s.batches[anchor_index]; + if (!anchor.hardware_transform) continue; + const std::uint32_t target = anchor.draw.framebuffer_address & 0x001FFFF0u; + const auto anchor_origin = origin(anchor); + const bool anchor_origin_valid = finite_origin(anchor_origin); + const bool anchor_identity_origin = approximately_identity_origin(anchor_origin); + + const auto candidate_matches = [&](const Dx12Batch &candidate, + std::size_t distance_in_draws) { + if (!candidate.hardware_transform || candidate.draw.clear_mode || + candidate.framebuffer_feedback || candidate.draw.through) + return false; + if ((candidate.draw.framebuffer_address & 0x001FFFF0u) != target) + return false; + if (candidate.draw.shader_pipe == GeShaderPipe::Skin || + candidate.draw.shader_pipe == GeShaderPipe::Native) + return false; + if (candidate.draw.depth_test_enabled != anchor.draw.depth_test_enabled || + candidate.draw.depth_write_enabled != anchor.draw.depth_write_enabled) + return false; + const auto candidate_origin = origin(candidate); + if (!anchor_origin_valid || !finite_origin(candidate_origin)) return false; + const bool candidate_identity = approximately_identity_origin(candidate_origin); + if (!anchor_identity_origin || !candidate_identity) + return distance2(anchor_origin, candidate_origin) <= kMaxVehiclePartDistance2; + // Some VCS entity draws arrive pretransformed in world space and + // therefore carry identity model_to_world. In that case spatial + // origin cannot distinguish them; keep promotion intentionally + // narrow and contiguous around the high-confidence palette anchor. + return distance_in_draws <= 12u; + }; + + for (int direction : {-1, 1}) { + for (std::size_t step = 1u; step <= kMaxNeighbourDraws; ++step) { + const std::int64_t signed_index = static_cast(anchor_index) + + static_cast(direction) * static_cast(step); + if (signed_index < 0 || + signed_index >= static_cast(s.batches.size())) + break; + Dx12Batch &candidate = s.batches[static_cast(signed_index)]; + if (!candidate_matches(candidate, step)) break; + if (candidate.draw.shader_pipe == GeShaderPipe::Building) { + candidate.draw.shader_pipe = GeShaderPipe::Vehicle; + ++promoted; + } + } + } + } + + if (promoted != 0u && (s.frame_epoch < 8u || (s.frame_epoch % 120u) == 0u)) { + runtime_log_line("ProperShaders vehicle entity promotion anchors=" + + std::to_string(anchors.size()) + " promoted_parts=" + + std::to_string(promoted)); + } +} + bool compile_shaders(Dx12GeState &s, std::string &error) noexcept { const char *shader = R"HLSL( Texture2D SourceTexture : register(t0); @@ -1334,16 +1500,17 @@ cbuffer DrawPixelState : register(b1) { uint TextureEnvPacked; uint FogControlPacked; uint FramebufferFormat; - float3 CloudRight; - float CloudInvProjectionX; - float3 CloudUp; - float CloudInvProjectionY; - float3 CloudCameraPosition; - float CloudTime; - float CloudCoverage; - float CloudOpacity; - float CloudMarchSteps; - float CloudEnabled; + // The remaining 16 DWORDs are private-material scratch constants. Their + // scalar/float3 packing deliberately starts at c1.y so all 21 root DWORDs + // remain densely addressable without cbuffer alignment holes. + float3 ProperShadowRow0XYZ; + float ProperShadowRow0W; + float3 ProperShadowRow1XYZ; + float ProperShadowRow1W; + float3 ProperShadowRow2XYZ; + float ProperShadowRow2W; + float3 ProperShadowRow3XYZ; + float ProperShadowRow3W; }; struct VSIn { float4 position : POSITION; @@ -1553,7 +1720,102 @@ float4 PSMain(VSOut input) : SV_TARGET { return false; } - const char *present = kCloudWorksPresentShaderHlsl; + if (proper_shaders_private_available() && vcs_configuration().proper_shaders.enabled) { + const auto compile_pipe = [&](GeShaderPipe pipe, + bool material_shadows, + ComPtr &vs_blob, + ComPtr &packed_vs_blob, + ComPtr &ps_blob, + const char *name) -> bool { + const std::string source = proper_shaders_make_pipe_hlsl(pipe, shader, material_shadows); + // Private material variants are optional. Do not turn conservative FXC + // warnings into a hard pipe shutdown; the native GE shader keeps the + // stricter warning policy above. + constexpr UINT pipe_flags = D3DCOMPILE_OPTIMIZATION_LEVEL3; + errors.Reset(); + HRESULT pipe_hr = D3DCompile(source.data(), source.size(), name, nullptr, nullptr, + "VSMain", "vs_5_1", pipe_flags, 0u, &vs_blob, &errors); + if (FAILED(pipe_hr)) { + error = errors ? std::string(static_cast(errors->GetBufferPointer()), errors->GetBufferSize()) + : hr_text(pipe_hr, name); + return false; + } + errors.Reset(); + pipe_hr = D3DCompile(source.data(), source.size(), name, nullptr, nullptr, + "VSMainPacked0115", "vs_5_1", pipe_flags, 0u, + &packed_vs_blob, &errors); + if (FAILED(pipe_hr)) { + error = errors ? std::string(static_cast(errors->GetBufferPointer()), errors->GetBufferSize()) + : hr_text(pipe_hr, name); + return false; + } + errors.Reset(); + pipe_hr = D3DCompile(source.data(), source.size(), name, nullptr, nullptr, + "PSMain", "ps_5_1", pipe_flags, 0u, &ps_blob, &errors); + if (FAILED(pipe_hr)) { + error = errors ? std::string(static_cast(errors->GetBufferPointer()), errors->GetBufferSize()) + : hr_text(pipe_hr, name); + return false; + } + return true; + }; + const auto compile_optional_pipe = [&](GeShaderPipe pipe, + ComPtr &vs_blob, + ComPtr &packed_vs_blob, + ComPtr &ps_blob, + const char *name) { + std::string shadow_error; + if (compile_pipe(pipe, true, vs_blob, packed_vs_blob, ps_blob, name)) { + runtime_log_line(std::string("ProperShaders pipe compiled: ") + name + + " shadow_receiver=1"); + return; + } + shadow_error = error; + error.clear(); + vs_blob.Reset(); packed_vs_blob.Reset(); ps_blob.Reset(); + + // A shadow receiver must never be allowed to kill a working material + // pipe. Retry the same Building/Skin/Vehicle debug/custom shader with + // the shadow extension completely absent. This reproduces the last + // known-good pipe architecture if the receiver integration regresses. + if (compile_pipe(pipe, false, vs_blob, packed_vs_blob, ps_blob, name)) { + runtime_log_error(std::string("ProperShaders shadow receiver disabled; pipe recovered: ") + name, + shadow_error); + runtime_log_line(std::string("ProperShaders pipe compiled fallback: ") + name + + " shadow_receiver=0"); + return; + } + const std::string fallback_error = error; + error.clear(); + vs_blob.Reset(); packed_vs_blob.Reset(); ps_blob.Reset(); + runtime_log_error(std::string("optional ProperShaders pipe disabled after shadow-free retry: ") + name, + fallback_error); + }; + compile_optional_pipe(GeShaderPipe::Building, + s.building_vertex_shader, s.building_packed_0115_vertex_shader, + s.building_pixel_shader, "VCSBuildingPipe"); + compile_optional_pipe(GeShaderPipe::Skin, + s.skin_vertex_shader, s.skin_packed_0115_vertex_shader, + s.skin_pixel_shader, "VCSSkinPipe"); + compile_optional_pipe(GeShaderPipe::Vehicle, + s.vehicle_vertex_shader, s.vehicle_packed_0115_vertex_shader, + s.vehicle_pixel_shader, "VCSVehiclePipe"); + } + + const char *present = proper_shaders_volumetric_clouds_hlsl(); + if (present == nullptr) { + // Public build: keep the native fullscreen copy shader self-contained. + present = R"HLSL( +Texture2D CloudTexture0 : register(t0); +SamplerState CloudSampler : register(s0); +struct PresentVertexOutput { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; +PresentVertexOutput PresentVS(uint id : SV_VertexID) { PresentVertexOutput o; if(id==0){o.position=float4(-1,-1,0,1);o.uv=float2(0,1);} else if(id==1){o.position=float4(-1,3,0,1);o.uv=float2(0,-1);} else{o.position=float4(3,-1,0,1);o.uv=float2(2,1);} return o; } +float4 PresentPS(PresentVertexOutput i):SV_TARGET { return CloudTexture0.SampleLevel(CloudSampler,i.uv,0.0); } +float4 CloudMarchPS(PresentVertexOutput i):SV_TARGET { return float4(0,0,0,1); } +float4 CloudTemporalResolvePS(PresentVertexOutput i):SV_TARGET { return float4(0,0,0,1); } +float4 CloudCompositePS(PresentVertexOutput i):SV_TARGET { return float4(0,0,0,0); } +)HLSL"; + } errors.Reset(); hr = D3DCompile(present, std::strlen(present), "VCSNativeDX12GEPresent", nullptr, nullptr, "PresentVS", "vs_5_1", flags, 0u, &s.present_vertex_shader, &errors); @@ -1606,6 +1868,52 @@ float4 PSMain(VSOut input) : SV_TARGET { : hr_text(hr, "D3DCompile(DX12 GE cloud composite PS)"); return false; } + s.realtime_shadows_available = false; + if (realtime_shadows_requested()) { + const char *shadow = proper_shaders_realtime_shadows_hlsl(); + if (shadow != nullptr) { + // This is an optional effect. FXC warnings in the private shader + // must not demote the entire native GE to the software backend. + const UINT shadow_flags = D3DCOMPILE_OPTIMIZATION_LEVEL3; + std::string shadow_error; + bool shadow_ok = true; + errors.Reset(); + hr = D3DCompile(shadow, std::strlen(shadow), "VCSNativeDX12GEShadowCaster", + nullptr, nullptr, "ShadowCasterVS", "vs_5_1", shadow_flags, 0u, + &s.realtime_shadow_caster_vertex_shader, &errors); + if (FAILED(hr)) { + shadow_error = errors ? std::string(static_cast(errors->GetBufferPointer()), + errors->GetBufferSize()) + : hr_text(hr, "D3DCompile(DX12 GE shadow caster VS)"); + shadow_ok = false; + } + if (shadow_ok) { + errors.Reset(); + hr = D3DCompile(shadow, std::strlen(shadow), "VCSNativeDX12GEShadowCasterPacked", + nullptr, nullptr, "ShadowCasterPackedVS", "vs_5_1", shadow_flags, 0u, + &s.realtime_shadow_caster_packed_vertex_shader, &errors); + if (FAILED(hr)) { + shadow_error = errors ? std::string(static_cast(errors->GetBufferPointer()), + errors->GetBufferSize()) + : hr_text(hr, "D3DCompile(DX12 GE packed shadow caster VS)"); + shadow_ok = false; + } + } + // The old fullscreen ShadowPS/contact-composite path is deliberately + // not compiled. Realtime shadows now consist of a depth-only caster + // pass plus PCF sampling inside the actual material shaders. + s.realtime_shadow_pixel_shader.Reset(); + if (shadow_ok) { + s.realtime_shadows_available = true; + runtime_log_line("ProperShaders realtime shadow HLSL compiled"); + } else { + s.realtime_shadow_caster_vertex_shader.Reset(); + s.realtime_shadow_caster_packed_vertex_shader.Reset(); + s.realtime_shadow_pixel_shader.Reset(); + runtime_log_error("optional ProperShaders realtime shadows disabled", shadow_error); + } + } + } return true; } @@ -1622,7 +1930,13 @@ bool create_root_signature(Dx12GeState &s, std::string &error) noexcept { sampler_range.BaseShaderRegister = 0u; sampler_range.RegisterSpace = 0u; sampler_range.OffsetInDescriptorsFromTableStart = 0u; - std::array parameters{}; + D3D12_DESCRIPTOR_RANGE shadow_srv_range{}; + shadow_srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + shadow_srv_range.NumDescriptors = 1u; + shadow_srv_range.BaseShaderRegister = 1u; + shadow_srv_range.RegisterSpace = 0u; + shadow_srv_range.OffsetInDescriptorsFromTableStart = 0u; + std::array parameters{}; parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; parameters[0].DescriptorTable.NumDescriptorRanges = 1u; parameters[0].DescriptorTable.pDescriptorRanges = &srv_range; @@ -1642,11 +1956,18 @@ bool create_root_signature(Dx12GeState &s, std::string &error) noexcept { parameters[3].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; parameters[3].Constants.ShaderRegister = 1u; parameters[3].Constants.RegisterSpace = 0u; - // 5 draw-state DWORDs plus 16 cloud-present DWORDs. Together with the - // 40-DWORD vertex transform and two descriptor tables this uses 63 of the - // D3D12 root signature's 64 DWORD budget. + // 5 draw-state DWORDs plus 16 private material DWORDs (used by the + // model->light matrix when realtime shadows are enabled). Visibility ALL + // lets the pipe VS consume those rows while the stock PS still sees the + // original first five values. parameters[3].Constants.Num32BitValues = 21u; - parameters[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + parameters[3].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + // t1 is a dedicated material-shadow SRV. A descriptor table costs one root + // DWORD, bringing this root signature to exactly the D3D12 64-DWORD limit. + parameters[4].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + parameters[4].DescriptorTable.NumDescriptorRanges = 1u; + parameters[4].DescriptorTable.pDescriptorRanges = &shadow_srv_range; + parameters[4].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; D3D12_ROOT_SIGNATURE_DESC desc{}; desc.NumParameters = static_cast(parameters.size()); desc.pParameters = parameters.data(); @@ -1755,6 +2076,70 @@ bool create_targets(Dx12GeState &s, std::string &error) noexcept { default_sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; s.device->CreateSampler(&default_sampler, sampler_cpu(s, 0u)); + if (s.realtime_shadows_available) { + if (s.next_dsv >= kFramebufferTargetCapacity || s.next_srv >= kSrvCapacity) { + runtime_log_error("optional ProperShaders shadow map disabled", + "DX12 GE descriptor capacity exhausted"); + s.realtime_shadows_available = false; + } else { + const auto &shadow_config = vcs_configuration().proper_shaders.realtime_shadows; + ShadowMapTarget shadow{}; + shadow.resolution = std::clamp(shadow_config.map_resolution, 512u, 4096u); + shadow.dsv_index = s.next_dsv++; + shadow.srv_index = s.next_srv++; + + D3D12_RESOURCE_DESC shadow_desc{}; + shadow_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + shadow_desc.Width = shadow.resolution; + shadow_desc.Height = shadow.resolution; + shadow_desc.DepthOrArraySize = 1u; + shadow_desc.MipLevels = 1u; + shadow_desc.Format = DXGI_FORMAT_R32_TYPELESS; + shadow_desc.SampleDesc.Count = 1u; + shadow_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + shadow_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + D3D12_HEAP_PROPERTIES heap{}; + heap.Type = D3D12_HEAP_TYPE_DEFAULT; + D3D12_CLEAR_VALUE clear{}; + clear.Format = DXGI_FORMAT_D32_FLOAT; + clear.DepthStencil.Depth = 1.0f; + clear.DepthStencil.Stencil = 0u; + hr = s.device->CreateCommittedResource( + &heap, D3D12_HEAP_FLAG_NONE, &shadow_desc, + D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear, IID_PPV_ARGS(&shadow.depth)); + if (FAILED(hr)) { + runtime_log_error("optional ProperShaders shadow map disabled", + hr_text(hr, "CreateCommittedResource(DX12 GE realtime shadow map)")); + s.realtime_shadows_available = false; + } else { + D3D12_DEPTH_STENCIL_VIEW_DESC shadow_dsv{}; + shadow_dsv.Format = DXGI_FORMAT_D32_FLOAT; + shadow_dsv.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + s.device->CreateDepthStencilView(shadow.depth.Get(), &shadow_dsv, + dsv_cpu(s, shadow.dsv_index)); + D3D12_SHADER_RESOURCE_VIEW_DESC shadow_srv{}; + // Map the single R32 depth component to RGB as well. Material + // shaders still read it as Texture2D, while DebugMode=1 + // can present the exact raw shadow map as grayscale without a + // second debug-only pixel shader. + shadow_srv.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING( + D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0, + D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0, + D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0, + D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1); + shadow_srv.Format = DXGI_FORMAT_R32_FLOAT; + shadow_srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + shadow_srv.Texture2D.MipLevels = 1u; + s.device->CreateShaderResourceView(shadow.depth.Get(), &shadow_srv, + srv_cpu(s, shadow.srv_index)); + s.realtime_shadow_map = std::move(shadow); + runtime_log_line("ProperShaders directional shadow map created " + + std::to_string(s.realtime_shadow_map.resolution) + "x" + + std::to_string(s.realtime_shadow_map.resolution)); + } + } + } + // Readback exists only for the validation probe / explicit diagnostics. // Normal gameplay goes render-target -> SRV -> swapchain entirely on GPU. if (s.readback_enabled) { @@ -1791,9 +2176,11 @@ bool create_targets(Dx12GeState &s, std::string &error) noexcept { s.readback_bytes = 0u; } - if (vcs_configuration().volumetric_clouds.enabled) { + if (proper_shaders_private_available() && + vcs_configuration().proper_shaders.enabled && + vcs_configuration().proper_shaders.volumetric_clouds.enabled) { const std::uint32_t divisor = std::clamp( - vcs_configuration().volumetric_clouds.downscale_div, 1u, 8u); + vcs_configuration().proper_shaders.volumetric_clouds.downscale_div, 1u, 8u); std::uint32_t cloud_width = std::max(2u, (s.target_width + divisor - 1u) / divisor); std::uint32_t cloud_height = std::max(2u, (s.target_height + divisor - 1u) / divisor); cloud_width = (cloud_width + 1u) & ~1u; @@ -1903,9 +2290,11 @@ bool ensure_framebuffer_target(Dx12GeState &s, std::uint32_t address, ++s.report.dx12_framebuffer_target_hits; return existing->color != nullptr; } + const bool shadow_depth_srv = s.realtime_shadows_available; + const std::uint32_t required_srvs = shadow_depth_srv ? 3u : 1u; if (!s.device || !s.rtv_heap || !s.dsv_heap || !s.srv_heap || s.next_rtv >= kFramebufferTargetCapacity || s.next_dsv >= kFramebufferTargetCapacity || - s.next_srv >= kSrvCapacity) { + s.next_srv > kSrvCapacity - required_srvs) { error = "DX12 framebuffer target/descriptor capacity exhausted"; return false; } @@ -1915,6 +2304,10 @@ bool ensure_framebuffer_target(Dx12GeState &s, std::uint32_t address, target.rtv_index = s.next_rtv++; target.dsv_index = s.next_dsv++; target.srv_index = s.next_srv++; + if (shadow_depth_srv) { + target.depth_srv_base = s.next_srv; + s.next_srv += 2u; + } D3D12_HEAP_PROPERTIES default_heap{}; default_heap.Type = D3D12_HEAP_TYPE_DEFAULT; @@ -1971,7 +2364,8 @@ bool ensure_framebuffer_target(Dx12GeState &s, std::uint32_t address, depth.Height = s.target_height; depth.DepthOrArraySize = 1u; depth.MipLevels = 1u; - depth.Format = s.depth_format; + depth.Format = shadow_depth_srv ? typeless_depth_resource_format(s.depth_format) + : s.depth_format; depth.SampleDesc.Count = s.sample_count; depth.SampleDesc.Quality = s.sample_quality; depth.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; @@ -1989,6 +2383,25 @@ bool ensure_framebuffer_target(Dx12GeState &s, std::uint32_t address, dsv.ViewDimension = s.sample_count > 1u ? D3D12_DSV_DIMENSION_TEXTURE2DMS : D3D12_DSV_DIMENSION_TEXTURE2D; s.device->CreateDepthStencilView(target.depth.Get(), &dsv, dsv_cpu(s, target.dsv_index)); + if (shadow_depth_srv) { + D3D12_SHADER_RESOURCE_VIEW_DESC depth_srv{}; + depth_srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + depth_srv.Format = depth_srv_format(s.depth_format); + depth_srv.ViewDimension = s.sample_count > 1u + ? D3D12_SRV_DIMENSION_TEXTURE2DMS + : D3D12_SRV_DIMENSION_TEXTURE2D; + if (s.sample_count == 1u) depth_srv.Texture2D.MipLevels = 1u; + s.device->CreateShaderResourceView(target.depth.Get(), &depth_srv, + srv_cpu(s, target.depth_srv_base)); + // t1 is the private stable directional shadow map. Copy its descriptor + // beside scene depth so one descriptor table binds both resources. + if (s.realtime_shadow_map.depth) { + s.device->CopyDescriptorsSimple( + 1u, srv_cpu(s, target.depth_srv_base + 1u), + srv_cpu(s, s.realtime_shadow_map.srv_index), + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + } target.color_state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; s.frame_targets.emplace(address, std::move(target)); @@ -2003,7 +2416,9 @@ bool ensure_framebuffer_target(Dx12GeState &s, std::uint32_t address, log << "dx12 framebuffer target created address=0x" << std::hex << address << std::dec << " size=" << s.target_width << 'x' << s.target_height << " msaa=" << s.sample_count << " depth=" << s.depth_bits - << " srv=" << (created != s.frame_targets.end() ? created->second.srv_index : 0u); + << " srv=" << (created != s.frame_targets.end() ? created->second.srv_index : 0u) + << " depth_srv=" << (created != s.frame_targets.end() + ? created->second.depth_srv_base : 0u); runtime_log_line(log.str()); } return true; @@ -2074,8 +2489,24 @@ ComPtr create_pipeline(Dx12GeState &s, D3D12_GRAPHICS_PIPELINE_STATE_DESC pso{}; pso.pRootSignature = s.root_signature.Get(); ID3DBlob *vs = packed_0115 ? s.packed_0115_vertex_shader.Get() : s.vertex_shader.Get(); + ID3DBlob *ps = s.pixel_shader.Get(); + if (proper_shaders_private_available()) { + if (draw.shader_pipe == GeShaderPipe::Building && s.building_pixel_shader) { + vs = packed_0115 ? s.building_packed_0115_vertex_shader.Get() + : s.building_vertex_shader.Get(); + ps = s.building_pixel_shader.Get(); + } else if (draw.shader_pipe == GeShaderPipe::Skin && s.skin_pixel_shader) { + vs = packed_0115 ? s.skin_packed_0115_vertex_shader.Get() + : s.skin_vertex_shader.Get(); + ps = s.skin_pixel_shader.Get(); + } else if (draw.shader_pipe == GeShaderPipe::Vehicle && s.vehicle_pixel_shader) { + vs = packed_0115 ? s.vehicle_packed_0115_vertex_shader.Get() + : s.vehicle_vertex_shader.Get(); + ps = s.vehicle_pixel_shader.Get(); + } + } pso.VS = {vs->GetBufferPointer(), vs->GetBufferSize()}; - pso.PS = {s.pixel_shader->GetBufferPointer(), s.pixel_shader->GetBufferSize()}; + pso.PS = {ps->GetBufferPointer(), ps->GetBufferSize()}; pso.InputLayout = packed_0115 ? D3D12_INPUT_LAYOUT_DESC{packed_layout, static_cast(std::size(packed_layout))} : D3D12_INPUT_LAYOUT_DESC{layout, static_cast(std::size(layout))}; @@ -2322,6 +2753,15 @@ std::uint32_t cloud_linear_sampler(Dx12GeState &s) noexcept { return ensure_sampler(s, draw); } +std::uint32_t shadow_point_sampler(Dx12GeState &s) noexcept { + GeGpuDrawDescriptor draw{}; + draw.texture_min_linear = false; + draw.texture_mag_linear = false; + draw.texture_clamp_u = true; + draw.texture_clamp_v = true; + return ensure_sampler(s, draw); +} + bool create_cloud_root_signature(Dx12GeState &s, std::string &error) noexcept { D3D12_DESCRIPTOR_RANGE srv_range{}; srv_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; @@ -2370,6 +2810,45 @@ bool create_cloud_root_signature(Dx12GeState &s, std::string &error) noexcept { return true; } +bool create_realtime_shadow_root_signatures(Dx12GeState &s, std::string &error) noexcept { + if (!s.realtime_shadows_available) return true; + + // Caster root: one per-draw light MVP (16 floats) visible only to the VS. + D3D12_ROOT_PARAMETER caster_parameter{}; + caster_parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + caster_parameter.Constants.ShaderRegister = 1u; + caster_parameter.Constants.Num32BitValues = 16u; + caster_parameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + D3D12_ROOT_SIGNATURE_DESC caster_desc{}; + caster_desc.NumParameters = 1u; + caster_desc.pParameters = &caster_parameter; + caster_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS; + ComPtr blob, errors; + HRESULT hr = D3D12SerializeRootSignature(&caster_desc, D3D_ROOT_SIGNATURE_VERSION_1, + &blob, &errors); + if (FAILED(hr)) { + error = errors ? std::string(static_cast(errors->GetBufferPointer()), + errors->GetBufferSize()) + : hr_text(hr, "D3D12SerializeRootSignature(shadow caster)"); + return false; + } + hr = s.device->CreateRootSignature(0u, blob->GetBufferPointer(), blob->GetBufferSize(), + IID_PPV_ARGS(&s.realtime_shadow_caster_root_signature)); + if (FAILED(hr)) { + error = hr_text(hr, "CreateRootSignature(shadow caster)"); + return false; + } + + // No fullscreen/composite root signature is created. The completed + // directional depth map is bound as t1 on the normal GE material root. + s.realtime_shadow_composite_root_signature.Reset(); + return true; +} + bool invert_cloud_matrix(const std::array &matrix, std::array &inverse) noexcept { // Gauss-Jordan in double precision. GE projection matrices are small, but @@ -2419,6 +2898,210 @@ bool invert_cloud_matrix(const std::array &matrix, return true; } +std::array shadow_affine_4x3_to_mat4( + const std::array &m) noexcept { + return {m[0], m[1], m[2], 0.0f, + m[3], m[4], m[5], 0.0f, + m[6], m[7], m[8], 0.0f, + m[9], m[10], m[11], 1.0f}; +} + +std::array shadow_multiply_mat4(const std::array &a, + const std::array &b) noexcept { + std::array result{}; + for (std::size_t column = 0u; column < 4u; ++column) { + for (std::size_t row = 0u; row < 4u; ++row) { + float sum = 0.0f; + for (std::size_t k = 0u; k < 4u; ++k) + sum += a[k * 4u + row] * b[column * 4u + k]; + result[column * 4u + row] = sum; + } + } + return result; +} + +void shadow_matrix_rows(const std::array &matrix, + float *destination) noexcept { + for (std::size_t row = 0u; row < 4u; ++row) { + for (std::size_t column = 0u; column < 4u; ++column) + destination[row * 4u + column] = matrix[column * 4u + row]; + } +} + +bool shadow_effective_camera_vp(const CloudCameraCandidate &camera, + const Dx12FramebufferTarget &target, + std::array &view_projection, + std::array &inverse_view_projection) noexcept { + const float logical_width = static_cast( + std::max(1u, target.logical_width)); + const float logical_height = static_cast( + std::max(1u, target.logical_height)); + const float x_a = camera.viewport[0] * (2.0f / logical_width); + const float y_a = camera.viewport[1] * (2.0f / logical_height); + const float z_a = camera.viewport[2] * (1.0f / 65535.0f); + const float x_b = (camera.viewport[3] - camera.viewport[6]) * + (2.0f / logical_width) - 1.0f; + const float y_b = (camera.viewport[4] - camera.viewport[7]) * + (2.0f / logical_height) - 1.0f; + const float z_b = camera.viewport[5] * (1.0f / 65535.0f); + if (!std::isfinite(x_a) || !std::isfinite(y_a) || !std::isfinite(z_a) || + !std::isfinite(x_b) || !std::isfinite(y_b) || !std::isfinite(z_b) || + std::abs(x_a) < 1.0e-7f || std::abs(y_a) < 1.0e-7f || + std::abs(z_a) < 1.0e-9f) + return false; + + // Match make_transform_constants(): fold GE viewport X/Y and the PSP + // 0..65535 depth range into the guest projection before multiplying view. + std::array effective_projection{}; + for (std::size_t column = 0u; column < 4u; ++column) { + const std::size_t base = column * 4u; + const float w = camera.projection[base + 3u]; + effective_projection[base + 0u] = + x_a * camera.projection[base + 0u] + x_b * w; + effective_projection[base + 1u] = + -y_a * camera.projection[base + 1u] - y_b * w; + effective_projection[base + 2u] = + z_a * camera.projection[base + 2u] + z_b * w; + effective_projection[base + 3u] = w; + } + view_projection = shadow_multiply_mat4( + effective_projection, shadow_affine_4x3_to_mat4(camera.view)); + std::array inverse{}; + if (!invert_cloud_matrix(view_projection, inverse)) return false; + for (std::size_t i = 0u; i < inverse.size(); ++i) { + if (!std::isfinite(inverse[i]) || + std::abs(inverse[i]) > static_cast(std::numeric_limits::max())) + return false; + inverse_view_projection[i] = static_cast(inverse[i]); + } + return true; +} + +// World-space eye position, taken from the GE view matrix this frame's draws +// actually used. No guest address is involved. +// +// The two sources tried before this both put the box in empty space. VCS has no +// documented memory map, and CloudCameraCandidate::camera_position comes from +// the hardcoded kVcsCameraPosition in ge_renderer.cpp: measured against the +// caster geometry it was ~1490 world units off, so the 2*WorldRadius box landed +// where nothing is drawn (DebugMode=1 white, DebugMode=3 red). Unprojecting the +// effective view-projection was no better, because the GE folds the PSP 0..65535 +// depth range into the matrix and NDC z=0 is not the near plane. +// +// camera.view is the affine 4x3 GE view in the layout affine_4x3_to_mat4() +// expects: m[0..2], m[3..5], m[6..8] are the rotation basis columns and +// m[9..11] the translation. world->view is v = R*p + t, so the eye is -R^T * t +// whenever R is orthonormal. Reflection and camera-relative passes do not +// satisfy that, which is why orthonormality is verified instead of assumed. +bool shadow_view_eye_position(const CloudCameraCandidate &camera, + std::array &eye) noexcept { + const std::array &m = camera.view; + if (!std::all_of(m.begin(), m.end(), + [](float value) { return std::isfinite(value); })) + return false; + for (std::size_t column = 0u; column < 3u; ++column) { + const double length2 = + static_cast(m[column * 3u + 0u]) * m[column * 3u + 0u] + + static_cast(m[column * 3u + 1u]) * m[column * 3u + 1u] + + static_cast(m[column * 3u + 2u]) * m[column * 3u + 2u]; + if (!(length2 > 0.99 && length2 < 1.01)) return false; + } + const std::array translation{m[9], m[10], m[11]}; + std::array candidate{}; + for (std::size_t axis = 0u; axis < 3u; ++axis) { + candidate[axis] = -(m[axis * 3u + 0u] * translation[0] + + m[axis * 3u + 1u] * translation[1] + + m[axis * 3u + 2u] * translation[2]); + } + if (!std::all_of(candidate.begin(), candidate.end(), + [](float value) { return std::isfinite(value); })) + return false; + eye = candidate; + return true; +} + +bool shadow_directional_light_vp(const CloudCameraCandidate &camera, + const Dx12FramebufferTarget &target, + std::array &light_view_projection) noexcept { + const auto &proper = vcs_configuration().proper_shaders; + const auto &shadow = proper.realtime_shadows; + const auto &cloud = proper.volumetric_clouds; + std::array sun = shadow.use_cloud_sun_direction + ? std::array{cloud.sun_direction_x, cloud.sun_direction_y, + cloud.sun_direction_z} + : std::array{shadow.light_direction_x, shadow.light_direction_y, + shadow.light_direction_z}; + const auto normalize = [](std::array v, + const std::array &fallback) { + const double length2 = static_cast(v[0]) * v[0] + + static_cast(v[1]) * v[1] + + static_cast(v[2]) * v[2]; + if (!std::isfinite(length2) || length2 < 1.0e-12) return fallback; + const float inv_length = static_cast(1.0 / std::sqrt(length2)); + for (float &value : v) value *= inv_length; + return v; + }; + const auto cross = [](const std::array &a, + const std::array &b) { + return std::array{a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]}; + }; + const auto dot = [](const std::array &a, + const std::array &b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + }; + + sun = normalize(sun, {0.38f, -0.28f, 0.88f}); + // lightZ follows the rays from the sun toward the scene. With conventional + // LESS depth, casters nearer the sun then have smaller depth than receivers. + const std::array light_z{-sun[0], -sun[1], -sun[2]}; + const std::array up_seed = std::abs(light_z[2]) < 0.92f + ? std::array{0.0f, 0.0f, 1.0f} + : std::array{0.0f, 1.0f, 0.0f}; + const std::array light_x = + normalize(cross(up_seed, light_z), {1.0f, 0.0f, 0.0f}); + const std::array light_y = + normalize(cross(light_z, light_x), {0.0f, 1.0f, 0.0f}); + + const float radius = std::max(20.0f, shadow.world_radius); + const float depth_range = std::max(40.0f, shadow.depth_range); + const float resolution = static_cast(std::clamp( + shadow.map_resolution, 512u, 4096u)); + const float texel_world = (radius * 2.0f) / resolution; + std::array center{}; + const bool center_from_view = shadow_view_eye_position(camera, center); + if (!center_from_view) center = camera.camera_position; + static bool center_logged = false; + if (!center_logged) { + center_logged = true; + std::ostringstream line; + line << std::fixed << std::setprecision(3) + << "ProperShaders shadow box centre source=" + << (center_from_view ? "ge-view-matrix" : "guest-camera-position") + << " centre=(" << center[0] << ',' << center[1] << ',' << center[2] << ')' + << " guest_camera=(" << camera.camera_position[0] << ',' + << camera.camera_position[1] << ',' << camera.camera_position[2] << ')' + << " radius=" << radius << " depth_range=" << depth_range + << " sun=(" << sun[0] << ',' << sun[1] << ',' << sun[2] << ')'; + runtime_log_line(line.str()); + } + const float center_x = std::round(dot(light_x, center) / texel_world) * texel_world; + const float center_y = std::round(dot(light_y, center) / texel_world) * texel_world; + const float center_z = dot(light_z, center); + + // Column-major matrix, D3D x/y in [-1,1] and depth in [0,1]. Snapping the + // light-space X/Y origin to one shadow texel prevents camera shimmer. + light_view_projection = { + light_x[0] / radius, light_y[0] / radius, light_z[0] / depth_range, 0.0f, + light_x[1] / radius, light_y[1] / radius, light_z[1] / depth_range, 0.0f, + light_x[2] / radius, light_y[2] / radius, light_z[2] / depth_range, 0.0f, + -center_x / radius, -center_y / radius, + 0.5f - center_z / depth_range, 1.0f}; + return std::all_of(light_view_projection.begin(), light_view_projection.end(), + [](float value) { return std::isfinite(value); }); +} + bool create_cloud_target_pipeline(Dx12GeState &s, std::string &error) noexcept { D3D12_GRAPHICS_PIPELINE_STATE_DESC pso{}; pso.pRootSignature = s.cloud_root_signature.Get(); @@ -2518,6 +3201,69 @@ bool create_cloud_composite_pipeline(Dx12GeState &s, std::string &error) noexcep return true; } +bool create_realtime_shadow_pipeline(Dx12GeState &s, std::string &error) noexcept { + if (!s.realtime_shadows_available) return true; + if (!s.realtime_shadow_caster_root_signature || + !s.realtime_shadow_caster_vertex_shader || + !s.realtime_shadow_caster_packed_vertex_shader) { + error = "ProperShaders directional shadow caster shaders/root signature are incomplete"; + return false; + } + + static const D3D12_INPUT_ELEMENT_DESC caster_layout[] = { + {"POSITION", 0u, DXGI_FORMAT_R32G32B32A32_FLOAT, 0u, + static_cast(offsetof(Dx12UploadVertex, x)), + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0u}, + }; + static const D3D12_INPUT_ELEMENT_DESC packed_caster_layout[] = { + {"POSITION", 1u, DXGI_FORMAT_R16G16_SINT, 0u, 4u, + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0u}, + {"POSITION", 2u, DXGI_FORMAT_R16_SINT, 0u, 8u, + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0u}, + }; + const auto create_caster = [&](bool packed, ID3D12PipelineState **output) -> bool { + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso{}; + pso.pRootSignature = s.realtime_shadow_caster_root_signature.Get(); + ID3DBlob *vs = packed ? s.realtime_shadow_caster_packed_vertex_shader.Get() + : s.realtime_shadow_caster_vertex_shader.Get(); + pso.VS = {vs->GetBufferPointer(), vs->GetBufferSize()}; + pso.InputLayout = packed + ? D3D12_INPUT_LAYOUT_DESC{packed_caster_layout, + static_cast(std::size(packed_caster_layout))} + : D3D12_INPUT_LAYOUT_DESC{caster_layout, + static_cast(std::size(caster_layout))}; + pso.SampleMask = UINT_MAX; + pso.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; + // Private casters deliberately render two-sided. It is more robust for + // GTA-era thin geometry and avoids inheriting PSP winding differences. + pso.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; + pso.RasterizerState.DepthClipEnable = TRUE; + pso.DepthStencilState.DepthEnable = TRUE; + pso.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + pso.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + pso.DepthStencilState.StencilEnable = FALSE; + pso.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + pso.NumRenderTargets = 0u; + pso.DSVFormat = DXGI_FORMAT_D32_FLOAT; + pso.SampleDesc.Count = 1u; + const HRESULT hr = s.device->CreateGraphicsPipelineState(&pso, IID_PPV_ARGS(output)); + if (FAILED(hr)) { + error = hr_text(hr, packed + ? "CreateGraphicsPipelineState(DX12 GE packed shadow caster)" + : "CreateGraphicsPipelineState(DX12 GE shadow caster)"); + return false; + } + return true; + }; + if (!create_caster(false, s.realtime_shadow_caster_pipeline.ReleaseAndGetAddressOf()) || + !create_caster(true, s.realtime_shadow_caster_packed_pipeline.ReleaseAndGetAddressOf())) + return false; + + s.realtime_shadow_pipeline.Reset(); + runtime_log_line("ProperShaders realtime shadows active (directional shadow map + per-material PCF receivers; fullscreen contact pass removed)"); + return true; +} + const CloudCameraCandidate *select_cloud_camera(const Dx12GeState &s) noexcept { if (s.display_framebuffer == 0u) return nullptr; std::vector ancestors; @@ -2566,8 +3312,10 @@ const CloudCameraCandidate *select_cloud_camera(const Dx12GeState &s) noexcept { CloudShaderConstants cloud_present_constants(const Dx12GeState &s) noexcept { CloudShaderConstants out{}; - const auto &config = vcs_configuration().volumetric_clouds; - if (!config.enabled || s.cloud_cameras.empty()) return out; + const auto &config = vcs_configuration().proper_shaders.volumetric_clouds; + if (!proper_shaders_private_available() || + !vcs_configuration().proper_shaders.enabled || + !config.enabled || s.cloud_cameras.empty()) return out; const CloudCameraCandidate *camera = select_cloud_camera(s); if (camera == nullptr) return out; @@ -2580,9 +3328,9 @@ CloudShaderConstants cloud_present_constants(const Dx12GeState &s) noexcept { 1u, target != nullptr ? target->logical_height : kReferenceHeight)); const float x_a = camera->viewport[0] * (2.0f / logical_width); const float y_a = camera->viewport[1] * (2.0f / logical_height); - const float x_b = (camera->viewport[2] - camera->viewport[4]) * + const float x_b = (camera->viewport[3] - camera->viewport[6]) * (2.0f / logical_width) - 1.0f; - const float y_b = (camera->viewport[3] - camera->viewport[5]) * + const float y_b = (camera->viewport[4] - camera->viewport[7]) * (2.0f / logical_height) - 1.0f; if (!std::isfinite(x_a) || !std::isfinite(y_a) || std::abs(x_a) < 1.0e-6f || std::abs(y_a) < 1.0e-6f) @@ -2683,7 +3431,7 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ const std::uint32_t current_index = 1u - previous_index; CloudRenderTarget &previous = s.cloud_history[previous_index]; CloudRenderTarget ¤t = s.cloud_history[current_index]; - const auto &config = vcs_configuration().volumetric_clouds; + const auto &config = vcs_configuration().proper_shaders.volumetric_clouds; float camera_delta_squared = 0.0f; if (s.cloud_history_valid) { @@ -2830,6 +3578,217 @@ void record_clouds_into_world_target(Dx12GeState &s, Dx12FramebufferTarget &targ s.list->DrawInstanced(3u, 1u, 0u, 0u); } +bool record_directional_shadow_map( + Dx12GeState &s, Dx12FramebufferTarget &target, + const CloudCameraCandidate &camera, + const D3D12_VERTEX_BUFFER_VIEW ®ular_vb, + const D3D12_VERTEX_BUFFER_VIEW &packed_vb, + const D3D12_INDEX_BUFFER_VIEW *index_buffer, + std::array &light_vp_out) noexcept { + const auto &proper = vcs_configuration().proper_shaders; + const auto &shadow = proper.realtime_shadows; + if (!proper.enabled || !shadow.enabled || !proper_shaders_private_available() || + !s.realtime_shadows_available || !s.realtime_shadow_map.depth || + !s.realtime_shadow_caster_pipeline || + !s.realtime_shadow_caster_packed_pipeline || + !s.realtime_shadow_caster_root_signature) + return false; + + s.realtime_shadow_map.caster_draws = 0u; + if (!shadow_directional_light_vp(camera, target, light_vp_out)) return false; + + // Real shadow-map prepass. This happens before the normal world replay, so + // Building/Skin/Vehicle material shaders can sample the completed map while + // drawing their own surfaces. No fullscreen/contact-shadow composite is + // involved: receivers are shaded in their actual material PS. + transition(s.list.Get(), s.realtime_shadow_map.depth.Get(), + s.realtime_shadow_map.state, D3D12_RESOURCE_STATE_DEPTH_WRITE); + s.realtime_shadow_map.state = D3D12_RESOURCE_STATE_DEPTH_WRITE; + const D3D12_CPU_DESCRIPTOR_HANDLE shadow_dsv = + dsv_cpu(s, s.realtime_shadow_map.dsv_index); + s.list->OMSetRenderTargets(0u, nullptr, FALSE, &shadow_dsv); + s.list->ClearDepthStencilView(shadow_dsv, D3D12_CLEAR_FLAG_DEPTH, + 1.0f, 0u, 0u, nullptr); + const float map_size = static_cast(s.realtime_shadow_map.resolution); + const D3D12_VIEWPORT shadow_viewport{0.0f, 0.0f, map_size, map_size, 0.0f, 1.0f}; + const D3D12_RECT shadow_scissor{0, 0, + static_cast(s.realtime_shadow_map.resolution), + static_cast(s.realtime_shadow_map.resolution)}; + s.list->RSSetViewports(1u, &shadow_viewport); + s.list->RSSetScissorRects(1u, &shadow_scissor); + s.list->SetGraphicsRootSignature(s.realtime_shadow_caster_root_signature.Get()); + if (index_buffer != nullptr) s.list->IASetIndexBuffer(index_buffer); + + // One-shot coverage probe. caster_draws counts draw *calls*, which is why + // the pass could report 101 casters while the depth map stayed at its 1.0 + // clear: the calls were issued with a light matrix that put every triangle + // outside the box, and DepthClipEnable threw them away. This samples the + // same vertices the IA sees and reports how many actually land in the map. + static bool coverage_logged = false; + const bool probe_coverage = !coverage_logged; + std::array ndc_min{std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()}; + std::array ndc_max{std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest()}; + std::uint32_t probed_vertices = 0u; + std::uint32_t probed_inside = 0u; + // World-space bounds of the same samples. This is the ground truth the box + // has to enclose, and it depends on no camera source at all. + std::array world_min{std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max()}; + std::array world_max{std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest()}; + std::array world_sum{}; + + bool bound_packed = false; + bool vertex_view_valid = false; + ID3D12PipelineState *caster_pipeline = nullptr; + D3D12_PRIMITIVE_TOPOLOGY caster_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; + const std::uint32_t world_target = target.address & 0x001FFFF0u; + std::uint32_t caster_draws = 0u; + for (const Dx12Batch &batch : s.batches) { + if ((batch.draw.framebuffer_address & 0x001FFFF0u) != world_target || + batch.draw.clear_mode || !batch.hardware_transform) + continue; + const std::uint32_t primitive = batch.transform.primitive; + if (primitive < 3u || primitive > 5u) continue; + const bool semantic_caster = batch.draw.shader_pipe == GeShaderPipe::Building || + batch.draw.shader_pipe == GeShaderPipe::Skin || + batch.draw.shader_pipe == GeShaderPipe::Vehicle; + if (!semantic_caster) continue; + // Opaque/depth-writing world is always useful. Dynamic skin/vehicle + // draws are allowed even when their normal pass disables depth writes. + if (!batch.draw.depth_write_enabled && + batch.draw.shader_pipe == GeShaderPipe::Building) + continue; + + ID3D12PipelineState *wanted_pipeline = batch.packed_0115 + ? s.realtime_shadow_caster_packed_pipeline.Get() + : s.realtime_shadow_caster_pipeline.Get(); + if (wanted_pipeline != caster_pipeline) { + s.list->SetPipelineState(wanted_pipeline); + caster_pipeline = wanted_pipeline; + } + if (!vertex_view_valid || bound_packed != batch.packed_0115) { + const D3D12_VERTEX_BUFFER_VIEW &active_vb = + batch.packed_0115 ? packed_vb : regular_vb; + s.list->IASetVertexBuffers(0u, 1u, &active_vb); + bound_packed = batch.packed_0115; + vertex_view_valid = true; + } + const D3D12_PRIMITIVE_TOPOLOGY topology = batch_topology(batch); + if (topology != caster_topology) { + s.list->IASetPrimitiveTopology(topology); + caster_topology = topology; + } + + const std::array caster_mvp = shadow_multiply_mat4( + light_vp_out, batch.transform.model_to_world); + std::array caster_rows{}; + shadow_matrix_rows(caster_mvp, caster_rows.data()); + s.list->SetGraphicsRoot32BitConstants(0u, 16u, caster_rows.data(), 0u); + if (probe_coverage && batch.vertex_count != 0u && probed_vertices < 4096u) { + const std::uint32_t step = std::max(1u, batch.vertex_count / 32u); + for (std::uint32_t v = 0u; v < batch.vertex_count; v += step) { + std::array model{}; + if (batch.packed_0115) { + const std::size_t stride = s.packed_0115_gpu_stride; + const std::size_t base = + (static_cast(batch.first_vertex) + v) * stride; + if (base + kPacked0115GuestStride > s.packed_0115_vertices.size()) break; + const std::byte *source = s.packed_0115_vertices.data() + base; + std::array raw{}; + std::memcpy(raw.data(), source + 4u, 6u); + model = {static_cast(raw[0]) * (1.0f / 32768.0f), + static_cast(raw[1]) * (1.0f / 32768.0f), + static_cast(raw[2]) * (1.0f / 32768.0f), 1.0f}; + } else { + const std::size_t index = + static_cast(batch.first_vertex) + v; + if (index >= s.vertices.size()) break; + const Dx12UploadVertex &source = s.vertices[index]; + model = {source.x, source.y, source.z, source.w}; + } + std::array clip{}; + for (std::size_t row = 0u; row < 4u; ++row) { + clip[row] = static_cast(caster_mvp[row]) * model[0] + + static_cast(caster_mvp[4u + row]) * model[1] + + static_cast(caster_mvp[8u + row]) * model[2] + + static_cast(caster_mvp[12u + row]) * model[3]; + } + if (!std::isfinite(clip[3]) || std::abs(clip[3]) < 1.0e-9) continue; + const std::array ndc{clip[0] / clip[3], clip[1] / clip[3], + clip[2] / clip[3]}; + if (!std::all_of(ndc.begin(), ndc.end(), + [](double value) { return std::isfinite(value); })) + continue; + for (std::size_t axis = 0u; axis < 3u; ++axis) { + ndc_min[axis] = std::min(ndc_min[axis], ndc[axis]); + ndc_max[axis] = std::max(ndc_max[axis], ndc[axis]); + } + ++probed_vertices; + if (ndc[0] >= -1.0 && ndc[0] <= 1.0 && ndc[1] >= -1.0 && ndc[1] <= 1.0 && + ndc[2] >= 0.0 && ndc[2] <= 1.0) + ++probed_inside; + const std::array &w2 = batch.transform.model_to_world; + for (std::size_t axis = 0u; axis < 3u; ++axis) { + const double world = static_cast(w2[axis]) * model[0] + + static_cast(w2[4u + axis]) * model[1] + + static_cast(w2[8u + axis]) * model[2] + + static_cast(w2[12u + axis]) * model[3]; + if (!std::isfinite(world)) continue; + world_min[axis] = std::min(world_min[axis], world); + world_max[axis] = std::max(world_max[axis], world); + world_sum[axis] += world; + } + } + } + if (batch.indexed && index_buffer != nullptr) { + s.list->DrawIndexedInstanced(batch.index_count, 1u, batch.first_index, + static_cast(batch.first_vertex), 0u); + } else { + s.list->DrawInstanced(batch.vertex_count, 1u, batch.first_vertex, 0u); + } + ++caster_draws; + } + transition(s.list.Get(), s.realtime_shadow_map.depth.Get(), + s.realtime_shadow_map.state, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + s.realtime_shadow_map.state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + + s.realtime_shadow_map.caster_draws = caster_draws; + if (probe_coverage && probed_vertices != 0u) { + coverage_logged = true; + std::ostringstream line; + line << std::fixed << std::setprecision(3) + << "ProperShaders shadow caster coverage draws=" << caster_draws + << " probed_vertices=" << probed_vertices + << " inside_box=" << probed_inside + << " ndc_x=[" << ndc_min[0] << ',' << ndc_max[0] << ']' + << " ndc_y=[" << ndc_min[1] << ',' << ndc_max[1] << ']' + << " ndc_z=[" << ndc_min[2] << ',' << ndc_max[2] << ']'; + const double inv = 1.0 / static_cast(probed_vertices); + line << " world_min=(" << world_min[0] << ',' << world_min[1] << ',' + << world_min[2] << ')' + << " world_max=(" << world_max[0] << ',' << world_max[1] << ',' + << world_max[2] << ')' + << " world_centroid=(" << world_sum[0] * inv << ',' + << world_sum[1] * inv << ',' << world_sum[2] * inv << ')'; + runtime_log_line(line.str()); + } + static bool logged = false; + if (!logged) { + logged = true; + runtime_log_line("ProperShaders material shadow map populated casters=" + + std::to_string(caster_draws) + + " receivers=Building/Skin/Vehicle (no fullscreen contact composite)"); + } + return caster_draws != 0u; +} + bool record_direct_present(Dx12GeState &s, Dx12FramebufferTarget &source, std::string &error) noexcept { if (!ensure_swapchain(s, error)) return false; @@ -2856,8 +3815,27 @@ bool record_direct_present(Dx12GeState &s, Dx12FramebufferTarget &source, s.list->SetGraphicsRootSignature(s.root_signature.Get()); ID3D12DescriptorHeap *heaps[]{s.srv_heap.Get(), s.sampler_heap.Get()}; s.list->SetDescriptorHeaps(2u, heaps); - s.list->SetGraphicsRootDescriptorTable(0u, srv_gpu(s, source.srv_index)); + const auto &shadow_debug = vcs_configuration().proper_shaders.realtime_shadows; + const bool raw_shadow_debug = vcs_configuration().proper_shaders.enabled && + shadow_debug.enabled && shadow_debug.debug_mode == 1u && + proper_shaders_private_available() && s.realtime_shadows_available && + s.realtime_shadow_map.depth && + s.realtime_shadow_map.state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + const std::uint32_t present_srv = raw_shadow_debug + ? s.realtime_shadow_map.srv_index : source.srv_index; + s.list->SetGraphicsRootDescriptorTable(0u, srv_gpu(s, present_srv)); s.list->SetGraphicsRootDescriptorTable(1u, sampler_gpu(s, present_sampler(s))); + if (raw_shadow_debug) { + static bool shadow_debug_logged = false; + if (!shadow_debug_logged) { + shadow_debug_logged = true; + runtime_log_line("ProperShaders shadow DebugMode=1 presenting raw map " + + std::to_string(s.realtime_shadow_map.resolution) + "x" + + std::to_string(s.realtime_shadow_map.resolution) + + " caster_draws=" + + std::to_string(s.realtime_shadow_map.caster_draws)); + } + } // Clouds are rendered into the selected 3D world target before VCS samples // it for composition. Applying them here would mix world-camera rays with // final-display pixels and make the layer follow the screen. @@ -3233,7 +4211,17 @@ Dx12TransformConstants make_transform_constants(const Dx12Batch &batch, constants.row3 = clip_w; constants.view_z = hw.model_to_view_z; constants.uv = {hw.uv_scale_u, hw.uv_scale_v, hw.uv_offset_u, hw.uv_offset_v}; - constants.fog = {hw.fog_end, hw.fog_slope, 0.0f, 0.0f}; + // Extending world LOD/far clip without stretching the PSP fog range can make + // the extra submitted geometry mathematically present but visually identical: + // it is already fully blended into the fog colour at the stock distance. Scale + // the hardware fog curve in the same distance domain as DrawDistance.World. + // For factor = (viewZ + end) * slope, end*=m and slope/=m moves both fog + // start and end outward by m while preserving the original curve shape. + float fog_distance_scale = g_draw_distance_runtime_scales.world; + if (!std::isfinite(fog_distance_scale) || fog_distance_scale < 1.0f) + fog_distance_scale = 1.0f; + constants.fog = {hw.fog_end * fog_distance_scale, + hw.fog_slope / fog_distance_scale, 0.0f, 0.0f}; constants.control = {1u, hw.depth_clip_enabled ? 1u : 0u, hw.vertex_color_affine ? 1u : 0u, 0u}; constants.color_mul = hw.vertex_color_mul; @@ -3393,11 +4381,31 @@ bool create_backend(Dx12GeState &s, std::string &error) noexcept { } } if (!create_cloud_root_signature(s, error)) return false; + if (s.realtime_shadows_available) { + std::string shadow_error; + if (!create_realtime_shadow_root_signatures(s, shadow_error)) { + runtime_log_error("optional ProperShaders shadow root signature disabled", shadow_error); + s.realtime_shadows_available = false; + s.realtime_shadow_caster_root_signature.Reset(); + s.realtime_shadow_composite_root_signature.Reset(); + } + } if (!create_targets(s, error)) return false; if (!create_present_pipeline(s, error)) return false; if (!create_cloud_target_pipeline(s, error)) return false; if (!create_cloud_resolve_pipeline(s, error)) return false; if (!create_cloud_composite_pipeline(s, error)) return false; + if (s.realtime_shadows_available) { + std::string shadow_error; + if (!create_realtime_shadow_pipeline(s, shadow_error)) { + runtime_log_error("optional ProperShaders shadow PSO disabled", shadow_error); + s.realtime_shadows_available = false; + s.realtime_shadow_caster_pipeline.Reset(); + s.realtime_shadow_caster_packed_pipeline.Reset(); + s.realtime_shadow_pipeline.Reset(); + s.realtime_shadow_map = {}; + } + } s.vertices.reserve(262144u); s.packed_0115_vertices.reserve(2621440u); s.indices.reserve(524288u); @@ -3445,6 +4453,10 @@ void destroy_backend(Dx12GeState &s) noexcept { s.cloud_target_pipeline.Reset(); s.cloud_resolve_pipeline.Reset(); s.cloud_composite_pipeline.Reset(); + s.realtime_shadow_caster_pipeline.Reset(); + s.realtime_shadow_caster_packed_pipeline.Reset(); + s.realtime_shadow_pipeline.Reset(); + s.realtime_shadow_map = {}; s.cloud_target_pixel_shader.Reset(); s.cloud_resolve_pixel_shader.Reset(); s.cloud_composite_pixel_shader.Reset(); @@ -3467,12 +4479,26 @@ void destroy_backend(Dx12GeState &s) noexcept { s.known_frame_targets.clear(); s.last_registered_framebuffer_target = 0xFFFFFFFFu; s.pixel_shader.Reset(); + s.building_vertex_shader.Reset(); + s.building_packed_0115_vertex_shader.Reset(); + s.building_pixel_shader.Reset(); + s.skin_vertex_shader.Reset(); + s.skin_packed_0115_vertex_shader.Reset(); + s.skin_pixel_shader.Reset(); + s.vehicle_vertex_shader.Reset(); + s.vehicle_packed_0115_vertex_shader.Reset(); + s.vehicle_pixel_shader.Reset(); + s.realtime_shadow_caster_vertex_shader.Reset(); + s.realtime_shadow_caster_packed_vertex_shader.Reset(); + s.realtime_shadow_pixel_shader.Reset(); s.packed_0115_vertex_shader.Reset(); s.vertex_shader.Reset(); s.indirect_draw_signature.Reset(); s.indirect_draw_indexed_signature.Reset(); s.root_signature.Reset(); s.cloud_root_signature.Reset(); + s.realtime_shadow_caster_root_signature.Reset(); + s.realtime_shadow_composite_root_signature.Reset(); s.readback_buffer.Reset(); s.frame_targets.clear(); s.sampler_heap.Reset(); @@ -3687,12 +4713,15 @@ void ge_gpu_backend_record_draw(const GeGpuDrawDescriptor &draw) noexcept { void ge_gpu_backend_observe_camera(const std::array &view, const std::array &projection, - const std::array &viewport, + const std::array &viewport, const std::array &camera_position, const GeGpuDrawDescriptor &draw, std::uint32_t vertex_weight) noexcept { Dx12GeState &s = state(); - if (!s.enabled || !vcs_configuration().volumetric_clouds.enabled || + const auto &proper = vcs_configuration().proper_shaders; + const bool observer_needed = proper_shaders_private_available() && proper.enabled && + (proper.volumetric_clouds.enabled || proper.realtime_shadows.enabled); + if (!s.enabled || !observer_needed || vertex_weight == 0u || !draw.depth_test_enabled) return; if (!std::all_of(view.begin(), view.end(), [](float value) { return std::isfinite(value); }) || !std::all_of(projection.begin(), projection.end(), [](float value) { return std::isfinite(value); }) || @@ -4204,6 +5233,11 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { return false; } + // Final semantic pass: once all draws for the frame are known, expand each + // high-confidence vehicle material anchor to the complete contiguous vehicle + // entity run (wheels/glass/trim included). + promote_vehicle_batch_groups(s); + const std::size_t vertex_bytes = s.vertices.size() * sizeof(Dx12UploadVertex); const std::size_t packed_offset = (vertex_bytes + 3u) & ~std::size_t{3u}; const std::size_t packed_bytes = s.packed_0115_vertices.size(); @@ -4223,7 +5257,11 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { Dx12FramebufferTarget *display_target = find_framebuffer_target(s, s.display_framebuffer); Dx12FrameResources &frame = s.frames[s.frame_cursor]; + const auto &proper_config = vcs_configuration().proper_shaders; + const bool material_shadow_requested = proper_config.enabled && + proper_config.realtime_shadows.enabled && proper_shaders_private_available(); const bool indirect_enabled = dx12_execute_indirect_enabled() && + !material_shadow_requested && !s.amd_uma_safe_mode && s.indirect_draw_signature && s.indirect_draw_indexed_signature && frame.indirect_upload_buffer && frame.mapped_indirect_upload != nullptr; @@ -4304,6 +5342,8 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { bool active_transform_valid = false; Dx12PixelConstants active_pixel{}; bool active_pixel_valid = false; + std::array active_shadow_rows{}; + bool active_shadow_rows_valid = false; D3D12_RECT active_scissor{}; bool active_scissor_valid = false; std::uint32_t active_blend_fix = std::numeric_limits::max(); @@ -4315,6 +5355,29 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { const CloudShaderConstants clouds = cloud_present_constants(s); const std::uint32_t cloud_target_address = cloud_camera != nullptr ? cloud_camera->target : 0u; + + std::array material_shadow_light_vp{}; + bool material_shadow_ready = false; + if (cloud_camera != nullptr) { + if (Dx12FramebufferTarget *shadow_target = + find_framebuffer_target(s, cloud_target_address); + shadow_target != nullptr && shadow_target->color && shadow_target->depth) { + material_shadow_ready = record_directional_shadow_map( + s, *shadow_target, *cloud_camera, vb, packed_vb, + index_bytes != 0u ? &ib : nullptr, material_shadow_light_vp); + } + } + // Shadow-map generation uses its own depth-only root signature/viewport. + // Restore the material GE state before replaying the actual frame. t1 is + // always bound (null descriptor when unavailable), so every private pipe + // has a defined resource binding. + s.list->SetGraphicsRootSignature(s.root_signature.Get()); + s.list->SetDescriptorHeaps(2u, descriptor_heaps); + s.list->SetGraphicsRootDescriptorTable(4u, srv_gpu( + s, material_shadow_ready ? s.realtime_shadow_map.srv_index : 0u)); + s.list->RSSetViewports(1u, &viewport); + if (index_bytes != 0u) s.list->IASetIndexBuffer(&ib); + bool clouds_injected = false; bool cloud_depth_writing_world_seen = false; static bool cloud_trace_done = false; @@ -4368,10 +5431,13 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { if (current_target != nullptr && current_target != cloud_target) resolve_target_for_sampling(s, *current_target, false); record_clouds_into_world_target(s, *cloud_target, clouds); - // The cloud passes use their own 62-DWORD root layout. Restore - // the GE layout before recording the first FadingEntities draw; + // Private shadow/cloud passes use root layouts different from the GE + // material path. Restore the GE layout before FadingEntities; // all cached bindings below are invalidated and repopulated. s.list->SetGraphicsRootSignature(s.root_signature.Get()); + s.list->SetDescriptorHeaps(2u, descriptor_heaps); + s.list->SetGraphicsRootDescriptorTable(4u, srv_gpu( + s, material_shadow_ready ? s.realtime_shadow_map.srv_index : 0u)); current_target = cloud_target; current_address = cloud_target_address; clouds_injected = true; @@ -4380,6 +5446,7 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { bound_srv = bound_sampler = std::numeric_limits::max(); active_transform_valid = false; active_pixel_valid = false; + active_shadow_rows_valid = false; active_scissor_valid = false; active_blend_fix = std::numeric_limits::max(); active_vertex_layout_valid = false; @@ -4544,6 +5611,13 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { const Dx12TransformConstants draw_transform = make_transform_constants(batch, logical_width, logical_height); const Dx12PixelConstants pixel_state = make_pixel_constants(batch.draw, srv_index != 0u); + std::array shadow_rows{}; + if (material_shadow_ready && batch.hardware_transform && + batch.draw.shader_pipe != GeShaderPipe::Native) { + const std::array model_to_light = shadow_multiply_mat4( + material_shadow_light_vp, batch.transform.model_to_world); + shadow_matrix_rows(model_to_light, shadow_rows.data()); + } if (!active_transform_valid || std::memcmp(&draw_transform, &active_transform, sizeof(draw_transform)) != 0) { s.list->SetGraphicsRoot32BitConstants(2u, 40u, &draw_transform, 0u); @@ -4556,6 +5630,11 @@ bool ge_gpu_backend_finish_color_frame(std::uint64_t vblank) noexcept { active_pixel = pixel_state; active_pixel_valid = true; } + if (!active_shadow_rows_valid || shadow_rows != active_shadow_rows) { + s.list->SetGraphicsRoot32BitConstants(3u, 16u, shadow_rows.data(), 5u); + active_shadow_rows = shadow_rows; + active_shadow_rows_valid = true; + } } // The PSP scissor is expressed in 480x272 logical pixels and has to be @@ -4861,7 +5940,7 @@ bool ge_gpu_backend_graphics_ready() noexcept { return false; } void ge_gpu_backend_record_draw(const GeGpuDrawDescriptor &) noexcept {} void ge_gpu_backend_observe_camera(const std::array &, const std::array &, - const std::array &, + const std::array &, const std::array &, const GeGpuDrawDescriptor &, std::uint32_t) noexcept {} diff --git a/profiles/vcs/host/ge_gpu_hdr_shaders.inc b/profiles/vcs/host/ge_gpu_hdr_shaders.inc deleted file mode 100644 index 1ac7d74..0000000 --- a/profiles/vcs/host/ge_gpu_hdr_shaders.inc +++ /dev/null @@ -1,400 +0,0 @@ -// Generated by tools/build_hdr_spirv.sh. Do not edit by hand. -// -// SimulateHDR, ported from ProperShaders, itself a fork of "Blooming HDR" -// by Jose Negrete (BlueSkyDefender). CC BY-SA 4.0, changes were made; the -// full notice is in shaders/hdr/LICENSE_SIMULATEHDR.txt. -#pragma once - -#include -#include - -namespace vcs::hdr_shaders { - -inline constexpr std::array hdr_fullscreen_vert{{ - 0x07230203u, 0x00010000u, 0x000D000Bu, 0x0000002Cu, 0x00000000u, 0x00020011u, 0x00000001u, - 0x0006000Bu, 0x00000001u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u, 0x0003000Eu, - 0x00000000u, 0x00000001u, 0x0008000Fu, 0x00000000u, 0x00000004u, 0x6E69616Du, 0x00000000u, - 0x00000009u, 0x0000000Cu, 0x0000001Du, 0x00040047u, 0x00000009u, 0x0000001Eu, 0x00000000u, - 0x00040047u, 0x0000000Cu, 0x0000000Bu, 0x0000002Au, 0x00030047u, 0x0000001Bu, 0x00000002u, - 0x00050048u, 0x0000001Bu, 0x00000000u, 0x0000000Bu, 0x00000000u, 0x00050048u, 0x0000001Bu, - 0x00000001u, 0x0000000Bu, 0x00000001u, 0x00050048u, 0x0000001Bu, 0x00000002u, 0x0000000Bu, - 0x00000003u, 0x00050048u, 0x0000001Bu, 0x00000003u, 0x0000000Bu, 0x00000004u, 0x00020013u, - 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, - 0x00040017u, 0x00000007u, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000008u, 0x00000003u, - 0x00000007u, 0x0004003Bu, 0x00000008u, 0x00000009u, 0x00000003u, 0x00040015u, 0x0000000Au, - 0x00000020u, 0x00000001u, 0x00040020u, 0x0000000Bu, 0x00000001u, 0x0000000Au, 0x0004003Bu, - 0x0000000Bu, 0x0000000Cu, 0x00000001u, 0x0004002Bu, 0x0000000Au, 0x0000000Eu, 0x00000001u, - 0x0004002Bu, 0x0000000Au, 0x00000010u, 0x00000002u, 0x00040017u, 0x00000017u, 0x00000006u, - 0x00000004u, 0x00040015u, 0x00000018u, 0x00000020u, 0x00000000u, 0x0004002Bu, 0x00000018u, - 0x00000019u, 0x00000001u, 0x0004001Cu, 0x0000001Au, 0x00000006u, 0x00000019u, 0x0006001Eu, - 0x0000001Bu, 0x00000017u, 0x00000006u, 0x0000001Au, 0x0000001Au, 0x00040020u, 0x0000001Cu, - 0x00000003u, 0x0000001Bu, 0x0004003Bu, 0x0000001Cu, 0x0000001Du, 0x00000003u, 0x0004002Bu, - 0x0000000Au, 0x0000001Eu, 0x00000000u, 0x0004002Bu, 0x00000006u, 0x00000020u, 0x40000000u, - 0x0004002Bu, 0x00000006u, 0x00000022u, 0x3F800000u, 0x0004002Bu, 0x00000006u, 0x00000025u, - 0x00000000u, 0x00040020u, 0x00000029u, 0x00000003u, 0x00000017u, 0x0005002Cu, 0x00000007u, - 0x0000002Bu, 0x00000022u, 0x00000022u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, - 0x00000003u, 0x000200F8u, 0x00000005u, 0x0004003Du, 0x0000000Au, 0x0000000Du, 0x0000000Cu, - 0x000500C4u, 0x0000000Au, 0x0000000Fu, 0x0000000Du, 0x0000000Eu, 0x000500C7u, 0x0000000Au, - 0x00000011u, 0x0000000Fu, 0x00000010u, 0x0004006Fu, 0x00000006u, 0x00000012u, 0x00000011u, - 0x000500C7u, 0x0000000Au, 0x00000014u, 0x0000000Du, 0x00000010u, 0x0004006Fu, 0x00000006u, - 0x00000015u, 0x00000014u, 0x00050050u, 0x00000007u, 0x00000016u, 0x00000012u, 0x00000015u, - 0x0003003Eu, 0x00000009u, 0x00000016u, 0x0004003Du, 0x00000007u, 0x0000001Fu, 0x00000009u, - 0x0005008Eu, 0x00000007u, 0x00000021u, 0x0000001Fu, 0x00000020u, 0x00050083u, 0x00000007u, - 0x00000024u, 0x00000021u, 0x0000002Bu, 0x00050051u, 0x00000006u, 0x00000026u, 0x00000024u, - 0x00000000u, 0x00050051u, 0x00000006u, 0x00000027u, 0x00000024u, 0x00000001u, 0x00070050u, - 0x00000017u, 0x00000028u, 0x00000026u, 0x00000027u, 0x00000025u, 0x00000022u, 0x00050041u, - 0x00000029u, 0x0000002Au, 0x0000001Du, 0x0000001Eu, 0x0003003Eu, 0x0000002Au, 0x00000028u, - 0x000100FDu, 0x00010038u, -}}; - -inline constexpr std::array hdr_extract_frag{{ - 0x07230203u, 0x00010000u, 0x000D000Bu, 0x0000005Cu, 0x00000000u, 0x00020011u, 0x00000001u, - 0x0006000Bu, 0x00000001u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u, 0x0003000Eu, - 0x00000000u, 0x00000001u, 0x0007000Fu, 0x00000004u, 0x00000004u, 0x6E69616Du, 0x00000000u, - 0x0000001Du, 0x0000004Cu, 0x00030010u, 0x00000004u, 0x00000007u, 0x00040047u, 0x00000019u, - 0x00000021u, 0x00000000u, 0x00040047u, 0x00000019u, 0x00000022u, 0x00000000u, 0x00040047u, - 0x0000001Du, 0x0000001Eu, 0x00000000u, 0x00030047u, 0x00000029u, 0x00000002u, 0x00050048u, - 0x00000029u, 0x00000000u, 0x00000023u, 0x00000000u, 0x00050048u, 0x00000029u, 0x00000001u, - 0x00000023u, 0x00000004u, 0x00050048u, 0x00000029u, 0x00000002u, 0x00000023u, 0x00000008u, - 0x00040047u, 0x0000004Cu, 0x0000001Eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, - 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, - 0x00000006u, 0x00000003u, 0x0004002Bu, 0x00000006u, 0x0000000Eu, 0x3E59B3D0u, 0x0004002Bu, - 0x00000006u, 0x0000000Fu, 0x3F371759u, 0x0004002Bu, 0x00000006u, 0x00000010u, 0x3D93DD98u, - 0x0006002Cu, 0x00000007u, 0x00000011u, 0x0000000Eu, 0x0000000Fu, 0x00000010u, 0x00090019u, - 0x00000016u, 0x00000006u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, - 0x00000000u, 0x0003001Bu, 0x00000017u, 0x00000016u, 0x00040020u, 0x00000018u, 0x00000000u, - 0x00000017u, 0x0004003Bu, 0x00000018u, 0x00000019u, 0x00000000u, 0x00040017u, 0x0000001Bu, - 0x00000006u, 0x00000002u, 0x00040020u, 0x0000001Cu, 0x00000001u, 0x0000001Bu, 0x0004003Bu, - 0x0000001Cu, 0x0000001Du, 0x00000001u, 0x00040017u, 0x0000001Fu, 0x00000006u, 0x00000004u, - 0x0005001Eu, 0x00000029u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00040020u, 0x0000002Au, - 0x00000009u, 0x00000029u, 0x0004003Bu, 0x0000002Au, 0x0000002Bu, 0x00000009u, 0x00040015u, - 0x0000002Cu, 0x00000020u, 0x00000001u, 0x0004002Bu, 0x0000002Cu, 0x0000002Du, 0x00000000u, - 0x00040020u, 0x0000002Eu, 0x00000009u, 0x00000006u, 0x0004002Bu, 0x00000006u, 0x00000034u, - 0x3A83126Fu, 0x0004002Bu, 0x00000006u, 0x00000039u, 0x00000000u, 0x0004002Bu, 0x0000002Cu, - 0x0000003Bu, 0x00000001u, 0x0004002Bu, 0x0000002Cu, 0x00000046u, 0x00000002u, 0x00040020u, - 0x0000004Bu, 0x00000003u, 0x0000001Fu, 0x0004003Bu, 0x0000004Bu, 0x0000004Cu, 0x00000003u, - 0x0004002Bu, 0x00000006u, 0x0000004Eu, 0x3F800000u, 0x0006002Cu, 0x00000007u, 0x0000005Au, - 0x00000039u, 0x00000039u, 0x00000039u, 0x0006002Cu, 0x00000007u, 0x0000005Bu, 0x0000004Eu, - 0x0000004Eu, 0x0000004Eu, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, - 0x000200F8u, 0x00000005u, 0x0004003Du, 0x00000017u, 0x0000001Au, 0x00000019u, 0x0004003Du, - 0x0000001Bu, 0x0000001Eu, 0x0000001Du, 0x00050057u, 0x0000001Fu, 0x00000020u, 0x0000001Au, - 0x0000001Eu, 0x0008004Fu, 0x00000007u, 0x00000021u, 0x00000020u, 0x00000020u, 0x00000000u, - 0x00000001u, 0x00000002u, 0x00050094u, 0x00000006u, 0x00000059u, 0x00000021u, 0x00000011u, - 0x0006000Cu, 0x00000007u, 0x00000028u, 0x00000001u, 0x00000004u, 0x00000021u, 0x00050041u, - 0x0000002Eu, 0x0000002Fu, 0x0000002Bu, 0x0000002Du, 0x0004003Du, 0x00000006u, 0x00000030u, - 0x0000002Fu, 0x00060050u, 0x00000007u, 0x00000031u, 0x00000030u, 0x00000030u, 0x00000030u, - 0x0007000Cu, 0x00000007u, 0x00000032u, 0x00000001u, 0x0000001Au, 0x00000028u, 0x00000031u, - 0x0007000Cu, 0x00000006u, 0x00000035u, 0x00000001u, 0x00000028u, 0x00000059u, 0x00000034u, - 0x00060050u, 0x00000007u, 0x00000036u, 0x00000035u, 0x00000035u, 0x00000035u, 0x00050088u, - 0x00000007u, 0x00000037u, 0x00000032u, 0x00000036u, 0x00050041u, 0x0000002Eu, 0x0000003Cu, - 0x0000002Bu, 0x0000003Bu, 0x0004003Du, 0x00000006u, 0x0000003Du, 0x0000003Cu, 0x00050083u, - 0x00000006u, 0x0000003Eu, 0x00000059u, 0x0000003Du, 0x0007000Cu, 0x00000006u, 0x0000003Fu, - 0x00000001u, 0x00000028u, 0x00000039u, 0x0000003Eu, 0x0005008Eu, 0x00000007u, 0x00000042u, - 0x00000037u, 0x0000003Fu, 0x00060050u, 0x00000007u, 0x00000044u, 0x0000003Fu, 0x0000003Fu, - 0x0000003Fu, 0x00050041u, 0x0000002Eu, 0x00000047u, 0x0000002Bu, 0x00000046u, 0x0004003Du, - 0x00000006u, 0x00000048u, 0x00000047u, 0x00060050u, 0x00000007u, 0x00000049u, 0x00000048u, - 0x00000048u, 0x00000048u, 0x0008000Cu, 0x00000007u, 0x0000004Au, 0x00000001u, 0x0000002Eu, - 0x00000044u, 0x00000042u, 0x00000049u, 0x0008000Cu, 0x00000007u, 0x00000051u, 0x00000001u, - 0x0000002Bu, 0x0000004Au, 0x0000005Au, 0x0000005Bu, 0x00050051u, 0x00000006u, 0x00000052u, - 0x00000051u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000053u, 0x00000051u, 0x00000001u, - 0x00050051u, 0x00000006u, 0x00000054u, 0x00000051u, 0x00000002u, 0x00070050u, 0x0000001Fu, - 0x00000055u, 0x00000052u, 0x00000053u, 0x00000054u, 0x00000039u, 0x0003003Eu, 0x0000004Cu, - 0x00000055u, 0x000100FDu, 0x00010038u, -}}; - -inline constexpr std::array hdr_blur_frag{{ - 0x07230203u, 0x00010000u, 0x000D000Bu, 0x00000048u, 0x00000000u, 0x00020011u, 0x00000001u, - 0x0006000Bu, 0x00000001u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u, 0x0003000Eu, - 0x00000000u, 0x00000001u, 0x0007000Fu, 0x00000004u, 0x00000004u, 0x6E69616Du, 0x00000000u, - 0x0000002Du, 0x00000044u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00040047u, 0x0000001Eu, - 0x00000006u, 0x00000010u, 0x00030047u, 0x0000001Fu, 0x00000002u, 0x00050048u, 0x0000001Fu, - 0x00000000u, 0x00000023u, 0x00000000u, 0x00040047u, 0x0000002Au, 0x00000021u, 0x00000000u, - 0x00040047u, 0x0000002Au, 0x00000022u, 0x00000000u, 0x00040047u, 0x0000002Du, 0x0000001Eu, - 0x00000000u, 0x00040047u, 0x00000044u, 0x0000001Eu, 0x00000000u, 0x00020013u, 0x00000002u, - 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, - 0x00000007u, 0x00000006u, 0x00000004u, 0x0004002Bu, 0x00000006u, 0x0000000Au, 0x00000000u, - 0x0007002Cu, 0x00000007u, 0x0000000Bu, 0x0000000Au, 0x0000000Au, 0x0000000Au, 0x0000000Au, - 0x00040015u, 0x0000000Cu, 0x00000020u, 0x00000001u, 0x0004002Bu, 0x0000000Cu, 0x0000000Fu, - 0x00000000u, 0x0004002Bu, 0x0000000Cu, 0x00000016u, 0x00000004u, 0x00020014u, 0x00000017u, - 0x00040017u, 0x00000019u, 0x00000006u, 0x00000002u, 0x00040015u, 0x0000001Cu, 0x00000020u, - 0x00000000u, 0x0004002Bu, 0x0000001Cu, 0x0000001Du, 0x00000004u, 0x0004001Cu, 0x0000001Eu, - 0x00000007u, 0x0000001Du, 0x0003001Eu, 0x0000001Fu, 0x0000001Eu, 0x00040020u, 0x00000020u, - 0x00000009u, 0x0000001Fu, 0x0004003Bu, 0x00000020u, 0x00000021u, 0x00000009u, 0x00040020u, - 0x00000023u, 0x00000009u, 0x00000007u, 0x00090019u, 0x00000027u, 0x00000006u, 0x00000001u, - 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001Bu, 0x00000028u, - 0x00000027u, 0x00040020u, 0x00000029u, 0x00000000u, 0x00000028u, 0x0004003Bu, 0x00000029u, - 0x0000002Au, 0x00000000u, 0x00040020u, 0x0000002Cu, 0x00000001u, 0x00000019u, 0x0004003Bu, - 0x0000002Cu, 0x0000002Du, 0x00000001u, 0x0004002Bu, 0x0000001Cu, 0x00000039u, 0x00000002u, - 0x00040020u, 0x0000003Au, 0x00000009u, 0x00000006u, 0x0004002Bu, 0x0000000Cu, 0x00000041u, - 0x00000001u, 0x00040020u, 0x00000043u, 0x00000003u, 0x00000007u, 0x0004003Bu, 0x00000043u, - 0x00000044u, 0x00000003u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, - 0x000200F8u, 0x00000005u, 0x000200F9u, 0x00000010u, 0x000200F8u, 0x00000010u, 0x000700F5u, - 0x00000007u, 0x00000047u, 0x0000000Bu, 0x00000005u, 0x0000003Fu, 0x00000011u, 0x000700F5u, - 0x0000000Cu, 0x00000046u, 0x0000000Fu, 0x00000005u, 0x00000042u, 0x00000011u, 0x000500B1u, - 0x00000017u, 0x00000018u, 0x00000046u, 0x00000016u, 0x000400F6u, 0x00000012u, 0x00000011u, - 0x00000000u, 0x000400FAu, 0x00000018u, 0x00000011u, 0x00000012u, 0x000200F8u, 0x00000011u, - 0x00060041u, 0x00000023u, 0x00000024u, 0x00000021u, 0x0000000Fu, 0x00000046u, 0x0004003Du, - 0x00000007u, 0x00000025u, 0x00000024u, 0x0007004Fu, 0x00000019u, 0x00000026u, 0x00000025u, - 0x00000025u, 0x00000000u, 0x00000001u, 0x0004003Du, 0x00000028u, 0x0000002Bu, 0x0000002Au, - 0x0004003Du, 0x00000019u, 0x0000002Eu, 0x0000002Du, 0x00050081u, 0x00000019u, 0x00000030u, - 0x0000002Eu, 0x00000026u, 0x00050057u, 0x00000007u, 0x00000031u, 0x0000002Bu, 0x00000030u, - 0x0004003Du, 0x00000028u, 0x00000032u, 0x0000002Au, 0x00050083u, 0x00000019u, 0x00000035u, - 0x0000002Eu, 0x00000026u, 0x00050057u, 0x00000007u, 0x00000036u, 0x00000032u, 0x00000035u, - 0x00050081u, 0x00000007u, 0x00000037u, 0x00000031u, 0x00000036u, 0x00070041u, 0x0000003Au, - 0x0000003Bu, 0x00000021u, 0x0000000Fu, 0x00000046u, 0x00000039u, 0x0004003Du, 0x00000006u, - 0x0000003Cu, 0x0000003Bu, 0x0005008Eu, 0x00000007u, 0x0000003Du, 0x00000037u, 0x0000003Cu, - 0x00050081u, 0x00000007u, 0x0000003Fu, 0x00000047u, 0x0000003Du, 0x00050080u, 0x0000000Cu, - 0x00000042u, 0x00000046u, 0x00000041u, 0x000200F9u, 0x00000010u, 0x000200F8u, 0x00000012u, - 0x0003003Eu, 0x00000044u, 0x00000047u, 0x000100FDu, 0x00010038u, -}}; - -inline constexpr std::array hdr_combine_frag{{ - 0x07230203u, 0x00010000u, 0x000D000Bu, 0x0000004Au, 0x00000000u, 0x00020011u, 0x00000001u, - 0x0006000Bu, 0x00000001u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u, 0x0003000Eu, - 0x00000000u, 0x00000001u, 0x0007000Fu, 0x00000004u, 0x00000004u, 0x6E69616Du, 0x00000000u, - 0x00000011u, 0x0000003Au, 0x00030010u, 0x00000004u, 0x00000007u, 0x00040047u, 0x0000000Du, - 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000Du, 0x00000022u, 0x00000000u, 0x00040047u, - 0x00000011u, 0x0000001Eu, 0x00000000u, 0x00040047u, 0x00000016u, 0x00000021u, 0x00000001u, - 0x00040047u, 0x00000016u, 0x00000022u, 0x00000000u, 0x00040047u, 0x0000001Du, 0x00000021u, - 0x00000002u, 0x00040047u, 0x0000001Du, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000024u, - 0x00000021u, 0x00000003u, 0x00040047u, 0x00000024u, 0x00000022u, 0x00000000u, 0x00040047u, - 0x0000002Bu, 0x00000021u, 0x00000004u, 0x00040047u, 0x0000002Bu, 0x00000022u, 0x00000000u, - 0x00040047u, 0x00000032u, 0x00000021u, 0x00000005u, 0x00040047u, 0x00000032u, 0x00000022u, - 0x00000000u, 0x00040047u, 0x0000003Au, 0x0000001Eu, 0x00000000u, 0x00030047u, 0x0000003Cu, - 0x00000002u, 0x00050048u, 0x0000003Cu, 0x00000000u, 0x00000023u, 0x00000000u, 0x00020013u, - 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, - 0x00040017u, 0x00000007u, 0x00000006u, 0x00000003u, 0x00090019u, 0x0000000Au, 0x00000006u, - 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001Bu, - 0x0000000Bu, 0x0000000Au, 0x00040020u, 0x0000000Cu, 0x00000000u, 0x0000000Bu, 0x0004003Bu, - 0x0000000Cu, 0x0000000Du, 0x00000000u, 0x00040017u, 0x0000000Fu, 0x00000006u, 0x00000002u, - 0x00040020u, 0x00000010u, 0x00000001u, 0x0000000Fu, 0x0004003Bu, 0x00000010u, 0x00000011u, - 0x00000001u, 0x00040017u, 0x00000013u, 0x00000006u, 0x00000004u, 0x0004003Bu, 0x0000000Cu, - 0x00000016u, 0x00000000u, 0x0004003Bu, 0x0000000Cu, 0x0000001Du, 0x00000000u, 0x0004003Bu, - 0x0000000Cu, 0x00000024u, 0x00000000u, 0x0004003Bu, 0x0000000Cu, 0x0000002Bu, 0x00000000u, - 0x0004003Bu, 0x0000000Cu, 0x00000032u, 0x00000000u, 0x00040020u, 0x00000039u, 0x00000003u, - 0x00000013u, 0x0004003Bu, 0x00000039u, 0x0000003Au, 0x00000003u, 0x0003001Eu, 0x0000003Cu, - 0x00000006u, 0x00040020u, 0x0000003Du, 0x00000009u, 0x0000003Cu, 0x0004003Bu, 0x0000003Du, - 0x0000003Eu, 0x00000009u, 0x00040015u, 0x0000003Fu, 0x00000020u, 0x00000001u, 0x0004002Bu, - 0x0000003Fu, 0x00000040u, 0x00000000u, 0x00040020u, 0x00000041u, 0x00000009u, 0x00000006u, - 0x0004002Bu, 0x00000006u, 0x00000045u, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, - 0x00000000u, 0x00000003u, 0x000200F8u, 0x00000005u, 0x0004003Du, 0x0000000Bu, 0x0000000Eu, - 0x0000000Du, 0x0004003Du, 0x0000000Fu, 0x00000012u, 0x00000011u, 0x00050057u, 0x00000013u, - 0x00000014u, 0x0000000Eu, 0x00000012u, 0x0008004Fu, 0x00000007u, 0x00000015u, 0x00000014u, - 0x00000014u, 0x00000000u, 0x00000001u, 0x00000002u, 0x0004003Du, 0x0000000Bu, 0x00000017u, - 0x00000016u, 0x00050057u, 0x00000013u, 0x00000019u, 0x00000017u, 0x00000012u, 0x0008004Fu, - 0x00000007u, 0x0000001Au, 0x00000019u, 0x00000019u, 0x00000000u, 0x00000001u, 0x00000002u, - 0x00050081u, 0x00000007u, 0x0000001Cu, 0x00000015u, 0x0000001Au, 0x0004003Du, 0x0000000Bu, - 0x0000001Eu, 0x0000001Du, 0x00050057u, 0x00000013u, 0x00000020u, 0x0000001Eu, 0x00000012u, - 0x0008004Fu, 0x00000007u, 0x00000021u, 0x00000020u, 0x00000020u, 0x00000000u, 0x00000001u, - 0x00000002u, 0x00050081u, 0x00000007u, 0x00000023u, 0x0000001Cu, 0x00000021u, 0x0004003Du, - 0x0000000Bu, 0x00000025u, 0x00000024u, 0x00050057u, 0x00000013u, 0x00000027u, 0x00000025u, - 0x00000012u, 0x0008004Fu, 0x00000007u, 0x00000028u, 0x00000027u, 0x00000027u, 0x00000000u, - 0x00000001u, 0x00000002u, 0x00050081u, 0x00000007u, 0x0000002Au, 0x00000023u, 0x00000028u, - 0x0004003Du, 0x0000000Bu, 0x0000002Cu, 0x0000002Bu, 0x00050057u, 0x00000013u, 0x0000002Eu, - 0x0000002Cu, 0x00000012u, 0x0008004Fu, 0x00000007u, 0x0000002Fu, 0x0000002Eu, 0x0000002Eu, - 0x00000000u, 0x00000001u, 0x00000002u, 0x00050081u, 0x00000007u, 0x00000031u, 0x0000002Au, - 0x0000002Fu, 0x0004003Du, 0x0000000Bu, 0x00000033u, 0x00000032u, 0x00050057u, 0x00000013u, - 0x00000035u, 0x00000033u, 0x00000012u, 0x0008004Fu, 0x00000007u, 0x00000036u, 0x00000035u, - 0x00000035u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00050081u, 0x00000007u, 0x00000038u, - 0x00000031u, 0x00000036u, 0x00050041u, 0x00000041u, 0x00000042u, 0x0000003Eu, 0x00000040u, - 0x0004003Du, 0x00000006u, 0x00000043u, 0x00000042u, 0x0005008Eu, 0x00000007u, 0x00000044u, - 0x00000038u, 0x00000043u, 0x00050051u, 0x00000006u, 0x00000046u, 0x00000044u, 0x00000000u, - 0x00050051u, 0x00000006u, 0x00000047u, 0x00000044u, 0x00000001u, 0x00050051u, 0x00000006u, - 0x00000048u, 0x00000044u, 0x00000002u, 0x00070050u, 0x00000013u, 0x00000049u, 0x00000046u, - 0x00000047u, 0x00000048u, 0x00000045u, 0x0003003Eu, 0x0000003Au, 0x00000049u, 0x000100FDu, - 0x00010038u, -}}; - -inline constexpr std::array hdr_composite_frag{{ - 0x07230203u, 0x00010000u, 0x000D000Bu, 0x000001F3u, 0x00000000u, 0x00020011u, 0x00000001u, - 0x0006000Bu, 0x00000001u, 0x4C534C47u, 0x6474732Eu, 0x3035342Eu, 0x00000000u, 0x0003000Eu, - 0x00000000u, 0x00000001u, 0x0007000Fu, 0x00000004u, 0x00000004u, 0x6E69616Du, 0x00000000u, - 0x000000DBu, 0x00000162u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030047u, 0x0000005Bu, - 0x00000002u, 0x00050048u, 0x0000005Bu, 0x00000000u, 0x00000023u, 0x00000000u, 0x00050048u, - 0x0000005Bu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000005Bu, 0x00000002u, - 0x00000023u, 0x00000020u, 0x00050048u, 0x0000005Bu, 0x00000003u, 0x00000023u, 0x00000028u, - 0x00050048u, 0x0000005Bu, 0x00000004u, 0x00000023u, 0x0000002Cu, 0x00050048u, 0x0000005Bu, - 0x00000005u, 0x00000023u, 0x00000030u, 0x00050048u, 0x0000005Bu, 0x00000006u, 0x00000023u, - 0x00000034u, 0x00050048u, 0x0000005Bu, 0x00000007u, 0x00000023u, 0x00000038u, 0x00050048u, - 0x0000005Bu, 0x00000008u, 0x00000023u, 0x0000003Cu, 0x00050048u, 0x0000005Bu, 0x00000009u, - 0x00000023u, 0x00000040u, 0x00050048u, 0x0000005Bu, 0x0000000Au, 0x00000023u, 0x00000044u, - 0x00050048u, 0x0000005Bu, 0x0000000Bu, 0x00000023u, 0x00000048u, 0x00050048u, 0x0000005Bu, - 0x0000000Cu, 0x00000023u, 0x0000004Cu, 0x00040047u, 0x000000D8u, 0x00000021u, 0x00000001u, - 0x00040047u, 0x000000D8u, 0x00000022u, 0x00000000u, 0x00040047u, 0x000000DBu, 0x0000001Eu, - 0x00000000u, 0x00040047u, 0x000000FFu, 0x00000021u, 0x00000000u, 0x00040047u, 0x000000FFu, - 0x00000022u, 0x00000000u, 0x00040047u, 0x00000162u, 0x0000001Eu, 0x00000000u, 0x00020013u, - 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, - 0x00040017u, 0x00000007u, 0x00000006u, 0x00000003u, 0x00040017u, 0x0000000Du, 0x00000006u, - 0x00000002u, 0x0004002Bu, 0x00000006u, 0x00000022u, 0x3E59B3D0u, 0x0004002Bu, 0x00000006u, - 0x00000023u, 0x3F371759u, 0x0004002Bu, 0x00000006u, 0x00000024u, 0x3D93DD98u, 0x0006002Cu, - 0x00000007u, 0x00000025u, 0x00000022u, 0x00000023u, 0x00000024u, 0x00040015u, 0x0000002Au, - 0x00000020u, 0x00000000u, 0x0004002Bu, 0x0000002Au, 0x0000002Bu, 0x00000000u, 0x0004002Bu, - 0x00000006u, 0x0000002Eu, 0x3D897143u, 0x0004002Bu, 0x0000002Au, 0x00000030u, 0x00000001u, - 0x0004002Bu, 0x00000006u, 0x00000033u, 0x3BBF4590u, 0x0004002Bu, 0x00000006u, 0x00000036u, - 0x4253EE82u, 0x0004002Bu, 0x00000006u, 0x00000038u, 0x00000000u, 0x0004002Bu, 0x00000006u, - 0x00000039u, 0x3EA2F984u, 0x0004002Bu, 0x00000006u, 0x0000003Au, 0x3F22F984u, 0x0006002Cu, - 0x00000007u, 0x0000003Bu, 0x00000038u, 0x00000039u, 0x0000003Au, 0x0004002Bu, 0x00000006u, - 0x00000047u, 0x358637BDu, 0x0004002Bu, 0x0000002Au, 0x0000004Cu, 0x00000002u, 0x00040017u, - 0x00000059u, 0x00000006u, 0x00000004u, 0x00040015u, 0x0000005Au, 0x00000020u, 0x00000001u, - 0x000F001Eu, 0x0000005Bu, 0x00000059u, 0x00000059u, 0x0000000Du, 0x00000006u, 0x00000006u, - 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, 0x00000006u, - 0x0000005Au, 0x00040020u, 0x0000005Cu, 0x00000009u, 0x0000005Bu, 0x0004003Bu, 0x0000005Cu, - 0x0000005Du, 0x00000009u, 0x0004002Bu, 0x0000005Au, 0x0000005Eu, 0x00000000u, 0x00040020u, - 0x0000005Fu, 0x00000009u, 0x00000006u, 0x0004002Bu, 0x0000005Au, 0x0000006Eu, 0x00000001u, - 0x0004002Bu, 0x00000006u, 0x0000008Eu, 0x3CC9590Fu, 0x0004002Bu, 0x00000006u, 0x00000092u, - 0x38BDDEAFu, 0x0004002Bu, 0x00000006u, 0x00000097u, 0x3F7BD5AAu, 0x0004002Bu, 0x00000006u, - 0x0000009Au, 0x3EDDABC1u, 0x0004002Bu, 0x00000006u, 0x0000009Eu, 0x3E73CB81u, 0x0004002Bu, - 0x00000006u, 0x000000A7u, 0x3F000000u, 0x00040018u, 0x000000ABu, 0x00000007u, 0x00000003u, - 0x0004002Bu, 0x00000006u, 0x000000ACu, 0x3F18E172u, 0x0004002Bu, 0x00000006u, 0x000000ADu, - 0x3D9BA5E3u, 0x0004002Bu, 0x00000006u, 0x000000AEu, 0x3CE8A71Eu, 0x0006002Cu, 0x00000007u, - 0x000000AFu, 0x000000ACu, 0x000000ADu, 0x000000AEu, 0x0004002Bu, 0x00000006u, 0x000000B0u, - 0x3EB58B82u, 0x0004002Bu, 0x00000006u, 0x000000B1u, 0x3F6888F8u, 0x0004002Bu, 0x00000006u, - 0x000000B2u, 0x3E090ABBu, 0x0006002Cu, 0x00000007u, 0x000000B3u, 0x000000B0u, 0x000000B1u, - 0x000000B2u, 0x0004002Bu, 0x00000006u, 0x000000B4u, 0x3D458CD2u, 0x0004002Bu, 0x00000006u, - 0x000000B5u, 0x3C804966u, 0x0004002Bu, 0x00000006u, 0x000000B6u, 0x3F567818u, 0x0006002Cu, - 0x00000007u, 0x000000B7u, 0x000000B4u, 0x000000B5u, 0x000000B6u, 0x0006002Cu, 0x000000ABu, - 0x000000B8u, 0x000000AFu, 0x000000B3u, 0x000000B7u, 0x0004002Bu, 0x00000006u, 0x000000BEu, - 0x3FCD6873u, 0x0004002Bu, 0x00000006u, 0x000000BFu, 0xBDD10F52u, 0x0004002Bu, 0x00000006u, - 0x000000C0u, 0xBB564D7Fu, 0x0006002Cu, 0x00000007u, 0x000000C1u, 0x000000BEu, 0x000000BFu, - 0x000000C0u, 0x0004002Bu, 0x00000006u, 0x000000C2u, 0xBF07F4DCu, 0x0004002Bu, 0x00000006u, - 0x000000C3u, 0x3F8DD734u, 0x0004002Bu, 0x00000006u, 0x000000C4u, 0xBD950332u, 0x0006002Cu, - 0x00000007u, 0x000000C5u, 0x000000C2u, 0x000000C3u, 0x000000C4u, 0x0004002Bu, 0x00000006u, - 0x000000C6u, 0xBD96E04Cu, 0x0004002Bu, 0x00000006u, 0x000000C7u, 0xBBC63F14u, 0x0004002Bu, - 0x00000006u, 0x000000C8u, 0x3F89BB06u, 0x0006002Cu, 0x00000007u, 0x000000C9u, 0x000000C6u, - 0x000000C7u, 0x000000C8u, 0x0006002Cu, 0x000000ABu, 0x000000CAu, 0x000000C1u, 0x000000C5u, - 0x000000C9u, 0x0004002Bu, 0x0000005Au, 0x000000CEu, 0x0000000Bu, 0x00090019u, 0x000000D5u, - 0x00000006u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, - 0x0003001Bu, 0x000000D6u, 0x000000D5u, 0x00040020u, 0x000000D7u, 0x00000000u, 0x000000D6u, - 0x0004003Bu, 0x000000D7u, 0x000000D8u, 0x00000000u, 0x00040020u, 0x000000DAu, 0x00000001u, - 0x0000000Du, 0x0004003Bu, 0x000000DAu, 0x000000DBu, 0x00000001u, 0x0004002Bu, 0x0000005Au, - 0x000000E1u, 0x00000002u, 0x00040020u, 0x000000E2u, 0x00000009u, 0x0000000Du, 0x0004002Bu, - 0x00000006u, 0x000000EAu, 0x3DCCCCCDu, 0x0004002Bu, 0x0000005Au, 0x000000F0u, 0x00000003u, - 0x0004002Bu, 0x00000006u, 0x000000F5u, 0x3F800000u, 0x0004002Bu, 0x0000005Au, 0x000000F9u, - 0x00000004u, 0x0004003Bu, 0x000000D7u, 0x000000FFu, 0x00000000u, 0x0004002Bu, 0x0000005Au, - 0x00000104u, 0x0000000Cu, 0x00040020u, 0x00000105u, 0x00000009u, 0x0000005Au, 0x00020014u, - 0x00000108u, 0x0004002Bu, 0x0000005Au, 0x00000111u, 0x00000009u, 0x0004002Bu, 0x0000005Au, - 0x00000118u, 0x00000007u, 0x0006002Cu, 0x00000007u, 0x0000011Du, 0x00000038u, 0x00000038u, - 0x00000038u, 0x0004002Bu, 0x0000005Au, 0x00000123u, 0x00000005u, 0x0004002Bu, 0x00000006u, - 0x00000126u, 0x3A83126Fu, 0x0004002Bu, 0x0000005Au, 0x0000013Du, 0x00000006u, 0x0004002Bu, - 0x0000005Au, 0x0000014Eu, 0x00000008u, 0x0004002Bu, 0x0000005Au, 0x0000015Bu, 0x0000000Au, - 0x00040020u, 0x00000161u, 0x00000003u, 0x00000059u, 0x0004003Bu, 0x00000161u, 0x00000162u, - 0x00000003u, 0x0006002Cu, 0x00000007u, 0x000001ECu, 0x000000EAu, 0x000000EAu, 0x000000EAu, - 0x0006002Cu, 0x00000007u, 0x000001EDu, 0x000000F5u, 0x000000F5u, 0x000000F5u, 0x0006002Cu, - 0x00000007u, 0x000001EEu, 0x0000008Eu, 0x0000008Eu, 0x0000008Eu, 0x0006002Cu, 0x00000007u, - 0x000001EFu, 0x00000092u, 0x00000092u, 0x00000092u, 0x0006002Cu, 0x00000007u, 0x000001F0u, - 0x0000009Au, 0x0000009Au, 0x0000009Au, 0x0006002Cu, 0x00000007u, 0x000001F1u, 0x0000009Eu, - 0x0000009Eu, 0x0000009Eu, 0x0006002Cu, 0x00000007u, 0x000001F2u, 0x000000A7u, 0x000000A7u, - 0x000000A7u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200F8u, - 0x00000005u, 0x0004003Du, 0x000000D6u, 0x000000D9u, 0x000000D8u, 0x0004003Du, 0x0000000Du, - 0x000000DCu, 0x000000DBu, 0x00050057u, 0x00000059u, 0x000000DDu, 0x000000D9u, 0x000000DCu, - 0x0008004Fu, 0x00000007u, 0x000000DEu, 0x000000DDu, 0x000000DDu, 0x00000000u, 0x00000001u, - 0x00000002u, 0x00050041u, 0x000000E2u, 0x000000E3u, 0x0000005Du, 0x000000E1u, 0x0004003Du, - 0x0000000Du, 0x000000E4u, 0x000000E3u, 0x00050085u, 0x0000000Du, 0x000000E5u, 0x000000DCu, - 0x000000E4u, 0x00050051u, 0x00000006u, 0x0000016Cu, 0x000000E5u, 0x00000000u, 0x00050085u, - 0x00000006u, 0x0000016Du, 0x0000016Cu, 0x0000002Eu, 0x00050051u, 0x00000006u, 0x0000016Fu, - 0x000000E5u, 0x00000001u, 0x00050085u, 0x00000006u, 0x00000170u, 0x0000016Fu, 0x00000033u, - 0x00050081u, 0x00000006u, 0x00000171u, 0x0000016Du, 0x00000170u, 0x00060050u, 0x00000007u, - 0x00000173u, 0x00000171u, 0x00000171u, 0x00000171u, 0x00050081u, 0x00000007u, 0x00000174u, - 0x00000173u, 0x0000003Bu, 0x0006000Cu, 0x00000007u, 0x00000175u, 0x00000001u, 0x0000000Au, - 0x00000174u, 0x0005008Eu, 0x00000007u, 0x00000176u, 0x00000175u, 0x00000036u, 0x0006000Cu, - 0x00000007u, 0x00000177u, 0x00000001u, 0x0000000Au, 0x00000176u, 0x0008000Cu, 0x00000007u, - 0x000000EEu, 0x00000001u, 0x00000031u, 0x0000011Du, 0x000001ECu, 0x000000DEu, 0x00050085u, - 0x00000007u, 0x000000EFu, 0x00000177u, 0x000000EEu, 0x00050041u, 0x0000005Fu, 0x000000F1u, - 0x0000005Du, 0x000000F0u, 0x0004003Du, 0x00000006u, 0x000000F2u, 0x000000F1u, 0x0005008Eu, - 0x00000007u, 0x000000F3u, 0x000000EFu, 0x000000F2u, 0x00050081u, 0x00000007u, 0x000000F4u, - 0x000000DEu, 0x000000F3u, 0x0008000Cu, 0x00000007u, 0x000000F8u, 0x00000001u, 0x0000002Bu, - 0x000000F4u, 0x0000011Du, 0x000001EDu, 0x00050041u, 0x0000005Fu, 0x000000FAu, 0x0000005Du, - 0x000000F9u, 0x0004003Du, 0x00000006u, 0x000000FBu, 0x000000FAu, 0x0005008Eu, 0x00000007u, - 0x000000FDu, 0x000000F8u, 0x000000FBu, 0x0004003Du, 0x000000D6u, 0x00000100u, 0x000000FFu, - 0x00050057u, 0x00000059u, 0x00000102u, 0x00000100u, 0x000000DCu, 0x0008004Fu, 0x00000007u, - 0x00000103u, 0x00000102u, 0x00000102u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00050041u, - 0x00000105u, 0x00000106u, 0x0000005Du, 0x00000104u, 0x0004003Du, 0x0000005Au, 0x00000107u, - 0x00000106u, 0x000500AAu, 0x00000108u, 0x00000109u, 0x00000107u, 0x0000006Eu, 0x000300F7u, - 0x0000010Bu, 0x00000000u, 0x000400FAu, 0x00000109u, 0x0000010Au, 0x0000010Bu, 0x000200F8u, - 0x0000010Au, 0x00050094u, 0x00000006u, 0x0000017Bu, 0x00000103u, 0x00000025u, 0x00060050u, - 0x00000007u, 0x0000010Fu, 0x0000017Bu, 0x0000017Bu, 0x0000017Bu, 0x00050041u, 0x0000005Fu, - 0x00000112u, 0x0000005Du, 0x00000111u, 0x0004003Du, 0x00000006u, 0x00000113u, 0x00000112u, - 0x00060050u, 0x00000007u, 0x00000114u, 0x00000113u, 0x00000113u, 0x00000113u, 0x0008000Cu, - 0x00000007u, 0x00000115u, 0x00000001u, 0x0000002Eu, 0x0000010Fu, 0x00000103u, 0x00000114u, - 0x000200F9u, 0x0000010Bu, 0x000200F8u, 0x0000010Bu, 0x000700F5u, 0x00000007u, 0x000001E9u, - 0x00000103u, 0x00000005u, 0x00000115u, 0x0000010Au, 0x0006000Cu, 0x00000007u, 0x00000117u, - 0x00000001u, 0x00000004u, 0x000001E9u, 0x00050041u, 0x0000005Fu, 0x00000119u, 0x0000005Du, - 0x00000118u, 0x0004003Du, 0x00000006u, 0x0000011Au, 0x00000119u, 0x00060050u, 0x00000007u, - 0x0000011Bu, 0x0000011Au, 0x0000011Au, 0x0000011Au, 0x0007000Cu, 0x00000007u, 0x0000011Cu, - 0x00000001u, 0x0000001Au, 0x00000117u, 0x0000011Bu, 0x00050081u, 0x00000007u, 0x00000120u, - 0x0000011Cu, 0x000000FDu, 0x0007000Cu, 0x00000007u, 0x00000121u, 0x00000001u, 0x00000028u, - 0x0000011Du, 0x00000120u, 0x00050041u, 0x0000005Fu, 0x00000124u, 0x0000005Du, 0x00000123u, - 0x0004003Du, 0x00000006u, 0x00000125u, 0x00000124u, 0x0007000Cu, 0x00000006u, 0x00000127u, - 0x00000001u, 0x00000028u, 0x00000125u, 0x00000126u, 0x00050081u, 0x00000006u, 0x00000128u, - 0x000000F5u, 0x00000127u, 0x00050051u, 0x00000006u, 0x0000012Au, 0x00000121u, 0x00000000u, - 0x00050051u, 0x00000006u, 0x0000012Cu, 0x00000121u, 0x00000001u, 0x00050051u, 0x00000006u, - 0x0000012Eu, 0x00000121u, 0x00000002u, 0x0007000Cu, 0x00000006u, 0x0000012Fu, 0x00000001u, - 0x00000028u, 0x0000012Cu, 0x0000012Eu, 0x0007000Cu, 0x00000006u, 0x00000130u, 0x00000001u, - 0x00000028u, 0x0000012Au, 0x0000012Fu, 0x00050083u, 0x00000006u, 0x00000131u, 0x00000128u, - 0x00000130u, 0x0007000Cu, 0x00000006u, 0x00000133u, 0x00000001u, 0x00000028u, 0x00000131u, - 0x00000126u, 0x00050088u, 0x00000006u, 0x00000134u, 0x000000F5u, 0x00000133u, 0x0005008Eu, - 0x00000007u, 0x00000136u, 0x00000121u, 0x00000134u, 0x000500AAu, 0x00000108u, 0x00000139u, - 0x00000107u, 0x0000005Eu, 0x000300F7u, 0x0000013Cu, 0x00000000u, 0x000400FAu, 0x00000139u, - 0x0000013Bu, 0x00000144u, 0x000200F8u, 0x0000013Bu, 0x00050041u, 0x0000005Fu, 0x00000141u, - 0x0000005Du, 0x0000013Du, 0x0004003Du, 0x00000006u, 0x00000142u, 0x00000141u, 0x0005008Eu, - 0x00000007u, 0x00000184u, 0x00000136u, 0x00000142u, 0x00050051u, 0x00000006u, 0x00000186u, - 0x00000184u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000188u, 0x00000184u, 0x00000001u, - 0x00050051u, 0x00000006u, 0x0000018Au, 0x00000184u, 0x00000002u, 0x0007000Cu, 0x00000006u, - 0x0000018Bu, 0x00000001u, 0x00000028u, 0x00000188u, 0x0000018Au, 0x0007000Cu, 0x00000006u, - 0x0000018Cu, 0x00000001u, 0x00000028u, 0x00000186u, 0x0000018Bu, 0x0007000Cu, 0x00000006u, - 0x0000018Du, 0x00000001u, 0x00000028u, 0x00000047u, 0x0000018Cu, 0x00060050u, 0x00000007u, - 0x00000190u, 0x0000018Du, 0x0000018Du, 0x0000018Du, 0x00050088u, 0x00000007u, 0x00000191u, - 0x00000184u, 0x00000190u, 0x00060041u, 0x0000005Fu, 0x00000193u, 0x0000005Du, 0x0000005Eu, - 0x0000002Bu, 0x0004003Du, 0x00000006u, 0x00000194u, 0x00000193u, 0x0007000Cu, 0x00000006u, - 0x00000195u, 0x00000001u, 0x0000001Au, 0x0000018Du, 0x00000194u, 0x00060041u, 0x0000005Fu, - 0x00000198u, 0x0000005Du, 0x0000005Eu, 0x00000030u, 0x0004003Du, 0x00000006u, 0x00000199u, - 0x00000198u, 0x00050085u, 0x00000006u, 0x0000019Au, 0x00000195u, 0x00000199u, 0x00060041u, - 0x0000005Fu, 0x0000019Bu, 0x0000005Du, 0x0000005Eu, 0x0000004Cu, 0x0004003Du, 0x00000006u, - 0x0000019Cu, 0x0000019Bu, 0x00050081u, 0x00000006u, 0x0000019Du, 0x0000019Au, 0x0000019Cu, - 0x00050088u, 0x00000006u, 0x0000019Eu, 0x00000195u, 0x0000019Du, 0x0006000Cu, 0x00000007u, - 0x000001A0u, 0x00000001u, 0x00000004u, 0x00000191u, 0x00060041u, 0x0000005Fu, 0x000001A1u, - 0x0000005Du, 0x0000006Eu, 0x0000002Bu, 0x0004003Du, 0x00000006u, 0x000001A2u, 0x000001A1u, - 0x00060050u, 0x00000007u, 0x000001A3u, 0x000001A2u, 0x000001A2u, 0x000001A2u, 0x0007000Cu, - 0x00000007u, 0x000001A4u, 0x00000001u, 0x0000001Au, 0x000001A0u, 0x000001A3u, 0x00050085u, - 0x00000006u, 0x000001A7u, 0x0000019Eu, 0x0000019Eu, 0x00060041u, 0x0000005Fu, 0x000001A9u, - 0x0000005Du, 0x0000006Eu, 0x0000004Cu, 0x0004003Du, 0x00000006u, 0x000001AAu, 0x000001A9u, - 0x00060050u, 0x00000007u, 0x000001ABu, 0x000001AAu, 0x000001AAu, 0x000001AAu, 0x00050085u, - 0x00000006u, 0x000001AEu, 0x000001A7u, 0x000001A7u, 0x00060050u, 0x00000007u, 0x000001AFu, - 0x000001AEu, 0x000001AEu, 0x000001AEu, 0x0008000Cu, 0x00000007u, 0x000001B0u, 0x00000001u, - 0x0000002Eu, 0x000001A4u, 0x000001ABu, 0x000001AFu, 0x0006000Cu, 0x00000007u, 0x000001B2u, - 0x00000001u, 0x00000004u, 0x000001B0u, 0x00060041u, 0x0000005Fu, 0x000001B3u, 0x0000005Du, - 0x0000006Eu, 0x00000030u, 0x0004003Du, 0x00000006u, 0x000001B4u, 0x000001B3u, 0x00060050u, - 0x00000007u, 0x000001B5u, 0x000001B4u, 0x000001B4u, 0x000001B4u, 0x0007000Cu, 0x00000007u, - 0x000001B6u, 0x00000001u, 0x0000001Au, 0x000001B2u, 0x000001B5u, 0x0005008Eu, 0x00000007u, - 0x000001B9u, 0x000001B6u, 0x0000019Eu, 0x000200F9u, 0x0000013Cu, 0x000200F8u, 0x00000144u, - 0x00050041u, 0x0000005Fu, 0x00000148u, 0x0000005Du, 0x0000013Du, 0x0004003Du, 0x00000006u, - 0x00000149u, 0x00000148u, 0x00050081u, 0x00000006u, 0x000001BEu, 0x00000149u, 0x000000A7u, - 0x0005008Eu, 0x00000007u, 0x000001C0u, 0x00000136u, 0x000001BEu, 0x00050091u, 0x00000007u, - 0x000001C2u, 0x000000B8u, 0x000001C0u, 0x00050081u, 0x00000007u, 0x000001D2u, 0x000001C2u, - 0x000001EEu, 0x00050085u, 0x00000007u, 0x000001D3u, 0x000001C2u, 0x000001D2u, 0x00050083u, - 0x00000007u, 0x000001D5u, 0x000001D3u, 0x000001EFu, 0x0005008Eu, 0x00000007u, 0x000001D8u, - 0x000001C2u, 0x00000097u, 0x00050081u, 0x00000007u, 0x000001DAu, 0x000001D8u, 0x000001F0u, - 0x00050085u, 0x00000007u, 0x000001DBu, 0x000001C2u, 0x000001DAu, 0x00050081u, 0x00000007u, - 0x000001DDu, 0x000001DBu, 0x000001F1u, 0x00050088u, 0x00000007u, 0x000001E0u, 0x000001D5u, - 0x000001DDu, 0x00050091u, 0x00000007u, 0x000001C6u, 0x000000CAu, 0x000001E0u, 0x00050041u, - 0x0000005Fu, 0x000001C8u, 0x0000005Du, 0x000000CEu, 0x0004003Du, 0x00000006u, 0x000001C9u, - 0x000001C8u, 0x0005008Eu, 0x00000007u, 0x000001CAu, 0x000001C6u, 0x000001C9u, 0x000200F9u, - 0x0000013Cu, 0x000200F8u, 0x0000013Cu, 0x000700F5u, 0x00000007u, 0x000001EAu, 0x000001B9u, - 0x0000013Bu, 0x000001CAu, 0x00000144u, 0x0006000Cu, 0x00000007u, 0x0000014Du, 0x00000001u, - 0x00000004u, 0x000001EAu, 0x00050041u, 0x0000005Fu, 0x0000014Fu, 0x0000005Du, 0x0000014Eu, - 0x0004003Du, 0x00000006u, 0x00000150u, 0x0000014Fu, 0x00060050u, 0x00000007u, 0x00000151u, - 0x00000150u, 0x00000150u, 0x00000150u, 0x0007000Cu, 0x00000007u, 0x00000152u, 0x00000001u, - 0x0000001Au, 0x0000014Du, 0x00000151u, 0x000300F7u, 0x00000157u, 0x00000000u, 0x000400FAu, - 0x00000109u, 0x00000156u, 0x00000157u, 0x000200F8u, 0x00000156u, 0x00050083u, 0x00000007u, - 0x0000015Au, 0x00000152u, 0x000001F2u, 0x00050041u, 0x0000005Fu, 0x0000015Cu, 0x0000005Du, - 0x0000015Bu, 0x0004003Du, 0x00000006u, 0x0000015Du, 0x0000015Cu, 0x0005008Eu, 0x00000007u, - 0x0000015Eu, 0x0000015Au, 0x0000015Du, 0x00050081u, 0x00000007u, 0x00000160u, 0x0000015Eu, - 0x000001F2u, 0x000200F9u, 0x00000157u, 0x000200F8u, 0x00000157u, 0x000700F5u, 0x00000007u, - 0x000001EBu, 0x00000152u, 0x0000013Cu, 0x00000160u, 0x00000156u, 0x00050051u, 0x00000006u, - 0x00000164u, 0x000001EBu, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000165u, 0x000001EBu, - 0x00000001u, 0x00050051u, 0x00000006u, 0x00000166u, 0x000001EBu, 0x00000002u, 0x00070050u, - 0x00000059u, 0x00000167u, 0x00000164u, 0x00000165u, 0x00000166u, 0x000000F5u, 0x0003003Eu, - 0x00000162u, 0x00000167u, 0x000100FDu, 0x00010038u, -}}; - -} // namespace vcs::hdr_shaders diff --git a/profiles/vcs/host/ge_renderer.cpp b/profiles/vcs/host/ge_renderer.cpp index 680b9cb..7406bf6 100644 --- a/profiles/vcs/host/ge_renderer.cpp +++ b/profiles/vcs/host/ge_renderer.cpp @@ -1,5 +1,6 @@ #include "ge_renderer.hpp" #include "ge_gpu_backend.hpp" +#include "propershaders/ProperShadersBridge.hpp" #include "vcs_config.hpp" #include "vcs_project2dfx.hpp" #include "vcs_fps_overlay.hpp" @@ -1008,6 +1009,7 @@ GeGpuHardwareTransform build_gpu_hardware_transform( 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f} : affine_4x3_to_mat4(transform.world); + hw.model_to_world = world; const auto view = affine_4x3_to_mat4(transform.view); const auto model_to_view = multiply_mat4(view, world); // No widescreen correction on this matrix: transform.projection was already @@ -4209,14 +4211,21 @@ bool render_ge_primitive(psprecomp::GuestMemory &memory, } gpu_draw.texture_content_signature = any_signature ? signature : 0u; } + // Semantic pipe tag is resolved only after the mutable CLUT checksum is + // known. Skin is exact from the PSP weight format; vehicle recognition + // can learn the palette mutation VCS uses for recolourable cars. + gpu_draw.shader_pipe = proper_shaders_classify_draw( + gpu_draw, layout.weight_type != 0u); ge_gpu_backend_record_draw(gpu_draw); if (!gpu_draw.through && !gpu_draw.clear_mode && primitive >= 3u && primitive <= 5u) { - const std::array cloud_viewport{ + const std::array cloud_viewport{ decode_float24(data24(commands[0x42u])), decode_float24(data24(commands[0x43u])), + decode_float24(data24(commands[0x44u])), decode_float24(data24(commands[0x45u])), decode_float24(data24(commands[0x46u])), + decode_float24(data24(commands[0x47u])), static_cast(data24(commands[0x4Cu]) & 0xFFFFu) / 16.0f, static_cast(data24(commands[0x4Du]) & 0xFFFFu) / 16.0f}; // VCS' authoritative camera origin. The affine GE view used by diff --git a/profiles/vcs/host/main.cpp b/profiles/vcs/host/main.cpp index 703d080..902b9d4 100644 --- a/profiles/vcs/host/main.cpp +++ b/profiles/vcs/host/main.cpp @@ -10,6 +10,7 @@ #include "vcs_texture_replacement.hpp" #include "vcs_bootstrap_paths.hpp" #include "vcs_project2dfx.hpp" +#include "vcs_draw_distance_patch.hpp" #include "vcs_hdr_post.hpp" #include "vcs_runtime_log.hpp" @@ -215,10 +216,11 @@ int main(int argc, char **argv) { const std::uint32_t user_arena_start = static_cast((image_end + 0xFFu) & ~0xFFull); psprecomp::register_generated_functions(runtime); + vcs::install_draw_distance_patch(runtime, configuration.source_path); vcs::install_project2dfx(runtime, configuration.source_path, 0u); - // Reads [SimulateHDR] out of the same ini. The effect itself is built - // lazily on the first frame the Vulkan backend records. - vcs::hdr_post_configure(configuration.source_path); + // Legacy HDR configuration, if re-enabled later, belongs with the + // optional shader layer rather than core VCSNative.ini. + vcs::hdr_post_configure(executable_directory / "ProperShaders.ini"); vcs::install_profile(runtime, user_arena_start); std::string gpu_backend_error; diff --git a/profiles/vcs/host/propershaders/PrivateShaderStubs.cpp b/profiles/vcs/host/propershaders/PrivateShaderStubs.cpp new file mode 100644 index 0000000..70abdb9 --- /dev/null +++ b/profiles/vcs/host/propershaders/PrivateShaderStubs.cpp @@ -0,0 +1,11 @@ +#include "propershaders/ProperShadersBridge.hpp" + +namespace vcs { +bool proper_shaders_private_available() noexcept { return false; } +std::string proper_shaders_make_pipe_hlsl(GeShaderPipe, std::string_view native_ge_hlsl, bool) { + return std::string(native_ge_hlsl); +} +const char *proper_shaders_volumetric_clouds_hlsl() noexcept { return nullptr; } +const char *proper_shaders_realtime_shadows_hlsl() noexcept { return nullptr; } +const char *proper_shaders_material_shadow_hlsl() noexcept { return nullptr; } +} // namespace vcs diff --git a/profiles/vcs/host/propershaders/ProperShadersBridge.cpp b/profiles/vcs/host/propershaders/ProperShadersBridge.cpp new file mode 100644 index 0000000..d533590 --- /dev/null +++ b/profiles/vcs/host/propershaders/ProperShadersBridge.cpp @@ -0,0 +1,60 @@ +#include "propershaders/ProperShadersBridge.hpp" +#include "vcs_config.hpp" + +#include + +namespace vcs { +namespace { +struct PaletteIdentity { + std::uint32_t checksum{}; + bool mutated{}; + std::uint64_t last_epoch{}; +}; +std::unordered_map g_palettes; +std::uint64_t g_epoch{}; + +std::uint64_t palette_key(const GeGpuDrawDescriptor &draw) noexcept { + std::uint64_t key = static_cast(draw.texture_address) << 32u; + key ^= static_cast(draw.clut_address); + key ^= static_cast(draw.texture_format & 0xFu) << 60u; + return key; +} +} // namespace + +void proper_shaders_begin_frame(std::uint64_t frame_epoch) noexcept { + g_epoch = frame_epoch; + // Streaming constantly changes addresses. Keep the classifier bounded. + if ((frame_epoch % 600u) == 0u && g_palettes.size() > 4096u) g_palettes.clear(); +} + +GeShaderPipe proper_shaders_classify_draw(GeGpuDrawDescriptor &draw, bool skinned) noexcept { + draw.shader_pipe = GeShaderPipe::Native; + const auto &cfg = vcs_configuration().proper_shaders; + if (!cfg.enabled || !proper_shaders_private_available() || + draw.through || draw.clear_mode || draw.primitive < 3u || draw.primitive > 5u) + return draw.shader_pipe; + + if (skinned && cfg.skin_pipe.enabled) { + draw.shader_pipe = GeShaderPipe::Skin; + return draw.shader_pipe; + } + + if (cfg.vehicle_pipe.enabled && cfg.vehicle_pipe.palette_classifier && + draw.texture_enabled && draw.texture_format >= 4u && draw.texture_format <= 7u && + draw.clut_address != 0u && draw.clut_checksum != 0u) { + auto &identity = g_palettes[palette_key(draw)]; + if (identity.checksum != 0u && identity.checksum != draw.clut_checksum) + identity.mutated = true; + identity.checksum = draw.clut_checksum; + identity.last_epoch = g_epoch; + if (identity.mutated) { + draw.shader_pipe = GeShaderPipe::Vehicle; + return draw.shader_pipe; + } + } + + if (cfg.building_pipe.enabled) draw.shader_pipe = GeShaderPipe::Building; + return draw.shader_pipe; +} + +} // namespace vcs diff --git a/profiles/vcs/host/propershaders/ProperShadersBridge.hpp b/profiles/vcs/host/propershaders/ProperShadersBridge.hpp new file mode 100644 index 0000000..af7fcb9 --- /dev/null +++ b/profiles/vcs/host/propershaders/ProperShadersBridge.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "ge_gpu_backend.hpp" + +#include +#include +#include + +namespace vcs { + +// Host semantic classification. It is deliberately small and game-profile +// local: the generic PSP renderer only knows GE state, while VCS decides which +// custom pipe a draw belongs to. +[[nodiscard]] GeShaderPipe proper_shaders_classify_draw(GeGpuDrawDescriptor &draw, + bool skinned) noexcept; +void proper_shaders_begin_frame(std::uint64_t frame_epoch) noexcept; + +// Optional private shader module. Public/GitHub builds link the stubs and keep +// the native PSP shader unchanged. +[[nodiscard]] bool proper_shaders_private_available() noexcept; +[[nodiscard]] std::string proper_shaders_make_pipe_hlsl( + GeShaderPipe pipe, std::string_view native_ge_hlsl, + bool material_shadows = true); +[[nodiscard]] const char *proper_shaders_volumetric_clouds_hlsl() noexcept; +[[nodiscard]] const char *proper_shaders_realtime_shadows_hlsl() noexcept; +[[nodiscard]] const char *proper_shaders_material_shadow_hlsl() noexcept; + +} // namespace vcs diff --git a/profiles/vcs/host/propershaders/ProperShadersConfig.cpp b/profiles/vcs/host/propershaders/ProperShadersConfig.cpp new file mode 100644 index 0000000..3622202 --- /dev/null +++ b/profiles/vcs/host/propershaders/ProperShadersConfig.cpp @@ -0,0 +1,108 @@ +#include "propershaders/ProperShadersConfig.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace vcs { +namespace { +std::string trim(std::string value) { + const auto ok=[](unsigned char c){return std::isspace(c)==0;}; + value.erase(value.begin(),std::find_if(value.begin(),value.end(),ok)); + value.erase(std::find_if(value.rbegin(),value.rend(),ok).base(),value.end()); + return value; +} +std::string lower(std::string value) { + std::transform(value.begin(),value.end(),value.begin(),[](unsigned char c){return static_cast(std::tolower(c));}); + return value; +} +std::string uncomment(std::string value) { + bool quoted=false; char q='\0'; + for(std::size_t i=0;ihi)return false;out=v;return true;} +bool integer(std::string value,std::uint32_t lo,std::uint32_t hi,std::uint32_t &out){value=trim(std::move(value));char *end=nullptr;errno=0;unsigned long v=std::strtoul(value.c_str(),&end,10);if(errno!=0||end==value.c_str()||*end!='\0'||vhi)return false;out=static_cast(v);return true;} +void warn(std::vector &warnings,std::size_t line,const std::string &text){std::ostringstream s;s<<"line "< &warnings) { + std::ifstream input(path); if(!input)return; + std::string section,line; std::size_t line_no=0; + while(std::getline(input,line)){ + ++line_no; line=uncomment(std::move(line)); if(line.empty())continue; + if(line.front()=='['&&line.back()==']'){section=lower(trim(line.substr(1,line.size()-2)));continue;} + const auto eq=line.find('='); if(eq==std::string::npos){warn(warnings,line_no,"expected key=value");continue;} + const std::string key=lower(trim(line.substr(0,eq))); const std::string value=uncomment(line.substr(eq+1)); + auto b=[&](bool &dst,const char *name){if(!boolean(value,dst))warn(warnings,line_no,std::string(name)+" expects true/false");}; + auto f=[&](float &dst,float lo,float hi,const char *name){if(!real(value,lo,hi,dst))warn(warnings,line_no,std::string("invalid ")+name);}; + auto u=[&](std::uint32_t &dst,std::uint32_t lo,std::uint32_t hi,const char *name){if(!integer(value,lo,hi,dst))warn(warnings,line_no,std::string("invalid ")+name);}; + if(section=="propershaders"||section=="proper shaders") { if(key=="enabled") b(cfg.enabled,"Enabled"); else warn(warnings,line_no,"unknown [ProperShaders] key '"+key+"'"); } + else if(section=="buildingpipe"||section=="building pipe"||section=="worldpipe"||section=="world pipe") { + auto &c=cfg.building_pipe; + if(key=="enabled") b(c.enabled,"Enabled"); + else if(key=="debug"||key=="debugcolor") b(c.debug_color,"DebugColor"); + else if(key=="debugr") f(c.debug_r,0.0f,1.0f,"DebugR"); + else if(key=="debugg") f(c.debug_g,0.0f,1.0f,"DebugG"); + else if(key=="debugb") f(c.debug_b,0.0f,1.0f,"DebugB"); + else warn(warnings,line_no,"unknown [WorldPipe/BuildingPipe] key '"+key+"'"); + } + else if(section=="skinpipe"||section=="skin pipe") { + auto &c=cfg.skin_pipe; + if(key=="enabled") b(c.enabled,"Enabled"); + else if(key=="debug"||key=="debugcolor") b(c.debug_color,"DebugColor"); + else if(key=="debugr") f(c.debug_r,0.0f,1.0f,"DebugR"); + else if(key=="debugg") f(c.debug_g,0.0f,1.0f,"DebugG"); + else if(key=="debugb") f(c.debug_b,0.0f,1.0f,"DebugB"); + else warn(warnings,line_no,"unknown [SkinPipe] key '"+key+"'"); + } + else if(section=="vehiclepipe"||section=="vehicle pipe") { + auto &c=cfg.vehicle_pipe; + if(key=="enabled") b(c.enabled,"Enabled"); + else if(key=="paletteclassifier") b(c.palette_classifier,"PaletteClassifier"); + else if(key=="debug"||key=="debugcolor") b(c.debug_color,"DebugColor"); + else if(key=="debugr") f(c.debug_r,0.0f,1.0f,"DebugR"); + else if(key=="debugg") f(c.debug_g,0.0f,1.0f,"DebugG"); + else if(key=="debugb") f(c.debug_b,0.0f,1.0f,"DebugB"); + else warn(warnings,line_no,"unknown [VehiclePipe] key '"+key+"'"); + } + else if(section=="realtimeshadows"||section=="realtime shadows") { + auto &c=cfg.realtime_shadows; + if(key=="enabled") b(c.enabled,"Enabled"); + else if(key=="mapresolution") u(c.map_resolution,512u,4096u,"MapResolution"); + else if(key=="worldradius") f(c.world_radius,20.0f,1000.0f,"WorldRadius"); + else if(key=="depthrange") f(c.depth_range,40.0f,2000.0f,"DepthRange"); + else if(key=="mapbias") f(c.map_bias,0.000001f,0.05f,"MapBias"); + else if(key=="pcfradius") u(c.pcf_radius,0u,3u,"PcfRadius"); + else if(key=="usecloudsundirection") b(c.use_cloud_sun_direction,"UseCloudSunDirection"); + else if(key=="lightdirectionx") f(c.light_direction_x,-1.0f,1.0f,"LightDirectionX"); + else if(key=="lightdirectiony") f(c.light_direction_y,-1.0f,1.0f,"LightDirectionY"); + else if(key=="lightdirectionz") f(c.light_direction_z,-1.0f,1.0f,"LightDirectionZ"); + else if(key=="strength") f(c.strength,0.0f,1.0f,"Strength"); + else if(key=="debugmode") u(c.debug_mode,0u,6u,"DebugMode"); + else if(key=="contactshadows") b(c.contact_shadows,"ContactShadows"); + else if(key=="steps") u(c.steps,2u,32u,"Steps"); + else if(key=="maxdistancepixels") f(c.max_distance_pixels,1.0f,256.0f,"MaxDistancePixels"); + else if(key=="thickness") f(c.thickness,0.00001f,0.1f,"Thickness"); + else if(key=="contactstrength") f(c.contact_strength,0.0f,1.0f,"ContactStrength"); + else if(key=="lightscreenx") f(c.light_screen_x,-2.0f,2.0f,"LightScreenX"); + else if(key=="lightscreeny") f(c.light_screen_y,-2.0f,2.0f,"LightScreenY"); + else warn(warnings,line_no,"unknown [RealtimeShadows] key '"+key+"'"); + } + else if(section=="volumetricclouds"||section=="volumetric clouds") { + auto &c=cfg.volumetric_clouds; + if(key=="enabled")b(c.enabled,"Enabled"); else if(key=="downscalediv")u(c.downscale_div,1,8,"DownscaleDiv"); else if(key=="layers")u(c.layers,1,3,"Layers"); else if(key=="shadowsteps")u(c.shadow_steps,2,8,"ShadowSteps"); + else if(key=="coveragelow")f(c.coverage_low,0,1,"CoverageLow"); else if(key=="coveragemid")f(c.coverage_mid,0,1,"CoverageMid"); else if(key=="coveragehigh")f(c.coverage_high,0,1,"CoverageHigh"); else if(key=="opacity")f(c.opacity,0,1,"Opacity"); else if(key=="speed")f(c.speed,0,1000,"Speed"); else if(key=="brightness")f(c.brightness,0,8,"Brightness"); else if(key=="randomseed")f(c.random_seed,0,6.2831855f,"RandomSeed"); + else if(key=="sundirectionx")f(c.sun_direction_x,-1,1,"SunDirectionX"); else if(key=="sundirectiony")f(c.sun_direction_y,-1,1,"SunDirectionY"); else if(key=="sundirectionz")f(c.sun_direction_z,-1,1,"SunDirectionZ"); else if(key=="suncolorr")f(c.sun_color_r,0,4,"SunColorR"); else if(key=="suncolorg")f(c.sun_color_g,0,4,"SunColorG"); else if(key=="suncolorb")f(c.sun_color_b,0,4,"SunColorB"); + else if(key=="cloudbasecolorr")f(c.cloud_base_color_r,0,4,"CloudBaseColorR"); else if(key=="cloudbasecolorg")f(c.cloud_base_color_g,0,4,"CloudBaseColorG"); else if(key=="cloudbasecolorb")f(c.cloud_base_color_b,0,4,"CloudBaseColorB"); else if(key=="atmospheredensity")f(c.atmosphere_density,0,4,"AtmosphereDensity"); else if(key=="mist")f(c.mist,0,1,"Mist"); else if(key=="fogcolorr")f(c.fog_color_r,0,4,"FogColorR"); else if(key=="fogcolorg")f(c.fog_color_g,0,4,"FogColorG"); else if(key=="fogcolorb")f(c.fog_color_b,0,4,"FogColorB"); else if(key=="fogstart")f(c.fog_start,1,100000,"FogStart"); else if(key=="dayprogression")f(c.day_progression,-1,1,"DayProgression"); else if(key=="temporalblend")f(c.temporal_blend,0,.95f,"TemporalBlend"); else if(key=="temporaldenoise")f(c.temporal_denoise,0,16,"TemporalDenoise"); else if(key=="temporalclamp")f(c.temporal_clamp,0,16,"TemporalClamp"); else warn(warnings,line_no,"unknown [VolumetricClouds] key '"+key+"'"); + } + } +} +} // namespace vcs diff --git a/profiles/vcs/host/propershaders/ProperShadersConfig.hpp b/profiles/vcs/host/propershaders/ProperShadersConfig.hpp new file mode 100644 index 0000000..326fd9e --- /dev/null +++ b/profiles/vcs/host/propershaders/ProperShadersConfig.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include + +namespace vcs { + +struct ShaderPipeConfiguration { + bool enabled{true}; + + // Diagnostic proof that this is a real material shader pipe. When enabled, + // ApplyProperPipePixel replaces RGB on the surface in the private HLSL. + bool debug_color{false}; + float debug_r{1.0f}; + float debug_g{0.0f}; + float debug_b{1.0f}; +}; + +struct VehiclePipeConfiguration : ShaderPipeConfiguration { + // VCS recolours cars by rewriting CLUT data while keeping the same texture. + // Tracking that mutation gives the native GE frontend a semantic vehicle tag + // without baking game-model IDs into the renderer. + bool palette_classifier{true}; +}; + +struct RealtimeShadowsConfiguration { + bool enabled{true}; + + // Stable camera-centred directional shadow map. The private DX12 layer uses + // the same world matrices as the Building/Skin/Vehicle material pipes. + std::uint32_t map_resolution{2048u}; + float world_radius{140.0f}; + float depth_range{420.0f}; + float map_bias{0.0015f}; + std::uint32_t pcf_radius{1u}; + bool use_cloud_sun_direction{true}; + float light_direction_x{0.38f}; + float light_direction_y{-0.28f}; + float light_direction_z{0.88f}; + float strength{0.58f}; + + // Shadow diagnostics. 0=normal, 1=raw shadow map fullscreen, + // 2=receiver shadow mask, 3=light UV/frustum coverage, + // 4=sampled map depth, 5=receiver light depth, 6=lit/blocked compare. + std::uint32_t debug_mode{0u}; + + // Legacy fullscreen contact-shadow controls are retained for INI + // compatibility only. The DX12 path now receives directional shadows in + // Building/Skin/Vehicle material shaders, so the ghost-prone composite is + // disabled by default and is not used as the world-shadow implementation. + bool contact_shadows{false}; + std::uint32_t steps{12u}; + float max_distance_pixels{28.0f}; + float thickness{0.0025f}; + float contact_strength{0.22f}; + float light_screen_x{-0.55f}; + float light_screen_y{0.75f}; +}; + +struct VolumetricCloudsConfiguration { + bool enabled{false}; + std::uint32_t downscale_div{2u}; + std::uint32_t layers{2u}; + std::uint32_t shadow_steps{8u}; + float coverage_low{0.35f}; + float coverage_mid{0.25f}; + float coverage_high{0.18f}; + float opacity{1.0f}; + float speed{0.0f}; + float brightness{1.0f}; + float random_seed{0.0f}; + float sun_direction_x{0.38f}; + float sun_direction_y{-0.28f}; + float sun_direction_z{0.88f}; + float sun_color_r{1.0f}; + float sun_color_g{0.97f}; + float sun_color_b{0.88f}; + float cloud_base_color_r{0.70f}; + float cloud_base_color_g{0.70f}; + float cloud_base_color_b{0.70f}; + float atmosphere_density{0.0f}; + float mist{0.50f}; + float fog_color_r{0.58f}; + float fog_color_g{0.68f}; + float fog_color_b{0.78f}; + float fog_start{4500.0f}; + float day_progression{0.88f}; + float temporal_blend{0.50f}; + float temporal_denoise{1.0f}; + float temporal_clamp{1.0f}; +}; + +struct ProperShadersConfiguration { + bool enabled{true}; + ShaderPipeConfiguration building_pipe{}; + ShaderPipeConfiguration skin_pipe{}; + VehiclePipeConfiguration vehicle_pipe{}; + RealtimeShadowsConfiguration realtime_shadows{}; + VolumetricCloudsConfiguration volumetric_clouds{}; +}; + +// Parses only ProperShaders.ini. Unknown sections/keys are reported through +// warnings so shader configuration never pollutes VCSNative.ini. +void load_proper_shaders_configuration(ProperShadersConfiguration &config, + const std::filesystem::path &path, + std::vector &warnings); + +} // namespace vcs diff --git a/profiles/vcs/host/vcs_config.cpp b/profiles/vcs/host/vcs_config.cpp index 1f249b0..1a8c696 100644 --- a/profiles/vcs/host/vcs_config.cpp +++ b/profiles/vcs/host/vcs_config.cpp @@ -126,102 +126,6 @@ void warning(VcsConfiguration &config, std::size_t line, const std::string &mess config.warnings.push_back(stream.str()); } -void load_proper_shaders_configuration(VcsConfiguration &config, - const std::filesystem::path &path) { - std::ifstream input(path); - if (!input) return; - std::string section; - std::string raw_line; - std::size_t line_number = 0u; - while (std::getline(input, raw_line)) { - ++line_number; - std::string line = trim_copy(raw_line); - if (line.empty() || line[0] == ';' || line[0] == '#') continue; - if (line.front() == '[' && line.back() == ']') { - section = lowercase_copy(trim_copy(line.substr(1u, line.size() - 2u))); - continue; - } - if (section != "volumetricclouds" && section != "volumetric clouds") continue; - const std::size_t separator = line.find('='); - if (separator == std::string::npos) { - warning(config, line_number, "ProperShaders.ini: expected key=value"); - continue; - } - const std::string key = lowercase_copy(trim_copy(line.substr(0u, separator))); - const std::string value = strip_inline_comment(line.substr(separator + 1u)); - auto bad_float = [&](const char *name) { - warning(config, line_number, std::string("ProperShaders.ini: invalid ") + name); - }; - if (key == "enabled") { - if (!parse_bool(value, config.volumetric_clouds.enabled)) - warning(config, line_number, "ProperShaders.ini: Enabled expects true/false"); - } else if (key == "downscalediv") { - if (!parse_u32(value, 1u, 8u, config.volumetric_clouds.downscale_div)) - warning(config, line_number, "ProperShaders.ini: DownscaleDiv must be between 1 and 8"); - } else if (key == "layers") { - if (!parse_u32(value, 1u, 3u, config.volumetric_clouds.layers)) - warning(config, line_number, "ProperShaders.ini: Layers must be between 1 and 3"); - } else if (key == "shadowsteps") { - if (!parse_u32(value, 2u, 8u, config.volumetric_clouds.shadow_steps)) - warning(config, line_number, "ProperShaders.ini: ShadowSteps must be between 2 and 8"); - } else if (key == "coveragelow") { - if (!parse_float(value, 0.0f, 1.0f, config.volumetric_clouds.coverage_low)) bad_float("CoverageLow"); - } else if (key == "coveragemid") { - if (!parse_float(value, 0.0f, 1.0f, config.volumetric_clouds.coverage_mid)) bad_float("CoverageMid"); - } else if (key == "coveragehigh") { - if (!parse_float(value, 0.0f, 1.0f, config.volumetric_clouds.coverage_high)) bad_float("CoverageHigh"); - } else if (key == "opacity") { - if (!parse_float(value, 0.0f, 1.0f, config.volumetric_clouds.opacity)) bad_float("Opacity"); - } else if (key == "speed") { - if (!parse_float(value, 0.0f, 1000.0f, config.volumetric_clouds.speed)) bad_float("Speed"); - } else if (key == "brightness") { - if (!parse_float(value, 0.0f, 8.0f, config.volumetric_clouds.brightness)) bad_float("Brightness"); - } else if (key == "randomseed") { - if (!parse_float(value, 0.0f, 6.2831855f, config.volumetric_clouds.random_seed)) bad_float("RandomSeed"); - } else if (key == "sundirectionx") { - if (!parse_float(value, -1.0f, 1.0f, config.volumetric_clouds.sun_direction_x)) bad_float("SunDirectionX"); - } else if (key == "sundirectiony") { - if (!parse_float(value, -1.0f, 1.0f, config.volumetric_clouds.sun_direction_y)) bad_float("SunDirectionY"); - } else if (key == "sundirectionz") { - if (!parse_float(value, -1.0f, 1.0f, config.volumetric_clouds.sun_direction_z)) bad_float("SunDirectionZ"); - } else if (key == "suncolorr") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.sun_color_r)) bad_float("SunColorR"); - } else if (key == "suncolorg") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.sun_color_g)) bad_float("SunColorG"); - } else if (key == "suncolorb") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.sun_color_b)) bad_float("SunColorB"); - } else if (key == "cloudbasecolorr") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.cloud_base_color_r)) bad_float("CloudBaseColorR"); - } else if (key == "cloudbasecolorg") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.cloud_base_color_g)) bad_float("CloudBaseColorG"); - } else if (key == "cloudbasecolorb") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.cloud_base_color_b)) bad_float("CloudBaseColorB"); - } else if (key == "atmospheredensity") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.atmosphere_density)) bad_float("AtmosphereDensity"); - } else if (key == "mist") { - if (!parse_float(value, 0.0f, 1.0f, config.volumetric_clouds.mist)) bad_float("Mist"); - } else if (key == "fogcolorr") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.fog_color_r)) bad_float("FogColorR"); - } else if (key == "fogcolorg") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.fog_color_g)) bad_float("FogColorG"); - } else if (key == "fogcolorb") { - if (!parse_float(value, 0.0f, 4.0f, config.volumetric_clouds.fog_color_b)) bad_float("FogColorB"); - } else if (key == "fogstart") { - if (!parse_float(value, 1.0f, 100000.0f, config.volumetric_clouds.fog_start)) bad_float("FogStart"); - } else if (key == "dayprogression") { - if (!parse_float(value, -1.0f, 1.0f, config.volumetric_clouds.day_progression)) bad_float("DayProgression"); - } else if (key == "temporalblend") { - if (!parse_float(value, 0.0f, 0.95f, config.volumetric_clouds.temporal_blend)) bad_float("TemporalBlend"); - } else if (key == "temporaldenoise") { - if (!parse_float(value, 0.0f, 16.0f, config.volumetric_clouds.temporal_denoise)) bad_float("TemporalDenoise"); - } else if (key == "temporalclamp") { - if (!parse_float(value, 0.0f, 16.0f, config.volumetric_clouds.temporal_clamp)) bad_float("TemporalClamp"); - } else { - warning(config, line_number, "ProperShaders.ini: unknown [VolumetricClouds] key '" + key + "'"); - } - } -} - void apply_display_key(VcsConfiguration &config, const std::string &key, const std::string &value, std::size_t line) { if (key == "enabled") { @@ -780,10 +684,11 @@ VcsConfiguration load_vcs_configuration(const std::filesystem::path &path) { apply_frontend_key(config, key, value, line_number); else if (section == "textures" || section == "texturereplacement") apply_textures_key(config, key, value, line_number); - // These sections belong to the optional Project2DFX module, which - // deliberately owns its parser so it can be compiled independently of - // the core display/input configuration. They are nevertheless valid - // VCSNative.ini sections and must not be reported as unknown here. + // Optional modules deliberately own these parsers so the core + // display/input configuration stays game-neutral. DrawDistance is now + // installed independently of Project2DFX; SimulateHDR is accepted only + // as a legacy VCSNative.ini section (new shader settings live in + // ProperShaders.ini). else if (section == "project2dfx" || section == "project 2dfx" || section == "lodlights" || section == "lod lights" || section == "trafficlights" || section == "traffic lights" || @@ -792,7 +697,8 @@ VcsConfiguration load_vcs_configuration(const std::filesystem::path &path) { section == "heli height" || section == "drawdistance" || section == "draw distance" || section == "simulatehdr" || section == "simulate hdr") { - // Parsed by install_project2dfx() or hdr_post_configure(). + // Parsed by install_project2dfx(), install_draw_distance_patch(), + // or retained for legacy configuration compatibility. } else if (section.empty()) warning(config, line_number, "key outside a section"); @@ -812,7 +718,7 @@ void initialize_vcs_configuration(const std::filesystem::path &executable_direct } VcsConfiguration loaded = load_vcs_configuration(path); - load_proper_shaders_configuration(loaded, executable_directory / "ProperShaders.ini"); + load_proper_shaders_configuration(loaded.proper_shaders, executable_directory / "ProperShaders.ini", loaded.warnings); // SAVE_REPRO is intentionally off in the shipped INI. The internal restore // script/environment is an explicit developer opt-in and may enable it for // that process without requiring the user's normal configuration to change. diff --git a/profiles/vcs/host/vcs_config.hpp b/profiles/vcs/host/vcs_config.hpp index c095aff..eaf7c07 100644 --- a/profiles/vcs/host/vcs_config.hpp +++ b/profiles/vcs/host/vcs_config.hpp @@ -1,5 +1,7 @@ #pragma once +#include "propershaders/ProperShadersConfig.hpp" + #include #include #include @@ -187,44 +189,6 @@ struct WidescreenConfiguration { std::uint32_t aspect_y{0u}; }; -// Standalone ProperShaders.ini feature. Values normally supplied by the San -// Andreas timecycle/weather integration remain explicit placeholders until the -// equivalent VCS guest hooks exist. Keeping the inputs separate is important: -// collapsing the three decks and lighting state into one "coverage" value -// materially changes CloudWorks' appearance. -struct VolumetricCloudsConfiguration { - bool enabled{false}; - std::uint32_t downscale_div{2u}; - std::uint32_t layers{2u}; - std::uint32_t shadow_steps{8u}; - float coverage_low{0.35f}; - float coverage_mid{0.25f}; - float coverage_high{0.18f}; - float opacity{1.0f}; - float speed{0.0f}; - float brightness{1.0f}; - float random_seed{0.0f}; - float sun_direction_x{0.38f}; - float sun_direction_y{-0.28f}; - float sun_direction_z{0.88f}; - float sun_color_r{1.0f}; - float sun_color_g{0.97f}; - float sun_color_b{0.88f}; - float cloud_base_color_r{0.70f}; - float cloud_base_color_g{0.70f}; - float cloud_base_color_b{0.70f}; - float atmosphere_density{0.0f}; - float mist{0.50f}; - float fog_color_r{0.58f}; - float fog_color_g{0.68f}; - float fog_color_b{0.78f}; - float fog_start{4500.0f}; - float day_progression{0.88f}; - float temporal_blend{0.50f}; - float temporal_denoise{1.0f}; - float temporal_clamp{1.0f}; -}; - // The aspect the game itself builds its projection with. VCS loads the // constant 0x3FE38E39 -- exactly 16/9 -- and everything is relative to it. inline constexpr float kGameNativeAspectRatio = 16.0f / 9.0f; @@ -281,7 +245,7 @@ struct VcsConfiguration { DiagnosticsConfiguration diagnostics{}; TestingConfiguration testing{}; WidescreenConfiguration widescreen{}; - VolumetricCloudsConfiguration volumetric_clouds{}; + ProperShadersConfiguration proper_shaders{}; std::filesystem::path source_path{}; // Where the executable lives. Saves go beside it rather than into the game // data, so a player who points the runtime at a read-only or shared copy of diff --git a/profiles/vcs/host/vcs_draw_distance_patch.cpp b/profiles/vcs/host/vcs_draw_distance_patch.cpp index 4a4d36b..976cb17 100644 --- a/profiles/vcs/host/vcs_draw_distance_patch.cpp +++ b/profiles/vcs/host/vcs_draw_distance_patch.cpp @@ -5,12 +5,12 @@ // // [DrawDistance] // Enabled = 1 -// World = 1.50 +// Objects = 1.50 // Vehicles = 1.50 // NPCs = 1.50 // // 1.0 = original PSP distance. Values below 1.0 are clamped to 1.0. -// World is allowed up to 8.0; Vehicles/NPCs up to 4.0. +// Objects/World/Vehicles/NPCs are allowed up to 8.0 for explicit testing. // For mission compatibility, keep Vehicles/NPCs <= 2.0 unless tested. // // Integration (no header required): @@ -28,6 +28,7 @@ #include "vcs_draw_distance_patch.hpp" #include "psprecomp/runtime.hpp" #include "psprecomp/common.hpp" // hex32, para o relatorio de hooks vivos/mortos +#include "vcs_runtime_log.hpp" #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +48,7 @@ namespace vcs { DrawDistanceRuntimeScales g_draw_distance_runtime_scales{}; +DrawDistanceRuntimeTelemetry g_draw_distance_runtime_telemetry{}; namespace { @@ -83,10 +86,6 @@ struct OriginalWorldModel { }; std::unordered_map g_original_world_models; -bool g_far_clip_seen{}; -float g_far_clip_last_raw{}; -float g_far_clip_last_scaled{}; - std::string trim_copy(std::string value) { auto is_space = [](unsigned char c) { return std::isspace(c) != 0; }; while (!value.empty() && is_space(static_cast(value.front()))) @@ -171,7 +170,9 @@ DrawDistanceConfig load_config(const std::filesystem::path &path) { if (key == "enabled") { parse_bool(value, cfg.enabled); - } else if (key == "world" || key == "worldmultiplier") { + } else if (key == "world" || key == "worldmultiplier" || + key == "objects" || key == "buildings" || + key == "objectmultiplier" || key == "buildingmultiplier") { parse_float(value, cfg.world); } else if (key == "vehicles" || key == "cars" || key == "vehiclemultiplier") { parse_float(value, cfg.vehicles); @@ -181,8 +182,8 @@ DrawDistanceConfig load_config(const std::filesystem::path &path) { } cfg.world = std::clamp(cfg.world, 1.0f, 8.0f); - cfg.vehicles = std::clamp(cfg.vehicles, 1.0f, 4.0f); - cfg.npcs = std::clamp(cfg.npcs, 1.0f, 4.0f); + cfg.vehicles = std::clamp(cfg.vehicles, 1.0f, 8.0f); + cfg.npcs = std::clamp(cfg.npcs, 1.0f, 8.0f); return cfg; } @@ -309,6 +310,10 @@ bool patch_world_model_table(psprecomp::Runtime &runtime, std::uint32_t gp) { std::cerr << " [dd-verifica] passada=" << verifications << " intactos=" << intact << " revertidos=" << reverted << "\n"; } + if (patched != 0u) { + ++g_draw_distance_runtime_telemetry.world_table_patch_hits; + g_draw_distance_runtime_telemetry.world_models_patched += patched; + } return patched != 0u; } @@ -318,12 +323,11 @@ void install_draw_distance_patch(psprecomp::Runtime &runtime, const std::filesystem::path &ini_path) { g_config = load_config(ini_path); g_original_world_models.clear(); - g_far_clip_seen = false; - g_far_clip_last_raw = 0.0f; - g_far_clip_last_scaled = 0.0f; g_draw_distance_runtime_scales = {}; + g_draw_distance_runtime_telemetry = {}; if (g_config.enabled) { - g_draw_distance_runtime_scales.entity = std::max(g_config.vehicles, g_config.npcs); + g_draw_distance_runtime_scales.world = g_config.world; + g_draw_distance_runtime_scales.entity = std::max({g_config.world, g_config.vehicles, g_config.npcs}); g_draw_distance_runtime_scales.vehicles = g_config.vehicles; g_draw_distance_runtime_scales.npcs = g_config.npcs; } @@ -333,17 +337,15 @@ void install_draw_distance_patch(psprecomp::Runtime &runtime, return; } - // World is maintained at vblank. Entity/vehicle/NPC constants are - // patched directly at their generated local labels, because gameplay reaches + // Far clip and entity/vehicle/NPC constants are patched directly at + // their generated local labels, because gameplay reaches // those labels with intra-unit gotos that cannot be intercepted by the runtime // function registry. if (g_config.world > 1.0f) { - // World distance is maintained from the display-vblank boundary. Both - // historical entry hooks can be reached as local labels inside an AOT - // unit and therefore bypass Runtime::register_function entirely. Keeping - // them registered also poisons direct chaining for no reliable benefit. - std::cerr << "[draw-distance] world path=vblank (no fragile far_clip/IDE AOT hooks)\n"; + // Far clip is scaled at the exact gp+7796 setter. Model-info LODs are + // patched at IDE completion and refreshed at vblank if streaming rebuilds them. + std::cerr << "[draw-distance] world path=exact far-clip setter + IDE model table\n"; } if (g_config.vehicles > 1.0f || g_config.npcs > 1.0f) { @@ -351,51 +353,86 @@ void install_draw_distance_patch(psprecomp::Runtime &runtime, // generated units. Runtime::register_function cannot intercept those // paths. The generated corpus now reads g_draw_distance_runtime_scales // at the exact labels instead, so do not install misleading dead hooks. - std::cerr << "[draw-distance] entity/vehicle/NPC path=generated-local-label" << "\n"; + std::cerr << "[draw-distance] entity path=verified WidescreenFix semantic + vehicle/NPC local constants" << "\n"; } std::cerr << "[draw-distance] enabled" << " world=" << g_config.world << " vehicles=" << g_config.vehicles << " npcs=" << g_config.npcs - << " entity_lod=" << std::max(g_config.vehicles, g_config.npcs) + << " entity_lod=" << std::max({g_config.world, g_config.vehicles, g_config.npcs}) << "\n"; + { + std::ostringstream line; + line << "draw-distance config enabled=1" + << " world=" << g_config.world + << " vehicles=" << g_config.vehicles + << " npcs=" << g_config.npcs + << " common_lod=" << std::max({g_config.world, g_config.vehicles, g_config.npcs}); + runtime_log_line(line.str()); + } +} + +void draw_distance_world_table_ready(psprecomp::Runtime &runtime, + std::uint32_t guest_gp) noexcept { + if (!g_config.enabled || g_config.world <= 1.0f || guest_gp == 0u) return; + try { + if (patch_world_model_table(runtime, guest_gp)) { + std::cerr << "[draw-distance] world table patched at authoritative IDE init" + << " gp=" << psprecomp::hex32(guest_gp) << "\n"; + } + } catch (const std::exception &e) { + std::cerr << "[draw-distance] world IDE init patch skipped: " << e.what() << "\n"; + } catch (...) { + std::cerr << "[draw-distance] world IDE init patch skipped: unknown exception\n"; + } } void draw_distance_vblank_tick(psprecomp::Runtime &runtime, std::uint32_t guest_gp, std::uint64_t vblank_index) noexcept { - if (!g_config.enabled || g_config.world <= 1.0f || guest_gp == 0u) return; + if (!g_config.enabled || guest_gp == 0u) return; try { - const std::uint32_t far_address = guest_gp + kGpFarClipOffset; - if (runtime.memory().contains(far_address, sizeof(std::uint32_t))) { - const float current = load_float(runtime, far_address); - if (std::isfinite(current) && current > 0.0f) { - // Never multiply our own value again. If the game writes a new - // dynamic far clip (weather/interior/etc.), treat that new value - // as the next raw baseline and scale it exactly once. - if (!g_far_clip_seen || - (!nearly_equal(current, g_far_clip_last_scaled) && - !nearly_equal(current, g_far_clip_last_raw))) { - g_far_clip_seen = true; - g_far_clip_last_raw = current; - g_far_clip_last_scaled = current * g_config.world; - } - if (g_far_clip_seen && !nearly_equal(current, g_far_clip_last_scaled)) - store_float(runtime, far_address, g_far_clip_last_scaled); - } - } - - // The model-info table can appear after the first gameplay vblanks and - // can be repopulated by streaming. Retry aggressively at startup, then - // refresh twice a second-ish without touching generated guest code. - if (vblank_index < 240u || (vblank_index % 120u) == 0u) + // Far clip is now multiplied at the actual CDraw::SetFarClipZ AOT + // setter (0x08A1AD6C), so vblank must never multiply it a second time. + // World model-info is still refreshed here because streaming may rebuild + // entries after the original IDE load. + if (g_config.world > 1.0f && + (vblank_index < 240u || (vblank_index % 120u) == 0u)) (void)patch_world_model_table(runtime, guest_gp); + + if ((vblank_index % 120u) == 0u) { + const std::uint32_t count = runtime.memory().contains( + guest_gp + kGpIdeCountOffset, sizeof(std::uint32_t)) + ? runtime.memory().load32(guest_gp + kGpIdeCountOffset) : 0u; + const std::uint32_t table = runtime.memory().contains( + guest_gp + kGpIdeTableOffset, sizeof(std::uint32_t)) + ? runtime.memory().load32(guest_gp + kGpIdeTableOffset) : 0u; + float far_clip = 0.0f; + if (runtime.memory().contains(guest_gp + kGpFarClipOffset, + sizeof(std::uint32_t))) + far_clip = load_float(runtime, guest_gp + kGpFarClipOffset); + std::ostringstream line; + line << "draw-distance telemetry vblank=" << vblank_index + << " gp=" << psprecomp::hex32(guest_gp) + << " far_clip=" << far_clip + << " model_count=" << count + << " model_table=" << psprecomp::hex32(table) + << " far_set_hits=" << g_draw_distance_runtime_telemetry.far_clip_set_hits + << " actor_hits=" << g_draw_distance_runtime_telemetry.actor_lod_hits + << " npc_hits=" << g_draw_distance_runtime_telemetry.npc_constant_hits + << " veh_dynamic_hits=" << g_draw_distance_runtime_telemetry.vehicle_dynamic_hits + << " veh_fallback_hits=" << g_draw_distance_runtime_telemetry.vehicle_fallback_hits + << " table_patch_hits=" << g_draw_distance_runtime_telemetry.world_table_patch_hits + << " world_models_written=" << g_draw_distance_runtime_telemetry.world_models_patched; + runtime_log_line(line.str()); + } } catch (const std::exception &error) { static bool logged = false; if (!logged) { logged = true; std::cerr << "[draw-distance] vblank maintenance disabled after error: " << error.what() << "\n"; + runtime_log_error("draw-distance telemetry", error.what()); } } } diff --git a/profiles/vcs/host/vcs_draw_distance_patch.hpp b/profiles/vcs/host/vcs_draw_distance_patch.hpp index ba7bc73..f56a81b 100644 --- a/profiles/vcs/host/vcs_draw_distance_patch.hpp +++ b/profiles/vcs/host/vcs_draw_distance_patch.hpp @@ -8,23 +8,40 @@ namespace psprecomp { class Runtime; } namespace vcs { struct DrawDistanceRuntimeScales { + float world{1.0f}; float entity{1.0f}; float vehicles{1.0f}; float npcs{1.0f}; }; -// Read-only after profile installation. Generated AOT code reads these three -// values directly at the exact local labels that the old Runtime hook could -// not intercept. +struct DrawDistanceRuntimeTelemetry { + std::uint64_t far_clip_set_hits{}; + std::uint64_t actor_lod_hits{}; + std::uint64_t npc_constant_hits{}; + std::uint64_t vehicle_dynamic_hits{}; + std::uint64_t vehicle_fallback_hits{}; + std::uint64_t world_table_patch_hits{}; + std::uint64_t world_models_patched{}; +}; + +// Scales are read-only after profile installation. Generated AOT code reads +// them directly at exact local labels that Runtime function hooks cannot +// intercept. Telemetry counters are incremented by those same sites. extern DrawDistanceRuntimeScales g_draw_distance_runtime_scales; +extern DrawDistanceRuntimeTelemetry g_draw_distance_runtime_telemetry; void install_draw_distance_patch(psprecomp::Runtime &runtime, const std::filesystem::path &ini_path); -// Authoritative world-distance maintenance point. Called once per display -// vblank so it cannot be bypassed by generated AOT local gotos. +// Streaming refresh/telemetry point. The authoritative far-clip write itself +// is patched at its generated AOT setter. void draw_distance_vblank_tick(psprecomp::Runtime &runtime, std::uint32_t guest_gp, std::uint64_t vblank_index) noexcept; +// Called by generated VCS AOT at the exact completion point of the IDE/model +// info table initializer. This avoids relying on a vblank-time GP snapshot. +void draw_distance_world_table_ready(psprecomp::Runtime &runtime, + std::uint32_t guest_gp) noexcept; + } // namespace vcs diff --git a/profiles/vcs/host/vcs_project2dfx.cpp b/profiles/vcs/host/vcs_project2dfx.cpp index 37a2966..91e2ab9 100644 --- a/profiles/vcs/host/vcs_project2dfx.cpp +++ b/profiles/vcs/host/vcs_project2dfx.cpp @@ -1,6 +1,5 @@ #include "vcs_project2dfx.hpp" #include "vcs_project2dfx_lights.hpp" -#include "vcs_draw_distance_patch.hpp" #include "ge_gpu_backend.hpp" #include "psprecomp/runtime.hpp" @@ -580,10 +579,6 @@ void install_project2dfx(psprecomp::Runtime &runtime, std::uint32_t guest_scratch_base){ g_runtime=&runtime;g_projection_scratch=guest_scratch_base; g_ini_path=ini_path;g_cfg=read_config(ini_path);g_camera_candidates.clear();g_hot_camera=nullptr;g_hot_camera_revision=0u;g_hot_camera_target=0u;g_last_rendered_vblank=std::numeric_limits::max(); - // O draw distance tem [DrawDistance] Enabled proprio e nao faz parte do - // Project2DFX original, entao instala antes do return abaixo. Estava depois - // dele, o que amarrava um recurso independente ao outro sem dizer nada. - install_draw_distance_patch(runtime,ini_path); if(!g_cfg.enabled){std::cerr<<"[Project2DFX] disabled\n";return;} if(g_cfg.performance_log){ #ifdef _WIN32 diff --git a/profiles/vcs/host/vcs_runtime_log.cpp b/profiles/vcs/host/vcs_runtime_log.cpp index e332137..cefc191 100644 --- a/profiles/vcs/host/vcs_runtime_log.cpp +++ b/profiles/vcs/host/vcs_runtime_log.cpp @@ -89,14 +89,16 @@ void runtime_log_initialize(const VcsConfiguration &configuration) { << " gpr_shadow_clusters=4 gpr_shadow_regs=24 gpr_shadow_occurrences=3461 geometry_shadow=0" << " perf_layer=16 cpu_lean_revision=2 cpu_aggressive_revision=10 correctness_revision=8272 recovery_from_821=1" << " save_transaction=1 save_lifecycle_v82=1 sas_endflag_latched=1 sas_loop_history_restore=1" - << " atrac_virtual_source=1 atrac_stall_diag=1 atrac_stream_resident_status=1 atrac_nonloop_resident=-2 atrac_loop_resident=-3 output2_success_zero=1 output2_master_watermark=1 output2_late_catchup=0 save_repro_checkpoint=1 save_repro_trace=1 save_repro_hotkey_f8=1 save_repro_trace_hotkey_f10=1 save_repro_auto_restore=1 save_repro_dispatch_sample_stride=64 save_repro_passive_until_f8=1 save_repro_hle_hotpath=0 save_repro_ui_hotkeys=1 save_repro_f10_async_fallback=1 save_repro_collector_partial_bundle=1 save_repro_internal_ini_gate=1 save_repro_default_enabled=0 save_exitdelete_semantics=1 save_repro_legacy_exitdelete_repair=1 save_partition_reuse=1 news_atrac_v825_guard=1 runtime_chain_telemetry_default=0 arch_fastmem=1 aot_direct_fastmem_default=1 aot_hard_fastmem=1 aot_branchless_mem=1 aot_vfpu_block32=19455 aot_scalar_run_blocks=10665 aot_scalar_run_words=53173 aot_load_runs=5633 aot_store_runs=5032 vfpu_default_dest_fast=1 vfpu_vmscl_default_fast=1 vfpu_default_fastlane=1 vfpu_vec3_fastlane_sites=2469 vfpu_unary_fastlane_sites=1607 vfpu_vdot_default_fast=1 vfpu_vscl_default_fast=1 radio_atrac_identity_guard=1 atrac_direct_header_validation=1 atrac_identity_cache=1 atrac_header_only_producer_hint=1 vfpu_vtfm_ct=1 vfpu_vtfm_ct_sites=337 vfpu_vi2f_ct=1 vfpu_vi2f_ct_sites=164 vfpu_vcmp_default_fast=1 vfpu_vcmp_ct_sites=558 vfpu_vcmov_default_fast=1 vfpu_vcmov_ct_sites=461 vfpu_cross_default_fast=1 vfpu_cross_ct_sites=217 vfpu_helper_ct2=1 trusted_chain=1 trusted_chain_sites=45968 trusted_chain_poisonable_sites=3240 compact_leaf_dispatch=1 compact_leaf_targets=4 compact_leaf_sites=1885 outer_entry_dispatch=1 outer_aot_view_reuse=1 v87_tiny_leaf_reverted=1 aot_register_residency=1 aot_register_residency_units=230 aot_gpr_resident_slots=6 aot_fpr_resident_slots=4 aot_gpr_resident_occurrences=914095 aot_fpr_resident_occurrences=146809 aot_resident_region_weight=6 resident_regions=1 resident_region_targets=83 resident_region_sites=11171 resident_region_files=220 resident_region_sync_out_removed=11171 resident_region_sync_in_removed=11171 resident_region_resident_args=53234 resident_region_ctx_args=26183 resident_region_scheduler_guard=1 resident_region_scheduler_fallback_sites=11171" + << " atrac_virtual_source=1 atrac_stall_diag=1 atrac_stream_resident_status=1 atrac_nonloop_resident=-2 atrac_loop_resident=-3 output2_success_zero=1 output2_master_watermark=1 output2_late_catchup=0 save_repro_checkpoint=1 save_repro_trace=1 save_repro_hotkey_f8=1 save_repro_trace_hotkey_f10=1 save_repro_auto_restore=1 save_repro_dispatch_sample_stride=64 save_repro_passive_until_f8=1 save_repro_hle_hotpath=0 save_repro_ui_hotkeys=1 save_repro_f10_async_fallback=1 save_repro_collector_partial_bundle=1 save_repro_internal_ini_gate=1 save_repro_default_enabled=0 save_exitdelete_semantics=1 save_repro_legacy_exitdelete_repair=1 save_partition_reuse=1 news_atrac_v825_guard=1 runtime_chain_telemetry_default=0 arch_fastmem=1 aot_direct_fastmem_default=1 aot_hard_fastmem=1 aot_branchless_mem=1 aot_vfpu_block32=19455 aot_scalar_run_blocks=10665 aot_scalar_run_words=53173 aot_load_runs=5633 aot_store_runs=5032 vfpu_default_dest_fast=1 vfpu_vmscl_default_fast=1 vfpu_default_fastlane=1 vfpu_vec3_fastlane_sites=2469 vfpu_unary_fastlane_sites=1607 vfpu_vdot_default_fast=1 vfpu_vscl_default_fast=1 radio_atrac_identity_guard=1 atrac_direct_header_validation=1 atrac_identity_cache=1 atrac_header_only_producer_hint=1 vfpu_vtfm_ct=1 vfpu_vtfm_ct_sites=337 vfpu_vi2f_ct=1 vfpu_vi2f_ct_sites=164 vfpu_vcmp_default_fast=1 vfpu_vcmp_ct_sites=558 vfpu_vcmov_default_fast=1 vfpu_vcmov_ct_sites=461 vfpu_cross_default_fast=1 vfpu_cross_ct_sites=217 vfpu_helper_ct2=1 trusted_chain=1 trusted_chain_sites=45968 trusted_chain_poisonable_sites=3240 compact_leaf_dispatch=1 compact_leaf_targets=4 compact_leaf_sites=1885 outer_entry_dispatch=1 outer_aot_view_reuse=1 v87_tiny_leaf_reverted=1 aot_register_residency=1 aot_register_residency_units=230 aot_gpr_resident_slots=6 aot_fpr_resident_slots=4 aot_gpr_resident_occurrences=914095 aot_fpr_resident_occurrences=146813 aot_resident_region_weight=6 resident_regions=1 resident_region_targets=83 resident_region_sites=11171 resident_region_files=220 resident_region_sync_out_removed=11171 resident_region_sync_in_removed=11171 resident_region_resident_args=53234 resident_region_ctx_args=26183 resident_region_scheduler_guard=1 resident_region_scheduler_fallback_sites=11171" << " tier2_direct_fastmem=1 tier2_direct_mem_sites=1969 tier2_deep_telemetry_default=0 geometry_fusion_rollback=1" << " entity_leaf_inline=1 entity_leaf_scheduler_accounting=1 entity_leaf_resume_pc_fix=1" << " ge_async_default=0 parallel_vertex_decode_default=0" << " v5_vfpu_fast_quarantined=1 native_vfpu_088b1780_v4=1" << " dx12_execute_indirect_default=0 indirect_buffer_mb=4" << " ge_async_quarantined=1 parallel_vertex_decode_quarantined=1 legacy_perf_env_ignored=1" - << " amd_uma_compat=1 uma_msaa_guard=1 packed0115_amd_stride=12\n\n"; + << " amd_uma_compat=1 uma_msaa_guard=1 packed0115_amd_stride=12" + << " pipe_shadow_failsafe=1 material_shadow_scalar_probe=1" + << " draw_distance_common_world=1 draw_distance_fog_scale=1\n\n"; if (s.flush_every_line) s.file.flush(); } diff --git a/profiles/vcs/shaders/hdr/LICENSE_SIMULATEHDR.txt b/profiles/vcs/shaders/hdr/LICENSE_SIMULATEHDR.txt deleted file mode 100644 index 37b202d..0000000 --- a/profiles/vcs/shaders/hdr/LICENSE_SIMULATEHDR.txt +++ /dev/null @@ -1,57 +0,0 @@ -SimulateHDR — upstream attribution and licence -============================================== - -The shaders in this directory are a port of SimulateHDR, the bloom and tone -mapping post-process of ProperShaders (an advanced graphics mod for GTA San -Andreas PC, DirectX 9 / Shader Model 3.0). - -SimulateHDR is itself a fork of "Blooming HDR" by Jose Negrete, also known as -BlueSkyDefender: - - https://github.com/BlueSkyDefender/Depth3D - -and is distributed under the Creative Commons Attribution-ShareAlike 4.0 -International licence (CC BY-SA 4.0): - - https://creativecommons.org/licenses/by-sa/4.0/ - -CHANGES WERE MADE. This port is a derivative work and is redistributed under -the same CC BY-SA 4.0 terms. - - -What was changed in this port ------------------------------ - -- Translated from HLSL ps_3_0 (.fx / .fxh effect files) to GLSL 450 compiled to - SPIR-V for Vulkan. -- The DirectX 9 effect framework is gone. Uniforms that arrived through - ID3DXEffect::SetFloat/SetVectorArray are Vulkan push constants; textures bound - by name are descriptor set bindings. -- The fullscreen quad with a vertex buffer became a vertex-buffer-less - fullscreen triangle derived from gl_VertexIndex. -- The tone mapper is a uniform branch on a push constant instead of four - compile-time technique specialisations (T#E#L#D#). ps_3_0 did not reward - runtime branching; a desktop GPU running a branch that is uniform across the - draw does. -- HLSL mul(M, v) row-vector products were rewritten as GLSL column-major matrix - products with the ACES matrices entered as columns. -- Eye adaptation (AUTOEXP) is not ported: it needs a frame-average luminance - read back to the host, which is the readback this port avoids by design. -- The colour grading LUT (LUTMODE) is not ported: it needs PNG lookup tables. -- The debug heat map (DEBUG) is not ported. - - The three above collapse to their "off" branch, which is exactly what the - upstream T#E0L0D0 technique variant compiled to. - - -Due diligence carried over from the upstream headers ----------------------------------------------------- - - Timothy Lottes tone mapper https://www.shadertoy.com/view/XljBRK - ACES Cinematic fit Krzysztof Narkowicz and Stephen Hill, via MJP's - BakingLab - https://github.com/TheRealMJP/BakingLab - Uncharted 2 tone mapper John Hable - Eye adaptation Padraic Hennessy, MJP and David Neubelt, - ported by prod80 - Bloom rework inspired by Adyss' bloom diff --git a/profiles/vcs/shaders/hdr/hdr_blur.frag b/profiles/vcs/shaders/hdr/hdr_blur.frag deleted file mode 100644 index eb19213..0000000 --- a/profiles/vcs/shaders/hdr/hdr_blur.frag +++ /dev/null @@ -1,35 +0,0 @@ -#version 450 -//---------------------------------------------------------------------------// -// SimulateHDR - separable blur, shared by all six blur passes. -// -// Ported from PS_BloomBlur in SimulateHDR_Bloom.fxh. See -// shaders/hdr/LICENSE_SIMULATEHDR.txt (CC BY-SA 4.0). Changes were made. -// -// The kernel is uniform over the frame, so the host solves it once and hands -// over four taps per side with the UV offset already scaled for this octave and -// direction and the weight already normalised. Adjacent taps of the original -// fourteen are merged into a single bilinear fetch at their weighted centroid, -// preserving reach and total weight at eight fetches instead of fourteen. -// -// One shader serves every octave: a different image bound to u_input and a -// different tap set is the whole difference between the six passes. -//---------------------------------------------------------------------------// - -layout(set = 0, binding = 0) uniform sampler2D u_input; - -layout(push_constant) uniform Push { - vec4 taps[4]; // xy = UV offset, z = normalised weight -} pc; - -layout(location = 0) in vec2 v_uv; -layout(location = 0) out vec4 o_color; - -void main() { - vec4 blur = vec4(0.0); - for (int i = 0; i < 4; ++i) { - const vec2 offset = pc.taps[i].xy; - blur += (texture(u_input, v_uv + offset) + - texture(u_input, v_uv - offset)) * pc.taps[i].z; - } - o_color = blur; -} diff --git a/profiles/vcs/shaders/hdr/hdr_blur.frag.spv b/profiles/vcs/shaders/hdr/hdr_blur.frag.spv deleted file mode 100644 index ecc6c892b539c740f650682fcf94b0317e599906..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1196 zcmZ9L%T7~K6oxk~lt2Yq7&KD5inN zOoZVu2p4~T{{E|D4V-`qaoTARdPs}#QfgN(h9%?z=!cro?lKv*#T;HT$-J593-m;DCTaDj=JC8GDJL)1E zum2eCt(@0->fTgahu?^G+>4~`AC@8be|RP8@>?ocqX)d7F^8ZE^m(hVzFZ+gZo44= wF7Ile5_~WEuVel`x;3vt1N_%nQwI(ZtzkZ6vzi))GRj9`P^w^89$V(aZ=y|}KwUGlAx7uWf_ zCGTn7FRt@jCGQ!{i|hPu$xoKNxX$~H|Igcw7vL6t5|d3#7(IWmWisZv%*68;uM$nM zcJMX&J14RE^_Yn(1^fNACB8}JED7s6?7v!y4&DZDw&v)Ly#*NB~GYOE8>)fzXz^17B9zv%0GxrN)uy43YedM~NlA(pFk zyI^@;8`tf@scl{A-Xr#YQ};fxT&=qame;j$-3M@LTbH^IiG3%j+b5Q*bsvG{b!}XC z2TpD4Qui_OE}pu3#B#OnK3HDY#&r+i)V6NG%m=KmaNpiBZZF@`Yh33P73E+`NS>zzTV^Rx8A&O_lTG3|Ha&26b|rzVnjB% diff --git a/profiles/vcs/shaders/hdr/hdr_composite.frag b/profiles/vcs/shaders/hdr/hdr_composite.frag deleted file mode 100644 index 462bc50..0000000 --- a/profiles/vcs/shaders/hdr/hdr_composite.frag +++ /dev/null @@ -1,147 +0,0 @@ -#version 450 -//---------------------------------------------------------------------------// -// SimulateHDR - final full-screen composite. -// -// Ported from HDR_Composite in SimulateHDR_Final.fxh and the two operators in -// SimulateHDR_Tonemap.fxh. See shaders/hdr/LICENSE_SIMULATEHDR.txt -// (CC BY-SA 4.0). Changes were made. -// -// Deliberate differences from the DX9 original: -// - The tone mapper is a uniform branch instead of four compile-time -// specialisations. ps_3_0 did not reward runtime branching; a desktop GPU -// running a push-constant branch that is uniform across the draw does. -// - Eye adaptation and the colour grading LUT are not ported. AUTOEXP needed a -// frame-average luminance read back to the host, which is the readback this -// port avoids on purpose; LUTMODE needed PNG tables. Both collapse to their -// "off" branch here, exactly as the T#E0L0D0 variant did. -//---------------------------------------------------------------------------// - -layout(set = 0, binding = 0) uniform sampler2D u_scene; -layout(set = 0, binding = 1) uniform sampler2D u_bloom; - -layout(push_constant) uniform Push { - vec4 timothy_curve; // (contrast, b, c, unused) - vec4 timothy_cross; // (saturation/crossSat, crossSat, white point, unused) - vec2 screen_size; - float dither_amount; - float bloom_opacity; - float hdr_power; - float exposure_bias; - float gamma_in; - float gamma_out; - float saturate_amount; - float contrast; - float inv_white_point; - int tonemap; // 0 = Timothy Lottes, 1 = ACES -} pc; - -layout(location = 0) in vec2 v_uv; -layout(location = 0) out vec4 o_color; - -float luma(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); } // Rec. 709 - -//---------------------------------------------------------------------------// -// Interleaved gradient noise, three decorrelated channels off one dot product. -//---------------------------------------------------------------------------// -vec3 dither_noise(vec2 pixel_pos) { - const float t = pixel_pos.x * 0.06711056 + pixel_pos.y * 0.00583715; - return fract(52.9829189 * fract(t + vec3(0.0, 0.3183099, 0.6366198))); -} - -//---------------------------------------------------------------------------// -// Timothy Lottes. The shoulder term is fixed at 1.0, which collapses ColTone's -// pow(z, shoulder) to z, so the curve is one pow plus a reciprocal. -//---------------------------------------------------------------------------// -vec3 timothy_tonemap(vec3 color, float exposure) { - color *= exposure; - - float peak = max(1e-6, max(color.r, max(color.g, color.b))); - vec3 ratio = color / peak; - - const float z = pow(peak, pc.timothy_curve.x); - peak = z / (z * pc.timothy_curve.y + pc.timothy_curve.z); - - // Channel crosstalk, wrapped in the saturation transform. The crosstalk - // amount is pow(peak, 4), which is two multiplies rather than a real pow. - ratio = pow(abs(ratio), vec3(pc.timothy_cross.x)); - const float peak2 = peak * peak; - ratio = mix(ratio, vec3(pc.timothy_cross.z), peak2 * peak2); - ratio = pow(abs(ratio), vec3(pc.timothy_cross.y)); - - return peak * ratio; -} - -//---------------------------------------------------------------------------// -// ACES fitted, after Stephen Hill / Krzysztof Narkowicz via MJP's BakingLab. -// -// HLSL mul(M, v) is a row-vector-times-matrix product; GLSL mat3 is column -// major, so the same maths is written here as v * M with the rows entered as -// columns. Transposing one and not the other is the classic way to port this -// wrong and get a colour cast. -//---------------------------------------------------------------------------// -const mat3 kAcesInput = mat3( - 0.59719, 0.07600, 0.02840, - 0.35458, 0.90834, 0.13383, - 0.04823, 0.01566, 0.83777); - -const mat3 kAcesOutput = mat3( - 1.60475, -0.10208, -0.00327, - -0.53108, 1.10813, -0.07276, - -0.07367, -0.00605, 1.07602); - -vec3 rrt_and_odt_fit(vec3 v) { - const vec3 a = v * (v + 0.0245786) - 0.000090537; - const vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081; - return a / b; -} - -vec3 aces_tonemap(vec3 color, float exposure) { - color *= exposure + 0.5; // the fit expects exposure pre-adjusted this way - color = kAcesInput * color; - color = rrt_and_odt_fit(color); - color = kAcesOutput * color; - return color * pc.inv_white_point; -} - -void main() { - vec3 bloom = texture(u_bloom, v_uv).rgb; - - const vec3 noise = dither_noise(v_uv * pc.screen_size); - bloom = clamp(bloom + noise * smoothstep(0.0, 0.1, bloom) * pc.dither_amount, - 0.0, 1.0); - bloom *= pc.bloom_opacity; - - vec3 color = texture(u_scene, v_uv).rgb; - - if (pc.tonemap == 1) - color = mix(vec3(luma(color)), color, pc.saturate_amount); - - // De-gamma into linear before anything else touches the colour. - color = pow(abs(color), vec3(pc.gamma_in)); - - // Bloom goes in before the inverse tone map, otherwise the high range it is - // supposed to feed has already been clipped away. - color = max(vec3(0.0), color + bloom); - - // Inverse tone map (Timothy Lottes fast reversible). The floor on the - // denominator is not paranoia: a blown highlight is exactly 1.0 and - // pow(1.0, g) is exactly 1.0, so the denominator hits zero wherever bloom - // equals hdr_power. That gives INF, then INF/INF = NaN inside the ACES fit, - // and a NaN lands in an 8-bit target as black -- dark patches in the - // brightest part of a bloom. Clamping scales every channel by the same - // factor, so hue is untouched. - const float hdr_scale = (1.0 + max(pc.hdr_power, 0.001)) - - max(color.r, max(color.g, color.b)); - color *= 1.0 / max(hdr_scale, 0.001); - - color = (pc.tonemap == 0) ? timothy_tonemap(color, pc.exposure_bias) - : aces_tonemap(color, pc.exposure_bias); - - // gamma_out is 1/2.2, or 1.0 when the gamma conversion is switched off. - color = pow(abs(color), vec3(pc.gamma_out)); - - if (pc.tonemap == 1) - color = (color - 0.5) * pc.contrast + 0.5; - - o_color = vec4(color, 1.0); -} diff --git a/profiles/vcs/shaders/hdr/hdr_composite.frag.spv b/profiles/vcs/shaders/hdr/hdr_composite.frag.spv deleted file mode 100644 index 55eb1f945eda1afde148850e7e0f92b0de8f6b37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5280 zcmaKvX{=V&6^0M9z_oy)ptj^9qD09R6%oA*g<>uysGtc3P+E;+;!s7~CjO{X4b~x! zRdI--=B7|VMXU`$a3~cVq8J1RjA)zGiAhbt#GZ ztco)i{pKP*t?0kxtG{!{?%`|J>^Ag%v(}Ew+U`VO`q9d<(eFiHwBhP{FBhP{xOj72 zvxm{IeQjQ?ccSx598CPh0QcM8$$WnSU$JFyJ>Qe)pFZ(Gxu@p21R2u#?{V3KrRa{) zPg~}B3Vhwo%j>vLqwku3VLk6N=qvj4o{)K#p*u!DZJFm;@cLU$tm7|7KmVj_>Uunf zKKOSt>z?>4x?}Xy=KQYndBj|eqfh340X*-*H|o4rp`X_8?Uk{wM*scEGglxv z+288ke?HpZ=j&L{jdpeKKe@!;0`@HG{|R6FU;nJ{bMPClzh3UQ`EEy={rN3=QCw5j zRr_wlvz&aqPkj=vJHcDfjn@NgJjV?ri@ng}H=wdbAB^tV$bS6{-Mc0dzhUtHKB-&F zL(wnW=lT<3ACE2{{Si z@;>^>T>>ZH29Doll^>tl@7E~u{;pkHjlCS*@!F;{o4L;e%SZH;zY{Vh6g>8rh^s?}CF`J)i~)}FC`uFtsJ5Xb6g{rnx;fmlD!oAt8} zI}!Qh@HN=6@>#<-VCUAB_`ATy_wNJk=3sv&)^~8m+KnXEf5GyJ^*z{mPLU$A_}4g||ZQs4dHc+38Deq-vpn|zX& z-*4G%_xrQT$1~_yb56ev0`s!6J$}jW07&!m9!!1*g5{InL14K^OBL^@aJytAv25jEi(yPaV?bW&X+Nh?^EhJSv!(=H-L>PpPJkV zmiGxazv86B{juVjwYwQkA8Y3reNu~C8a89-8{4f7TL)wHO%L7<)*i8b_WT`i=Ba%W zo!}jJ7uYphEAOVP`)+t`iFXf}x9nf%)t))-180s=jXCaz*OuRq1z_iq&)5gR8SDQ^ z68}MXZRy#Ez>b$sKRgU}K5aR>kAQj0v+H^j=TWe6yuX_3TnMi%wR;RK7jga8?r}KY za{b28FYD?An~Rcoe+DOBb6tzzwI$wSuv{c_E`f7iCB93+@{!zqPr>n)cb|DWr@pDl zGhp7bCi*7FW#Hu4+_z`pwI%Lyuv{cHc@B=Ztcfx7%Q^WQSi3dx4)zRs-lp))d7BD0 ze)GJY1Sg+;e*rAliC^YkjqVxLc1P9QFM*%ON1J2av-M#4tw?_7->Eol-b4Cr0LyPf z5@QqCvG-!neD9;nZ$~oU2VniQ#cwlMeg~4X_c7Qw+LH4c@H*z#Hi=w3kFSEQ$(u;d z!df`_ge%cmD+h#Qh# zI1P@s>;>o7&%5^wNuK51e?zszRBk-uzYGU7wlO1)c;pt zdFQe|iRU-<`2uV_Yo@=s`cz3=XR diff --git a/profiles/vcs/shaders/hdr/hdr_extract.frag b/profiles/vcs/shaders/hdr/hdr_extract.frag deleted file mode 100644 index 86b7782..0000000 --- a/profiles/vcs/shaders/hdr/hdr_extract.frag +++ /dev/null @@ -1,39 +0,0 @@ -#version 450 -//---------------------------------------------------------------------------// -// SimulateHDR - bright pass. -// -// Ported from PS_BloomExtract in SimulateHDR_Bloom.fxh. See -// shaders/hdr/LICENSE_SIMULATEHDR.txt (CC BY-SA 4.0). Changes were made. -// -// Runs at quarter resolution straight off the scene copy; the bilinear fetch of -// a full resolution source at reduced UVs is already a box filter, so no -// explicit downsample is needed. -//---------------------------------------------------------------------------// - -layout(set = 0, binding = 0) uniform sampler2D u_scene; - -layout(push_constant) uniform Push { - float sensitivity; - float threshold; - float saturation; // min(10, Saturation * 10) on the host side -} pc; - -layout(location = 0) in vec2 v_uv; -layout(location = 0) out vec4 o_color; - -float luma(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); } // Rec. 709 - -void main() { - vec3 c = texture(u_scene, v_uv).rgb; - const float l = luma(c); - - // Sensitivity curve, then divide out the luma so the threshold below is the - // only thing deciding how bright a pixel ends up. - c = pow(abs(c), vec3(pc.sensitivity)) / max(l, 0.001); - - const float bright = max(0.0, l - pc.threshold); - c *= bright; - c = mix(vec3(bright), c, pc.saturation); - - o_color = vec4(clamp(c, 0.0, 1.0), 0.0); -} diff --git a/profiles/vcs/shaders/hdr/hdr_extract.frag.spv b/profiles/vcs/shaders/hdr/hdr_extract.frag.spv deleted file mode 100644 index d0b0748a01fe319a8b5ec667b69a81e20d1f2110..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1440 zcmYk5%T7~K6oxmgfW*p0qZLs&l?e*gOSp(w8jZ1_jeuxO)rpA{2kJl{z=Vk-Cpz#w zd=X#3_i$q3_nots=}!Kxwf=QIYwy-;i{mMcq!a06dX=g(lWH&tZmg8O{$aoU@zb03 z>e_W9CR4o(G-fJ|r(;O*?)BUEIxd1fm?lo12C0WOi);Z;*q17^w9T9UZ`adh^cvVn zqouBE(H~B5&LC%>;LMRb4(#JBQTH?z^|zSKuTVdWHij%flX&OV*1wFcJ_&yP9BhX@ zg*|9)bi6=Xx^r`AP^Y96Kj*d+n#ufBEmxJWCvh5B%&N+v4 zHow963rK6p8?Wt7<&0lI8t<;=^G>w`*0_Q_0o=Vcw3jn?273j)0M_)|wXXt?`E8{6 z){Nb(qJKDbbUW-#Y)|BGp%2%4fF0lT2;F;dcIVOdK8Nq-F?LFyyRrKhSoR16{}RsF zz&bmykNfag^AF4@vIFbh&jWX^?OuIH?({iP&Qt(n;W*7d`rKq8sxy_qm5|FJoe+ z``MNg(Z7dof8R{>-$9p;++B3JKqKGNLpWz}&(^oEvEe^J*Kd7eeOJDrgZy1jN}Zol mR(E??>Q!FEJQmpf9qDcwAvFwR~QIGVJCFfXeglswC)&NS2tI?&o6ho`v-eEYM~sBdWJ)9%qx5! zZtou@eP9qQfL5qPzDVwpyEgzDk(a|9u>>wdHRhyo-YFy`Ne$`D_392;Beq z|0$yV*st}ci3MQ`GNb9T9gd&0iviT3QieaU?fwcJ;J6M3Eq+vl0D zKtgyu%X~fZPW^3iYx(aT-pzS6foH@ans@YGF1=qURpJS%^BMxq>H}3ioHXb4nY7WQ zJF}#t`LuHL4Oz14x|)KBTgH&wG2_m_a$d-5+QtsuT7ZH-*rdtReC rvkjo`%GxHmns3&ox-;4Wdfju5≫~&anY}8{fm; 1.0f) {\n" + << " ++vcs::g_draw_distance_runtime_telemetry.far_clip_set_hits;\n" + << " ctx.fpr[12] *= vcs::g_draw_distance_runtime_scales.world;\n" + << " }\n"; + } + + // VCS actor LOD extender. The established PSP VCS WidescreenFix + // replaces the call at the 0x08A24128 site: it writes s0+0x7A0 from + // s0+0x7A8 * multiplier and returns multiplier in f0. Reproduce that + // exact semantic before the original call; 1.0 keeps stock behavior. if (pc == 0x08A24128u) { body << " if (vcs::g_draw_distance_runtime_scales.entity > 1.0f) {\n" - << " const float dd_base = std::bit_cast(aot_mem.aot_load32(ctx.gpr[16] + static_cast(1960)));\n" + << " ++vcs::g_draw_distance_runtime_telemetry.actor_lod_hits;\n" << " const float dd_scale = vcs::g_draw_distance_runtime_scales.entity;\n" - << " aot_mem.aot_store32(ctx.gpr[16] + static_cast(1952), std::bit_cast(dd_base * dd_scale));\n" + << " const float dd_base = std::bit_cast(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast(1960)));\n" + << " aot_mem.aot_direct_store32(aot_gpr_16 + static_cast(1952), std::bit_cast(dd_base * dd_scale));\n" << " ctx.fpr[0] = dd_scale;\n" << " goto L_08A24138;\n" << " }\n"; } + // The model-info pointer table has just finished its initializer at + // this local label. Patch world/timed-object LOD fields here, while + // the guest GP and table are authoritative, instead of guessing at vblank. + if (pc == 0x08AEC918u) { + body << " vcs::draw_distance_world_table_ready(rt, ctx.gpr[28]);\n"; + } if (pc == 0x089CB38Cu) { body << " if (vcs::g_draw_distance_runtime_scales.npcs > 1.0f) {\n" + << " ++vcs::g_draw_distance_runtime_telemetry.npc_constant_hits;\n" << " const float dd_scale = vcs::g_draw_distance_runtime_scales.npcs;\n" << " ctx.gpr[19] = ctx.gpr[29] + static_cast(64);\n" << " ctx.gpr[30] = ctx.gpr[29] + static_cast(16);\n" @@ -1031,8 +1049,19 @@ std::string emit_function_source(const GeneratedFunctionInput &function, << " goto L_089CB3C8;\n" << " }\n"; } + // The common vehicle path branches unconditionally from AB8 to AC8 + // after its delay-slot multiply, so patching only AC0 misses most cars. + // Scale f12 immediately before that branch; the original delay-slot + // multiply still runs and preserves the game's dynamic term. + if (pc == 0x08B45AB8u) { + body << " if (vcs::g_draw_distance_runtime_scales.vehicles > 1.0f) {\n" + << " ++vcs::g_draw_distance_runtime_telemetry.vehicle_dynamic_hits;\n" + << " ctx.fpr[12] *= vcs::g_draw_distance_runtime_scales.vehicles;\n" + << " }\n"; + } if (pc == 0x08B45AC0u) { body << " if (vcs::g_draw_distance_runtime_scales.vehicles > 1.0f) {\n" + << " ++vcs::g_draw_distance_runtime_telemetry.vehicle_fallback_hits;\n" << " ctx.fpr[12] = 60.0f * vcs::g_draw_distance_runtime_scales.vehicles;\n" << " goto L_08B45AC8;\n" << " }\n";