improving graphics

improving graphics
This commit is contained in:
Jessica_Natalia
2026-08-20 02:45:26 -03:00
parent bd875e7d53
commit 261f2d962b
49 changed files with 1893 additions and 1415 deletions
+1
View File
@@ -50,3 +50,4 @@ SAVEDATA/
# Python
__pycache__/
*.pyc
/profiles/vcs/host/propershaders/private
+19
View File
@@ -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
+39
View File
@@ -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")
@@ -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');" ^
@@ -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');" ^
@@ -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');" ^
@@ -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';" ^
+3 -1
View File
@@ -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`.
+82 -5
View File
@@ -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
+6 -10
View File
@@ -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
@@ -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<std::uint32_t>(64);
ctx.gpr[30] = ctx.gpr[29] + static_cast<std::uint32_t>(16);
@@ -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<std::uint32_t>(7796), std::bit_cast<std::uint32_t>(aot_fpr_12));
local_pc = jump_target;
@@ -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<float>(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast<std::uint32_t>(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<float>(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast<std::uint32_t>(1960)));
aot_mem.aot_direct_store32(aot_gpr_16 + static_cast<std::uint32_t>(1952),
std::bit_cast<std::uint32_t>(dd_base * dd_scale));
std::bit_cast<std::uint32_t>(aot_fpr_12 * dd_scale));
ctx.fpr[0] = dd_scale;
goto L_08A24138;
}
@@ -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<std::uint32_t>(0), aot_run_words);
aot_gpr_16 = aot_run_words[0];
@@ -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<float>(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<float>(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;
}
@@ -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",
@@ -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<float4> CloudTexture0 : register(t0);
Texture2D<float4> 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<a.volumeBox.y||i>=(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<bottom||d.x-d.w>=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
+12 -1
View File
@@ -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<float, 16> model_to_world{};
std::array<float, 16> model_to_clip{};
std::array<float, 4> 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<float, 12> &view,
const std::array<float, 16> &projection,
const std::array<float, 6> &viewport,
const std::array<float, 8> &viewport,
const std::array<float, 3> &camera_position,
const GeGpuDrawDescriptor &draw,
std::uint32_t vertex_weight) noexcept;
File diff suppressed because it is too large Load Diff
-400
View File
@@ -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 <array>
#include <cstdint>
namespace vcs::hdr_shaders {
inline constexpr std::array<std::uint32_t, 233> 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<std::uint32_t, 360> 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<std::uint32_t, 299> 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<std::uint32_t, 372> 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<std::uint32_t, 1320> 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
+10 -1
View File
@@ -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<float, 6> cloud_viewport{
const std::array<float, 8> 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<float>(data24(commands[0x4Cu]) & 0xFFFFu) / 16.0f,
static_cast<float>(data24(commands[0x4Du]) & 0xFFFFu) / 16.0f};
// VCS' authoritative camera origin. The affine GE view used by
+5 -3
View File
@@ -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<std::uint32_t>((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;
@@ -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
@@ -0,0 +1,60 @@
#include "propershaders/ProperShadersBridge.hpp"
#include "vcs_config.hpp"
#include <unordered_map>
namespace vcs {
namespace {
struct PaletteIdentity {
std::uint32_t checksum{};
bool mutated{};
std::uint64_t last_epoch{};
};
std::unordered_map<std::uint64_t, PaletteIdentity> g_palettes;
std::uint64_t g_epoch{};
std::uint64_t palette_key(const GeGpuDrawDescriptor &draw) noexcept {
std::uint64_t key = static_cast<std::uint64_t>(draw.texture_address) << 32u;
key ^= static_cast<std::uint64_t>(draw.clut_address);
key ^= static_cast<std::uint64_t>(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
@@ -0,0 +1,28 @@
#pragma once
#include "ge_gpu_backend.hpp"
#include <cstdint>
#include <string>
#include <string_view>
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
@@ -0,0 +1,108 @@
#include "propershaders/ProperShadersConfig.hpp"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <sstream>
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<char>(std::tolower(c));});
return value;
}
std::string uncomment(std::string value) {
bool quoted=false; char q='\0';
for(std::size_t i=0;i<value.size();++i){const char c=value[i]; if(c=='\''||c=='"'){if(!quoted){quoted=true;q=c;}else if(q==c)quoted=false;}else if(!quoted&&(c==';'||c=='#')){value.resize(i);break;}}
return trim(std::move(value));
}
bool boolean(std::string value,bool &out){value=lower(trim(std::move(value)));if(value=="1"||value=="true"||value=="yes"||value=="on"){out=true;return true;}if(value=="0"||value=="false"||value=="no"||value=="off"){out=false;return true;}return false;}
bool real(std::string value,float lo,float hi,float &out){value=trim(std::move(value));char *end=nullptr;errno=0;float v=std::strtof(value.c_str(),&end);if(errno!=0||end==value.c_str()||*end!='\0'||!std::isfinite(v)||v<lo||v>hi)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'||v<lo||v>hi)return false;out=static_cast<std::uint32_t>(v);return true;}
void warn(std::vector<std::string> &warnings,std::size_t line,const std::string &text){std::ostringstream s;s<<"line "<<line<<": ProperShaders.ini: "<<text;warnings.push_back(s.str());}
} // namespace
void load_proper_shaders_configuration(ProperShadersConfiguration &cfg,
const std::filesystem::path &path,
std::vector<std::string> &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
@@ -0,0 +1,110 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>
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<std::string> &warnings);
} // namespace vcs
+8 -102
View File
@@ -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.
+3 -39
View File
@@ -1,5 +1,7 @@
#pragma once
#include "propershaders/ProperShadersConfig.hpp"
#include <cstdint>
#include <filesystem>
#include <string>
@@ -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
+83 -46
View File
@@ -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 <algorithm>
#include <bit>
@@ -39,6 +40,7 @@
#include <fstream>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -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<std::uint32_t, OriginalWorldModel> 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<unsigned char>(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());
}
}
}
+22 -5
View File
@@ -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
-5
View File
@@ -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<std::uint64_t>::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
+4 -2
View File
@@ -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();
}
@@ -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
-35
View File
@@ -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;
}
Binary file not shown.
-35
View File
@@ -1,35 +0,0 @@
#version 450
//---------------------------------------------------------------------------//
// SimulateHDR - octave combine.
//
// Ported from PS_BloomCombine in SimulateHDR_Bloom.fxh. See
// shaders/hdr/LICENSE_SIMULATEHDR.txt (CC BY-SA 4.0). Changes were made.
//
// Both the horizontal-only and the fully blurred target of each octave go into
// the sum -- the horizontal ones are what give the bloom its streak, so
// dropping them would change the look.
//---------------------------------------------------------------------------//
layout(set = 0, binding = 0) uniform sampler2D u_h_a;
layout(set = 0, binding = 1) uniform sampler2D u_v_a;
layout(set = 0, binding = 2) uniform sampler2D u_h_b;
layout(set = 0, binding = 3) uniform sampler2D u_v_b;
layout(set = 0, binding = 4) uniform sampler2D u_h_c;
layout(set = 0, binding = 5) uniform sampler2D u_v_c;
layout(push_constant) uniform Push {
float mix_scale; // intensity * 10 / 6 on the host side
} pc;
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 o_color;
void main() {
vec3 bloom = texture(u_h_a, v_uv).rgb;
bloom += texture(u_v_a, v_uv).rgb;
bloom += texture(u_h_b, v_uv).rgb;
bloom += texture(u_v_b, v_uv).rgb;
bloom += texture(u_h_c, v_uv).rgb;
bloom += texture(u_v_c, v_uv).rgb;
o_color = vec4(bloom * pc.mix_scale, 0.0);
}
Binary file not shown.
-147
View File
@@ -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);
}
Binary file not shown.
-39
View File
@@ -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);
}
Binary file not shown.
@@ -1,17 +0,0 @@
#version 450
//---------------------------------------------------------------------------//
// SimulateHDR - fullscreen triangle, shared by every pass.
//
// Ported to Vulkan/GLSL for VCSNative from ProperShaders' SimulateHDR, itself a
// fork of "Blooming HDR" by Jose Negrete (BlueSkyDefender), CC BY-SA 4.0.
// See shaders/hdr/LICENSE_SIMULATEHDR.txt. Changes were made.
//
// No vertex buffer: three vertices covering the screen, derived from the index.
//---------------------------------------------------------------------------//
layout(location = 0) out vec2 v_uv;
void main() {
v_uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(v_uv * 2.0 - 1.0, 0.0, 1.0);
}
Binary file not shown.
+24 -4
View File
@@ -36,6 +36,7 @@ if "host/savedata_dialog.cpp" in (profile / "CMakeLists.txt").read_text(encoding
require(generated / "generated_units.hpp", '#include "vcs_draw_distance_patch.hpp"',
"generated draw-distance state header")
checks = [
("generated_unit_0133.cpp", "g_draw_distance_runtime_telemetry.far_clip_set_hits", "world far-clip setter"),
("generated_unit_0136.cpp", "g_draw_distance_runtime_scales.entity", "entity local label"),
("generated_unit_0113.cpp", "g_draw_distance_runtime_scales.npcs", "NPC local label"),
("generated_unit_0208.cpp", "g_draw_distance_runtime_scales.vehicles", "vehicle local label"),
@@ -43,10 +44,13 @@ checks = [
for filename, needle, label in checks:
require(generated / filename, needle, label)
codegen = tools / "vcs_codegen_main.cpp"
for pc in ("0x08A24128u", "0x089CB38Cu", "0x08B45AC0u"):
for pc in ("0x08A1AD6Cu", "0x08A24128u", "0x08AEC918u", "0x089CB38Cu", "0x08B45AB8u", "0x08B45AC0u"):
require(codegen, pc, f"future codegen preserves {pc}")
require(host / "vcs_profile.cpp", "draw_distance_vblank_tick(rt, ctx.gpr[28], display_vblank_index);",
"world draw-distance vblank maintenance")
require(host / "vcs_draw_distance_patch.cpp",
"std::max({g_config.world, g_config.vehicles, g_config.npcs})",
"world multiplier participates in common camera/entity LOD gate")
dx12 = host / "ge_gpu_backend_dx12.cpp"
require(dx12, "Dx12BlendPlan dx12_blend_plan", "DX12 generalized PSP blend mapper")
@@ -54,11 +58,25 @@ require(dx12, "D3D12_BLEND_BLEND_FACTOR", "DX12 FIX blend constant")
require(dx12, "D3D12_BLEND_INV_BLEND_FACTOR", "DX12 complementary FIX blend")
require(dx12, "plan.src = D3D12_BLEND_SRC_ALPHA;", "DX12 safe translucent fallback")
require(dx12, "PSPRECOMP_DX12_BLEND_DIAG", "DX12 unsupported-blend diagnostic")
require(dx12, "g_draw_distance_runtime_scales.world", "DX12 world fog follows DrawDistance.World")
require(dx12, "hw.fog_end * fog_distance_scale", "DX12 fog end is extended with draw distance")
require(dx12, "hw.fog_slope / fog_distance_scale", "DX12 fog slope preserves scaled curve")
require(dx12, "compile_pipe(pipe, false", "ProperShaders shadow-free material-pipe retry")
require(dx12, "pipe compiled fallback:", "ProperShaders pipe recovery runtime marker")
require(dx12, "constexpr UINT pipe_flags = D3DCOMPILE_OPTIMIZATION_LEVEL3;",
"optional material pipes do not promote FXC warnings to fatal errors")
private_shaders = host / "propershaders/private/PrivateShaders.cpp"
require(private_shaders, "bool material_shadows", "ProperShaders separable material-shadow variant")
require(private_shaders, "shadow_enabled ? proper_shaders_material_shadow_hlsl() : nullptr",
"ProperShaders shadow-free fallback excludes receiver HLSL")
for filename in ("BuildingPipe.cpp", "SkinPipe.cpp", "VehiclePipe.cpp"):
pipe = host / "propershaders/private" / filename
require(pipe, "#if PROPER_SHADOW_ENABLED", f"{filename} guards shadow hooks")
ini = (profile / "config/VCSNative.ini").read_text(encoding="utf-8", errors="replace")
dd = ini.split("[DrawDistance]", 1)[1].split("[", 1)[0]
if "Enabled=false" not in dd or "World=1.50" not in dd:
errors.append("DrawDistance config must remain opt-in with 1.50x values prefilled")
if "Enabled=true" not in dd or "Objects=2.00" not in dd or "Vehicles=2.00" not in dd or "NPCs=2.00" not in dd:
errors.append("DrawDistance test profile must enable 2.00x objects/vehicles/NPCs")
if errors:
print("CORRECTNESS 0.1 SOURCE AUDIT: FAIL")
@@ -70,5 +88,7 @@ print("CORRECTNESS 0.1 SOURCE AUDIT: PASS")
print(" - audio release/timing guards present")
print(" - savedata LIST UI is firmware-style HLE rendered in the PSP framebuffer")
print(" - draw-distance local AOT labels + regeneration hooks present")
print(" - world distance participates in the common camera/entity LOD multiplier")
print(" - ProperShaders material pipes survive a shadow-receiver compile failure")
print(" - DX12 unsafe opaque blend fallback removed")
print(" - draw distance remains opt-in for baseline FPS comparison")
print(" - draw distance test profile enables 2x objects/vehicles/NPCs")
@@ -24,7 +24,7 @@ for token in (
'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','aot_resident_region_weight=6',
'aot_gpr_resident_occurrences=914095','aot_fpr_resident_occurrences=146809',
'aot_gpr_resident_occurrences=914095','aot_fpr_resident_occurrences=146813',
'radio_atrac_identity_guard=1','save_exitdelete_semantics=1','ge_async_default=0',
'parallel_vertex_decode_default=0'):
need(token in log,'metadata '+token)
@@ -20,7 +20,7 @@ for token in (
'aot_register_residency=1','aot_register_residency_units=230',
'aot_gpr_resident_slots=6','aot_fpr_resident_slots=4',
('aot_gpr_resident_occurrences=914095' if is_v810 else 'aot_gpr_resident_occurrences=876226'),
('aot_fpr_resident_occurrences=146809' if is_v810 else 'aot_fpr_resident_occurrences=145774'),
('aot_fpr_resident_occurrences=146813' if is_v810 else 'aot_fpr_resident_occurrences=145774'),
'trusted_chain=1','outer_aot_view_reuse=1','radio_atrac_identity_guard=1',
'ge_async_default=0','parallel_vertex_decode_default=0'):
need(token in log,'metadata '+token)
@@ -38,8 +38,8 @@ need('PSPRECOMP_AOT_REGCACHE_BEGIN' in tool and 'AOT_REGCACHE_SYNC_OUT' in tool
need('vcs' not in tool.lower() and '0x088' not in tool and '0x089' not in tool and '0x08a' not in tool.lower(),'generic optimizer has no VCS/address coupling')
need(manifest['files']==234,'manifest corpus contains 234 units')
if is_v810:
need(manifest['gpr_occurrences']==914095 and manifest['gpr_total']==1266110 and manifest.get('resident_weight')==6,'V8.10 manifest GPR coverage/region weight exact')
need(manifest['fpr_occurrences']==146809 and manifest['fpr_total']==198361,'V8.10 manifest FPR coverage exact')
need(manifest['gpr_occurrences']==914095 and manifest['gpr_total']==1266111 and manifest.get('resident_weight')==6,'V8.10 manifest GPR coverage/region weight exact')
need(manifest['fpr_occurrences']==146813 and manifest['fpr_total']==198365,'V8.10 manifest FPR coverage exact')
else:
need(manifest['gpr_occurrences']==876226 and manifest['gpr_total']==1188249,'manifest GPR coverage exact')
need(manifest['fpr_occurrences']==145774 and manifest['fpr_total']==196805,'manifest FPR coverage exact')
+55 -2
View File
@@ -172,7 +172,36 @@ int main() {
"SAVE_REPRO must be disabled by default");
{
std::ofstream proper(root / "ProperShaders.ini", std::ios::trunc);
proper << "[VolumetricClouds]\n"
proper << "[ProperShaders]\n"
<< "Enabled=true\n"
<< "[BuildingPipe]\n"
<< "Enabled=true\n"
<< "[SkinPipe]\n"
<< "Enabled=false\n"
<< "[VehiclePipe]\n"
<< "Enabled=true\n"
<< "PaletteClassifier=false\n"
<< "[RealtimeShadows]\n"
<< "Enabled=true\n"
<< "MapResolution=3072\n"
<< "WorldRadius=220\n"
<< "DepthRange=600\n"
<< "MapBias=0.002\n"
<< "PcfRadius=2\n"
<< "UseCloudSunDirection=false\n"
<< "LightDirectionX=0.2\n"
<< "LightDirectionY=-0.4\n"
<< "LightDirectionZ=0.9\n"
<< "Strength=0.55\n"
<< "DebugMode=6\n"
<< "ContactShadows=false\n"
<< "ContactStrength=0.31\n"
<< "Steps=18\n"
<< "MaxDistancePixels=64\n"
<< "Thickness=0.003\n"
<< "LightScreenX=-0.4\n"
<< "LightScreenY=0.8\n"
<< "[VolumetricClouds]\n"
<< "Enabled=true\n"
<< "DownscaleDiv=4\n"
<< "Layers=3\n"
@@ -192,7 +221,31 @@ int main() {
"DirectX12 sync-recovery must force legacy GE async off");
require(parallel_decode_default != nullptr && std::string(parallel_decode_default) == "0",
"DirectX12 stable recovery must force parallel vertex decode off");
const auto &clouds = vcs::vcs_configuration().volumetric_clouds;
const auto &proper = vcs::vcs_configuration().proper_shaders;
require(proper.enabled && proper.building_pipe.enabled && !proper.skin_pipe.enabled &&
proper.vehicle_pipe.enabled && !proper.vehicle_pipe.palette_classifier,
"ProperShaders material-pipe controls were not parsed");
require(proper.realtime_shadows.enabled &&
proper.realtime_shadows.map_resolution == 3072u &&
std::abs(proper.realtime_shadows.world_radius - 220.0f) < 0.0001f &&
std::abs(proper.realtime_shadows.depth_range - 600.0f) < 0.0001f &&
std::abs(proper.realtime_shadows.map_bias - 0.002f) < 0.0001f &&
proper.realtime_shadows.pcf_radius == 2u &&
!proper.realtime_shadows.use_cloud_sun_direction &&
std::abs(proper.realtime_shadows.light_direction_x - 0.2f) < 0.0001f &&
std::abs(proper.realtime_shadows.light_direction_y + 0.4f) < 0.0001f &&
std::abs(proper.realtime_shadows.light_direction_z - 0.9f) < 0.0001f &&
std::abs(proper.realtime_shadows.strength - 0.55f) < 0.0001f &&
proper.realtime_shadows.debug_mode == 6u &&
!proper.realtime_shadows.contact_shadows &&
std::abs(proper.realtime_shadows.contact_strength - 0.31f) < 0.0001f &&
proper.realtime_shadows.steps == 18u &&
std::abs(proper.realtime_shadows.max_distance_pixels - 64.0f) < 0.0001f &&
std::abs(proper.realtime_shadows.thickness - 0.003f) < 0.0001f &&
std::abs(proper.realtime_shadows.light_screen_x + 0.4f) < 0.0001f &&
std::abs(proper.realtime_shadows.light_screen_y - 0.8f) < 0.0001f,
"ProperShaders realtime-shadow controls were not parsed");
const auto &clouds = proper.volumetric_clouds;
require(clouds.enabled, "ProperShaders.ini VolumetricClouds.Enabled was not parsed");
require(clouds.downscale_div == 4u && clouds.layers == 3u &&
clouds.shadow_steps == 6u,
+36 -7
View File
@@ -1000,22 +1000,40 @@ std::string emit_function_source(const GeneratedFunctionInput &function,
break;
}
// VCS draw-distance local-label patches. These addresses are valid
// AOT entries, but normal gameplay reaches them through local gotos,
// which bypasses Runtime::register_function replacements. Keep the
// exact original path at scale 1.0 and only short-circuit when the
// user explicitly enables extended draw distance.
// VCS world far-clip extender. 0x08A1AD6C is the actual
// CDraw::SetFarClipZ-style store into gp+7796. Multiply the value
// immediately before the guest store so every camera mode observes
// the configured world distance without vblank double-scaling.
if (pc == 0x08A1AD6Cu) {
body << " if (vcs::g_draw_distance_runtime_scales.world > 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<float>(aot_mem.aot_load32(ctx.gpr[16] + static_cast<std::uint32_t>(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<std::uint32_t>(1952), std::bit_cast<std::uint32_t>(dd_base * dd_scale));\n"
<< " const float dd_base = std::bit_cast<float>(aot_mem.aot_direct_load32(aot_gpr_16 + static_cast<std::uint32_t>(1960)));\n"
<< " aot_mem.aot_direct_store32(aot_gpr_16 + static_cast<std::uint32_t>(1952), std::bit_cast<std::uint32_t>(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<std::uint32_t>(64);\n"
<< " ctx.gpr[30] = ctx.gpr[29] + static_cast<std::uint32_t>(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";