Merge pull request #89: Shadow resolution, cascade range, soft and contact shadows, anisotropic filtering (itaskaev2)

Merged with applezra's follow-ups on the same branch: the sun shadow filter narrowed by the shadow map scale
(translator patch 0011), anisotropic filtering forced only on tiling samplers, and TOTK_DUMP_UBO with a vec4 count.

Conflicts with dev: cfg.c (names, display section, defaults next to aspect and device_profile), menu.cpp (the new rows
after the aspect row), vk_texture.c (the pool alias check takes scale == 1 and dev's bind_pool_memory size argument),
build_xlate.py (patches 0005-0011 are ours; the Linux note kept), totk.ini.example (both sections). The contact pass
release went into gfx_present.c, the shared half of the old gfx_window.c.

aniso defaults to game: the picture stays as before until a player raises it in the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valiantsin DzerakhandClaude Opus 5.5 committed 2026-10-31 14:44:48 +03:00
commit 6a04fe9222
21 files changed
+891 -25

No files matched your search

+9
View File
@@ -10,6 +10,7 @@ set cloth TOTK_CLOTH_REALTIME 1
set bounce TOTK_HIGHFPS_FIX 1
set output TOTK_PATCH_OUTPUT 0
set scene TOTK_PATCH_SCENE 0
set cascade TOTK_PATCH_CASCADE 0
# 60 fps: the adaptive present-interval controller (main+0x2db174, 1.2.1 0x8f26c8) gets the minimum interval 1.
site fps60 main 2db204 6b08015f 7100055f # clamp: cmp w10, #1 (was cmp w10, w8 = minimum)
@@ -960,3 +961,11 @@ spin subsdk0 1c4e54 1c4e60 # 4 instructions; loads
spin sdk 2d3654 2d3668 # 6 instructions; loads
spin sdk 443864 443884 # 9 instructions; loads
spin sdk 541e54 541f98 # 32 instructions; loads
# Sun shadow cascade distances (runtime/resolution.c res_shadow_range switches the set on). The depth shadow parameter
# object (main+0x6fa3c0) registers depth_shadow_near_0_1..3 = 20, 40, 80 m and depth_shadow_far_0 = 160 m as
# immediates, and the field's .bgsdw files do not override them; with the set on they come from page +64..+76.
site cascade main 6fabb8 52a83408 @page:x8 b9404108 # mov w8, #20.0 -> ldr w8, [page+64]
site cascade main 6fac40 52a84408 @page:x8 b9404508 # mov w8, #40.0 -> ldr w8, [page+68]
site cascade main 6facc8 52a85408 @page:x8 b9404908 # mov w8, #80.0 -> ldr w8, [page+72]
site cascade main 6fad4c 52a86408 @page:x8 b9404d08 # mov w8, #160.0 -> ldr w8, [page+76]
+9 -4
View File
@@ -30,24 +30,24 @@ static int save_ok = 1; // the last cfg_save_to wrote (o
// Every name the runtime reads (and the patch sets in generated/), without the prefix. A file key outside this list is
// logged as unknown. A new variable goes here too, or its ini key logs a harmless "unknown key" line.
static const char *const known[] = {
"AB", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_OFF", "AUDIO_PREFILL_MS",
"AB", "ANISO", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_OFF", "AUDIO_PREFILL_MS",
"BACKGROUND_WINDOW", "BIND_A", "BIND_B", "BIND_DOWN", "BIND_L", "BIND_LCLICK", "BIND_LEFT", "BIND_LSTICK_DOWN", "BIND_LSTICK_LEFT",
"BIND_LSTICK_RIGHT", "BIND_LSTICK_UP", "BIND_MINUS",
"BIND_PLUS", "BIND_R", "BIND_RCLICK", "BIND_RIGHT", "BIND_RSTICK_DOWN", "BIND_RSTICK_LEFT", "BIND_RSTICK_RIGHT", "BIND_RSTICK_UP",
"BIND_SKIP", "BIND_STATS", "BIND_UP", "BIND_X", "BIND_Y",
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLOTH_REALTIME", "CORE_DIAG", "CRIT", "CRIT_OUT", "DESC_TEMPLATE", "DEVICE_PROFILE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG",
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLOTH_REALTIME", "CONTACT_DEBUG", "CONTACT_SHADOWS", "CORE_DIAG", "CRIT", "CRIT_OUT", "DESC_TEMPLATE", "DEVICE_PROFILE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG",
"EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES", "FLIGHT_SPIKE_MS",
"FLIGHT_THUMBS", "FPS60", "FPS_LIMIT", "FREEZE_IDLE", "FREE_CORES", "FULLSCREEN", "GPL", "GPL_DRAW_BUILD", "GPU",
"GPU_INDEX", "GPU_NAME", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD",
"LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LOCALE", "LOD_GUARD", "MEMORY", "MENU_LANG", "MENU_TEST",
"MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MIRROR_GAP", "MOTION", "MOUSE_SENS", "MOVIE_HOST", "MOVIE_PACE",
"ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PATCH_OUTPUT", "PATCH_SCENE", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT",
"ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PATCH_OUTPUT", "PATCH_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT",
"PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES",
"PROFILE_MAIN", "PROFILE_RENDER", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_END", "REEXEC",
"RENDER_SCALE", "REPLAY_ASYNC",
"REPLAY_CHECK", "RESOLVE_CHECK", "ROMFS", "ROMFS_INDEX", "ROMFS_MMAP", "ROMFS_MODS", "ROMFS_PATCHES",
"ROMFS_PROOF_DIR", "ROMFS_VERIFY", "RUMBLE", "RUMBLE_CURVE", "RUMBLE_STRENGTH", "RUMBLE_TRIGGERS", "SHADER_CACHE",
"SHADER_MEMO", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", "SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_US", "STICK_DEADZONE", "SWKBD_ANSWER",
"SHADER_MEMO", "SHADOW_RANGE", "SHADOW_SCALE", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", "SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_US", "STICK_DEADZONE", "SWKBD_ANSWER",
"SWKBD_TEST", "TEST_PAD", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_MEMO", "TEX_VRAM_PRESSURE", "TIMER_1MS", "TRACE", "UBO_SNAPSHOT", "UPSCALER", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NO_FLOAT64",
"VK_PRESENT", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VOLUME", "VRAM_HEADROOM", "VS_OUTS",
"WB_FENCE_LIMIT", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_PROCS", "ZSTD_CHECK", "ZSTD_NATIVE",
@@ -61,6 +61,8 @@ static const struct { const char *key, *section; } sections[] = {
{"GPU_INDEX", "display"}, {"OUTPUT_RES", "display"}, {"RENDER_SCALE", "display"}, {"UPSCALER", "display"},
{"SHARPNESS", "display"}, {"ASPECT", "display"},
{"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"},
{"ANISO", "display"}, {"SHADOW_SCALE", "display"}, {"SHADOW_RANGE", "display"}, {"CONTACT_SHADOWS", "display"},
{"PCSS", "display"},
{"MENU_LANG", "language"}, {"LANG", "language"}, {"OPERATION_MODE", "language"},
{"VOLUME", "audio"},
{"MOTION", "input"}, {"RUMBLE_STRENGTH", "input"}, {"RUMBLE_CURVE", "input"}, {"RUMBLE_TRIGGERS", "input"},
@@ -97,6 +99,9 @@ static const CfgMeta metas[] = {
{"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE},
{"ASPECT", "fit", "fit|stretch", LIVE},
{"DEVICE_PROFILE", "auto", "auto|desktop|deck", RESTART},
{"ANISO", "game", "game|2|4|8|16", RESTART}, {"SHADOW_SCALE", "1", "1..4", RESTART},
{"SHADOW_RANGE", "100", "50..400", RESTART}, {"CONTACT_SHADOWS", "0", "0..100", RESTART},
{"PCSS", "0", "0..200", RESTART},
// Game
{"FPS_LIMIT", "60", "30|40|45|60|90|120|144|display", LIVE}, {"FPS60", "1", "0|1", LIVE},
{"REALTIME", "1", "0|1", RESTART}, {"CLOTH_REALTIME", "1", "0|1", RESTART},
+156
View File
@@ -0,0 +1,156 @@
// Generated by tools/build_shaders.py from runtime/shaders/contact_shadow.comp; do not edit.
// source sha256 b452a1920e59c4d83b2c994b0c6573cc1e479f7032a1310a7432d11c5c2e9908
#pragma once
#include <stdint.h>
static const uint32_t contact_shadow_spv[] = {
0x07230203, 0x00010300, 0x000d000b, 0x000001b1, 0x00000000, 0x00020011, 0x00000001, 0x00020011,
0x00000031, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e,
0x00000000, 0x00000001, 0x0006000f, 0x00000005, 0x00000004, 0x6e69616d, 0x00000000, 0x00000069,
0x00060010, 0x00000004, 0x00000011, 0x00000008, 0x00000008, 0x00000001, 0x00030047, 0x00000027,
0x00000002, 0x00050048, 0x00000027, 0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000027,
0x00000001, 0x00000023, 0x00000010, 0x00050048, 0x00000027, 0x00000002, 0x00000023, 0x00000020,
0x00050048, 0x00000027, 0x00000003, 0x00000023, 0x00000030, 0x00040047, 0x00000069, 0x0000000b,
0x0000001c, 0x00040047, 0x00000088, 0x00000021, 0x00000000, 0x00040047, 0x00000088, 0x00000022,
0x00000000, 0x00040047, 0x0000013f, 0x00000021, 0x00000001, 0x00040047, 0x0000013f, 0x00000022,
0x00000000, 0x00040047, 0x0000015c, 0x0000000b, 0x00000019, 0x00020013, 0x00000002, 0x00030021,
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
0x00000002, 0x00040017, 0x0000000a, 0x00000006, 0x00000003, 0x0004002b, 0x00000006, 0x0000001b,
0x40000000, 0x0004002b, 0x00000006, 0x0000001d, 0x3f800000, 0x00040015, 0x00000020, 0x00000020,
0x00000000, 0x0004002b, 0x00000020, 0x00000021, 0x00000000, 0x00040017, 0x00000026, 0x00000006,
0x00000004, 0x0006001e, 0x00000027, 0x00000026, 0x00000026, 0x00000026, 0x00000026, 0x00040020,
0x00000028, 0x00000009, 0x00000027, 0x0004003b, 0x00000028, 0x00000029, 0x00000009, 0x00040015,
0x0000002a, 0x00000020, 0x00000001, 0x0004002b, 0x0000002a, 0x0000002b, 0x00000000, 0x00040020,
0x0000002c, 0x00000009, 0x00000006, 0x0004002b, 0x00000020, 0x00000030, 0x00000001, 0x0004002b,
0x00000020, 0x0000003f, 0x00000002, 0x0004002b, 0x00000006, 0x00000043, 0x3f000000, 0x0004002b,
0x00000006, 0x00000059, 0x4253ee82, 0x0004002b, 0x00000006, 0x0000005b, 0x3d897143, 0x0004002b,
0x00000006, 0x0000005c, 0x3bbf4590, 0x0005002c, 0x00000007, 0x0000005d, 0x0000005b, 0x0000005c,
0x00040017, 0x00000064, 0x0000002a, 0x00000002, 0x00040017, 0x00000067, 0x00000020, 0x00000003,
0x00040020, 0x00000068, 0x00000001, 0x00000067, 0x0004003b, 0x00000068, 0x00000069, 0x00000001,
0x00040017, 0x0000006a, 0x00000020, 0x00000002, 0x00020014, 0x0000006e, 0x0004002b, 0x0000002a,
0x00000072, 0x00000002, 0x00090019, 0x00000085, 0x00000006, 0x00000001, 0x00000000, 0x00000000,
0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x00000086, 0x00000085, 0x00040020, 0x00000087,
0x00000000, 0x00000086, 0x0004003b, 0x00000087, 0x00000088, 0x00000000, 0x0004002b, 0x00000006,
0x0000008f, 0x00000000, 0x0004002b, 0x00000006, 0x00000092, 0x3f7fbe77, 0x0004002b, 0x00000006,
0x0000009e, 0x42a00000, 0x00040020, 0x000000a8, 0x00000009, 0x00000026, 0x0004002b, 0x0000002a,
0x000000b4, 0x00000001, 0x0004002b, 0x00000020, 0x000000b5, 0x00000003, 0x0004002b, 0x00000006,
0x000000bb, 0x3ecccccd, 0x0004002b, 0x00000006, 0x000000bc, 0x40400000, 0x0004002b, 0x00000006,
0x000000c0, 0x3e19999a, 0x0004002b, 0x00000006, 0x000000c2, 0x3c23d70a, 0x0004002b, 0x00000006,
0x000000cb, 0x3e800000, 0x0004002b, 0x0000002a, 0x000000da, 0x00000010, 0x0004002b, 0x00000006,
0x000000e6, 0xbd4ccccd, 0x0005002c, 0x00000007, 0x000000f0, 0x0000008f, 0x0000008f, 0x00040017,
0x000000f1, 0x0000006e, 0x00000002, 0x0005002c, 0x00000007, 0x000000f8, 0x0000001d, 0x0000001d,
0x0004002b, 0x00000006, 0x00000114, 0x3ca3d70a, 0x0004002b, 0x00000006, 0x00000116, 0x3b03126f,
0x0004002b, 0x00000006, 0x0000012b, 0x42200000, 0x0004002b, 0x0000002a, 0x00000137, 0x00000003,
0x00090019, 0x0000013d, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002,
0x00000008, 0x00040020, 0x0000013e, 0x00000000, 0x0000013d, 0x0004003b, 0x0000013e, 0x0000013f,
0x00000000, 0x0004002b, 0x00000020, 0x0000015b, 0x00000008, 0x0006002c, 0x00000067, 0x0000015c,
0x0000015b, 0x0000015b, 0x00000030, 0x0005002c, 0x00000007, 0x000001ad, 0x00000043, 0x00000043,
0x0004002b, 0x00000006, 0x000001af, 0x3dcccccd, 0x0004002b, 0x00000006, 0x000001b0, 0x3d800000,
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x000300f7,
0x0000015d, 0x00000000, 0x000300fb, 0x00000021, 0x0000015e, 0x000200f8, 0x0000015e, 0x0004003d,
0x00000067, 0x0000006b, 0x00000069, 0x0007004f, 0x0000006a, 0x0000006c, 0x0000006b, 0x0000006b,
0x00000000, 0x00000001, 0x0004007c, 0x00000064, 0x0000006d, 0x0000006c, 0x00050051, 0x0000002a,
0x00000071, 0x0000006d, 0x00000000, 0x00060041, 0x0000002c, 0x00000073, 0x00000029, 0x00000072,
0x00000021, 0x0004003d, 0x00000006, 0x00000074, 0x00000073, 0x0004006e, 0x0000002a, 0x00000075,
0x00000074, 0x000500af, 0x0000006e, 0x00000076, 0x00000071, 0x00000075, 0x000400a8, 0x0000006e,
0x00000077, 0x00000076, 0x000300f7, 0x00000079, 0x00000000, 0x000400fa, 0x00000077, 0x00000078,
0x00000079, 0x000200f8, 0x00000078, 0x00050051, 0x0000002a, 0x0000007b, 0x0000006d, 0x00000001,
0x00060041, 0x0000002c, 0x0000007c, 0x00000029, 0x00000072, 0x00000030, 0x0004003d, 0x00000006,
0x0000007d, 0x0000007c, 0x0004006e, 0x0000002a, 0x0000007e, 0x0000007d, 0x000500af, 0x0000006e,
0x0000007f, 0x0000007b, 0x0000007e, 0x000200f9, 0x00000079, 0x000200f8, 0x00000079, 0x000700f5,
0x0000006e, 0x00000080, 0x00000076, 0x0000015e, 0x0000007f, 0x00000078, 0x000300f7, 0x00000082,
0x00000000, 0x000400fa, 0x00000080, 0x00000081, 0x00000082, 0x000200f8, 0x00000081, 0x000200f9,
0x0000015d, 0x000200f8, 0x00000082, 0x0004003d, 0x00000086, 0x00000089, 0x00000088, 0x00040064,
0x00000085, 0x0000008b, 0x00000089, 0x0007005f, 0x00000026, 0x0000008c, 0x0000008b, 0x0000006d,
0x00000002, 0x0000002b, 0x00050051, 0x00000006, 0x0000008d, 0x0000008c, 0x00000000, 0x000500bc,
0x0000006e, 0x00000090, 0x0000008d, 0x0000008f, 0x000500be, 0x0000006e, 0x00000093, 0x0000008d,
0x00000092, 0x000500a6, 0x0000006e, 0x00000094, 0x00000090, 0x00000093, 0x000300f7, 0x00000096,
0x00000000, 0x000400fa, 0x00000094, 0x00000095, 0x00000096, 0x000200f8, 0x00000095, 0x000200f9,
0x0000015d, 0x000200f8, 0x00000096, 0x00060041, 0x0000002c, 0x0000009a, 0x00000029, 0x0000002b,
0x0000003f, 0x0004003d, 0x00000006, 0x0000009b, 0x0000009a, 0x00050085, 0x00000006, 0x0000009c,
0x0000008d, 0x0000009b, 0x000500ba, 0x0000006e, 0x0000009f, 0x0000009c, 0x0000009e, 0x000300f7,
0x000000a1, 0x00000000, 0x000400fa, 0x0000009f, 0x000000a0, 0x000000a1, 0x000200f8, 0x000000a0,
0x000200f9, 0x0000015d, 0x000200f8, 0x000000a1, 0x0004006f, 0x00000007, 0x000000a5, 0x0000006d,
0x00050081, 0x00000007, 0x000000a7, 0x000000a5, 0x000001ad, 0x00050041, 0x000000a8, 0x000000a9,
0x00000029, 0x00000072, 0x0004003d, 0x00000026, 0x000000aa, 0x000000a9, 0x0007004f, 0x00000007,
0x000000ab, 0x000000aa, 0x000000aa, 0x00000002, 0x00000003, 0x00050085, 0x00000007, 0x000000ac,
0x000000a7, 0x000000ab, 0x0005008e, 0x00000007, 0x00000167, 0x000000ac, 0x0000001b, 0x00050083,
0x00000007, 0x00000169, 0x00000167, 0x000000f8, 0x00050051, 0x00000006, 0x0000016b, 0x00000169,
0x00000000, 0x00050085, 0x00000006, 0x0000016d, 0x0000016b, 0x0000009c, 0x00060041, 0x0000002c,
0x0000016e, 0x00000029, 0x0000002b, 0x00000021, 0x0004003d, 0x00000006, 0x0000016f, 0x0000016e,
0x00050088, 0x00000006, 0x00000170, 0x0000016d, 0x0000016f, 0x00050051, 0x00000006, 0x00000172,
0x00000169, 0x00000001, 0x0004007f, 0x00000006, 0x00000173, 0x00000172, 0x00050085, 0x00000006,
0x00000175, 0x00000173, 0x0000009c, 0x00060041, 0x0000002c, 0x00000176, 0x00000029, 0x0000002b,
0x00000030, 0x0004003d, 0x00000006, 0x00000177, 0x00000176, 0x00050088, 0x00000006, 0x00000178,
0x00000175, 0x00000177, 0x0004007f, 0x00000006, 0x0000017a, 0x0000009c, 0x00060050, 0x0000000a,
0x0000017b, 0x00000170, 0x00000178, 0x0000017a, 0x00060041, 0x0000002c, 0x000000b6, 0x00000029,
0x000000b4, 0x000000b5, 0x0004003d, 0x00000006, 0x000000b7, 0x000000b6, 0x00050085, 0x00000006,
0x000000ba, 0x0000009c, 0x000001af, 0x0008000c, 0x00000006, 0x000000bd, 0x00000001, 0x0000002b,
0x000000ba, 0x000000bb, 0x000000bc, 0x00050085, 0x00000006, 0x000000be, 0x000000b7, 0x000000bd,
0x00050085, 0x00000006, 0x000000c3, 0x0000009c, 0x000000c2, 0x00050081, 0x00000006, 0x000000c4,
0x000000c0, 0x000000c3, 0x00050085, 0x00000006, 0x000000c8, 0x000000be, 0x000001b0, 0x00050094,
0x00000006, 0x0000017f, 0x000000a5, 0x0000005d, 0x0006000c, 0x00000006, 0x00000180, 0x00000001,
0x0000000a, 0x0000017f, 0x00050085, 0x00000006, 0x00000181, 0x00000059, 0x00000180, 0x0006000c,
0x00000006, 0x00000182, 0x00000001, 0x0000000a, 0x00000181, 0x00050081, 0x00000006, 0x000000d0,
0x000000cb, 0x00000182, 0x00050085, 0x00000006, 0x000000d1, 0x000000c8, 0x000000d0, 0x000200f9,
0x000000d4, 0x000200f8, 0x000000d4, 0x000700f5, 0x00000006, 0x000001a7, 0x000000d1, 0x000000a1,
0x00000129, 0x000000d7, 0x000700f5, 0x0000002a, 0x000001a6, 0x0000002b, 0x000000a1, 0x00000126,
0x000000d7, 0x000500b1, 0x0000006e, 0x000000db, 0x000001a6, 0x000000da, 0x000400f6, 0x000000d6,
0x000000d7, 0x00000000, 0x000400fa, 0x000000db, 0x000000d5, 0x000000d6, 0x000200f8, 0x000000d5,
0x00050041, 0x000000a8, 0x000000de, 0x00000029, 0x000000b4, 0x0004003d, 0x00000026, 0x000000df,
0x000000de, 0x0008004f, 0x0000000a, 0x000000e0, 0x000000df, 0x000000df, 0x00000000, 0x00000001,
0x00000002, 0x0005008e, 0x0000000a, 0x000000e2, 0x000000e0, 0x000001a7, 0x00050081, 0x0000000a,
0x000000e3, 0x0000017b, 0x000000e2, 0x00050051, 0x00000006, 0x000000e5, 0x000000e3, 0x00000002,
0x000500be, 0x0000006e, 0x000000e7, 0x000000e5, 0x000000e6, 0x000300f7, 0x000000e9, 0x00000000,
0x000400fa, 0x000000e7, 0x000000e8, 0x000000e9, 0x000200f8, 0x000000e8, 0x000200f9, 0x000000d6,
0x000200f8, 0x000000e9, 0x0004007f, 0x00000006, 0x00000188, 0x000000e5, 0x00050051, 0x00000006,
0x0000018a, 0x000000e3, 0x00000000, 0x00050085, 0x00000006, 0x0000018b, 0x00000043, 0x0000018a,
0x00050085, 0x00000006, 0x0000018e, 0x0000018b, 0x0000016f, 0x00050088, 0x00000006, 0x00000190,
0x0000018e, 0x00000188, 0x00050081, 0x00000006, 0x00000191, 0x00000043, 0x00000190, 0x00050051,
0x00000006, 0x00000193, 0x000000e3, 0x00000001, 0x00050085, 0x00000006, 0x00000194, 0x00000043,
0x00000193, 0x00050085, 0x00000006, 0x00000197, 0x00000194, 0x00000177, 0x00050088, 0x00000006,
0x00000199, 0x00000197, 0x00000188, 0x00050083, 0x00000006, 0x0000019a, 0x00000043, 0x00000199,
0x00050050, 0x00000007, 0x0000019b, 0x00000191, 0x0000019a, 0x000500b8, 0x000000f1, 0x000000f2,
0x0000019b, 0x000000f0, 0x0004009a, 0x0000006e, 0x000000f3, 0x000000f2, 0x000400a8, 0x0000006e,
0x000000f4, 0x000000f3, 0x000300f7, 0x000000f6, 0x00000000, 0x000400fa, 0x000000f4, 0x000000f5,
0x000000f6, 0x000200f8, 0x000000f5, 0x000500ba, 0x000000f1, 0x000000f9, 0x0000019b, 0x000000f8,
0x0004009a, 0x0000006e, 0x000000fa, 0x000000f9, 0x000200f9, 0x000000f6, 0x000200f8, 0x000000f6,
0x000700f5, 0x0000006e, 0x000000fb, 0x000000f3, 0x000000e9, 0x000000fa, 0x000000f5, 0x000300f7,
0x000000fd, 0x00000000, 0x000400fa, 0x000000fb, 0x000000fc, 0x000000fd, 0x000200f8, 0x000000fc,
0x000200f9, 0x000000d6, 0x000200f8, 0x000000fd, 0x0004003d, 0x00000086, 0x00000100, 0x00000088,
0x0007004f, 0x00000007, 0x00000104, 0x000000aa, 0x000000aa, 0x00000000, 0x00000001, 0x00050085,
0x00000007, 0x00000105, 0x0000019b, 0x00000104, 0x0004006e, 0x00000064, 0x00000106, 0x00000105,
0x00040064, 0x00000085, 0x00000107, 0x00000100, 0x0007005f, 0x00000026, 0x00000108, 0x00000107,
0x00000106, 0x00000002, 0x0000002b, 0x00050051, 0x00000006, 0x00000109, 0x00000108, 0x00000000,
0x00050085, 0x00000006, 0x0000010c, 0x00000109, 0x0000009b, 0x00050083, 0x00000006, 0x00000112,
0x00000188, 0x0000010c, 0x00050085, 0x00000006, 0x00000117, 0x0000009c, 0x00000116, 0x00050081,
0x00000006, 0x00000118, 0x00000114, 0x00000117, 0x000500ba, 0x0000006e, 0x00000119, 0x00000112,
0x00000118, 0x000500b8, 0x0000006e, 0x0000011c, 0x00000112, 0x000000c4, 0x000500a7, 0x0000006e,
0x0000011d, 0x00000119, 0x0000011c, 0x000300f7, 0x0000011f, 0x00000000, 0x000400fa, 0x0000011d,
0x0000011e, 0x0000011f, 0x000200f8, 0x0000011e, 0x0004006f, 0x00000006, 0x00000121, 0x000001a6,
0x00050085, 0x00000006, 0x00000122, 0x00000121, 0x000001b0, 0x00050083, 0x00000006, 0x00000123,
0x0000001d, 0x00000122, 0x000200f9, 0x000000d6, 0x000200f8, 0x0000011f, 0x000200f9, 0x000000d7,
0x000200f8, 0x000000d7, 0x00050080, 0x0000002a, 0x00000126, 0x000001a6, 0x000000b4, 0x00050081,
0x00000006, 0x00000129, 0x000001a7, 0x000000c8, 0x000200f9, 0x000000d4, 0x000200f8, 0x000000d6,
0x000b00f5, 0x00000006, 0x000001aa, 0x0000008f, 0x000000d4, 0x0000008f, 0x000000e8, 0x0000008f,
0x000000fc, 0x00000123, 0x0000011e, 0x0008000c, 0x00000006, 0x0000012d, 0x00000001, 0x00000031,
0x0000012b, 0x0000009e, 0x0000009c, 0x00050083, 0x00000006, 0x0000012e, 0x0000001d, 0x0000012d,
0x00060041, 0x0000002c, 0x00000130, 0x00000029, 0x0000002b, 0x000000b5, 0x0004003d, 0x00000006,
0x00000131, 0x00000130, 0x00050085, 0x00000006, 0x00000133, 0x00000131, 0x000001aa, 0x00050085,
0x00000006, 0x00000135, 0x00000133, 0x0000012e, 0x00050083, 0x00000006, 0x00000136, 0x0000001d,
0x00000135, 0x00060041, 0x0000002c, 0x00000138, 0x00000029, 0x00000137, 0x00000021, 0x0004003d,
0x00000006, 0x00000139, 0x00000138, 0x000500ba, 0x0000006e, 0x0000013a, 0x00000139, 0x00000043,
0x000300f7, 0x0000013c, 0x00000000, 0x000400fa, 0x0000013a, 0x0000013b, 0x0000013c, 0x000200f8,
0x0000013b, 0x0004003d, 0x0000013d, 0x00000140, 0x0000013f, 0x00070050, 0x00000026, 0x00000143,
0x00000136, 0x00000136, 0x00000136, 0x0000001d, 0x00040063, 0x00000140, 0x0000006d, 0x00000143,
0x000200f9, 0x0000015d, 0x000200f8, 0x0000013c, 0x000500be, 0x0000006e, 0x00000146, 0x00000136,
0x00000092, 0x000300f7, 0x00000148, 0x00000000, 0x000400fa, 0x00000146, 0x00000147, 0x00000148,
0x000200f8, 0x00000147, 0x000200f9, 0x0000015d, 0x000200f8, 0x00000148, 0x0004003d, 0x0000013d,
0x0000014c, 0x0000013f, 0x00050062, 0x00000026, 0x0000014e, 0x0000014c, 0x0000006d, 0x0004003d,
0x0000013d, 0x0000014f, 0x0000013f, 0x0008004f, 0x0000000a, 0x00000152, 0x0000014e, 0x0000014e,
0x00000000, 0x00000001, 0x00000002, 0x0005008e, 0x0000000a, 0x00000154, 0x00000152, 0x00000136,
0x00050051, 0x00000006, 0x00000156, 0x0000014e, 0x00000003, 0x00050051, 0x00000006, 0x00000157,
0x00000154, 0x00000000, 0x00050051, 0x00000006, 0x00000158, 0x00000154, 0x00000001, 0x00050051,
0x00000006, 0x00000159, 0x00000154, 0x00000002, 0x00070050, 0x00000026, 0x0000015a, 0x00000157,
0x00000158, 0x00000159, 0x00000156, 0x00040063, 0x0000014f, 0x0000006d, 0x0000015a, 0x000200f9,
0x0000015d, 0x000200f8, 0x0000015d, 0x000100fd, 0x00010038,
};
+1
View File
@@ -719,6 +719,7 @@ void gfx_window_release_vk(void) {
g_flip_on = 0; // its blit is done (device idle); the swapchain goes away unpresented
menu_vk_release();
vk_fsr_release();
vk_contact_release();
free_capture();
free_thumbs();
for (int i = 0; i < FRAMES; i++) {
+18
View File
@@ -192,7 +192,25 @@ void res_heapdef_served(void) { R.heapdef_ok = 1; }
// ---- start: the patch page and the sets
static uint64_t pack(unsigned w, unsigned h) { return (uint64_t)h << 32 | w; }
// TOTK_SHADOW_RANGE (percent, 50 to 400; 100 = the game's): the sun's shadow cascades end at 20, 40, 80 and 160 m.
// Other values multiply those distances through the cascade patch set (recomp/patches-v143.txt), so the sharp first
// cascade reaches further and the visible steps between cascades move away from the player, at the cost of shadow
// texels per metre (TOTK_SHADOW_SCALE pays that back).
static void res_shadow_range(void) {
int pct = cfg_int("TOTK_SHADOW_RANGE"), set = rt_patch_set("cascade");
if (pct == 100) return;
if (set < 0) {
rt_log("resolution: the generated C has no cascade patch set; shadow_range stays 100");
return;
}
float k = (float)(pct < 50 ? 50 : pct > 400 ? 400 : pct) / 100.0f, d[4] = {20 * k, 40 * k, 80 * k, 160 * k};
memcpy((uint8_t *)rt_patch_page() + 64, d, sizeof d);
rt_patch_switch(set, 1);
rt_log("resolution: shadow cascades end at %g, %g, %g and %g m", d[0], d[1], d[2], d[3]);
}
void res_init(void) {
res_shadow_range();
if (!bigger()) {
rt_log("resolution: the game's own (output 1920x1080, scene 1600x900)");
return;
+67
View File
@@ -0,0 +1,67 @@
#version 450
// Screen-space contact shadows (runtime/vk_contact.c). Each pixel of the game's lit scene colour marches a short way
// toward the sun through the linear depth target; when the ray passes behind something close in front of it, the pixel
// is in a contact shadow the shadow maps are too coarse to show, and its colour is darkened.
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler2D lin_depth; // the game's linear depth: view distance / far
layout(binding = 1, r11f_g11f_b10f) uniform image2D scene; // the lit scene colour, darkened in place
layout(push_constant) uniform Params {
vec4 proj; // projection x scale, y scale, far plane, strength (0..1)
vec4 light; // view-space direction toward the sun (xyz), march length in metres at 10 m (w)
vec4 size; // width, height, 1/width, 1/height
vec4 opt; // x: debug (1: write the shadow factor as grey)
} pc;
vec3 view_pos(vec2 uv, float z) { // z: distance in front of the camera
vec2 ndc = uv * 2.0 - 1.0;
return vec3(ndc.x * z / pc.proj.x, -ndc.y * z / pc.proj.y, -z);
}
vec2 to_uv(vec3 p) {
float z = -p.z;
return vec2(0.5 + 0.5 * p.x * pc.proj.x / z, 0.5 - 0.5 * p.y * pc.proj.y / z);
}
float ign(vec2 px) { // interleaved gradient noise: a per-pixel start offset that hides the step pattern
return fract(52.9829189 * fract(dot(px, vec2(0.06711056, 0.00583715))));
}
void main() {
ivec2 px = ivec2(gl_GlobalInvocationID.xy);
if (px.x >= int(pc.size.x) || px.y >= int(pc.size.y)) return;
float d = texelFetch(lin_depth, px, 0).x;
if (d <= 0.0 || d >= 0.999) return; // nothing drawn, or the sky
float z = d * pc.proj.z;
if (z > 80.0) return;
vec2 uv = (vec2(px) + 0.5) * pc.size.zw;
vec3 p = view_pos(uv, z);
// the march grows with distance, as one pixel covers more ground; thickness keeps thin blades from hiding the sky
float len = pc.light.w * clamp(z / 10.0, 0.4, 3.0);
float thick = 0.15 + z * 0.01;
const int STEPS = 16;
float step_len = len / float(STEPS), t = step_len * (0.25 + ign(vec2(px)));
float occ = 0.0;
for (int i = 0; i < STEPS; i++, t += step_len) {
vec3 q = p + pc.light.xyz * t;
if (q.z >= -0.05) break;
vec2 quv = to_uv(q);
if (any(lessThan(quv, vec2(0.0))) || any(greaterThan(quv, vec2(1.0)))) break;
float sz = texelFetch(lin_depth, ivec2(quv * pc.size.xy), 0).x * pc.proj.z;
float delta = -q.z - sz; // how far the scene at that pixel is in front of the ray point
if (delta > 0.02 + z * 0.002 && delta < thick) {
occ = 1.0 - float(i) / float(STEPS); // a hit close to the pixel shadows more than one at the end
break;
}
}
float fade = 1.0 - smoothstep(40.0, 80.0, z);
float k = 1.0 - pc.proj.w * occ * fade;
if (pc.opt.x > 0.5) {
imageStore(scene, px, vec4(k, k, k, 1.0));
return;
}
if (k >= 0.999) return;
vec4 c = imageLoad(scene, px);
imageStore(scene, px, vec4(c.rgb * k, c.a));
}
+24
View File
@@ -450,6 +450,25 @@ static void row_render_scale(void) {
opt_choice_list("TOTK_RENDER_SCALE", UI_MENU_RENDER_SCALE, vals, names, N, UI_MENU_RENDER_SCALE_DESC);
}
static void row_aniso(void) {
static const char *const vals[] = {"game", "2", "4", "8", "16"};
static const char *names[] = {NULL, "2x", "4x", "8x", "16x"};
names[0] = menu_text(UI_MENU_ANISO_GAME);
opt_choice_list("TOTK_ANISO", UI_MENU_ANISO, vals, names, 5, UI_MENU_ANISO_DESC);
}
static void row_shadow_scale(void) {
static const char *const vals[] = {"1", "2", "3", "4"};
static const char *const names[] = {"960 (1x)", "1920 (2x)", "2880 (3x)", "3840 (4x)"};
opt_choice_list("TOTK_SHADOW_SCALE", UI_MENU_SHADOW_SCALE, vals, names, 4, UI_MENU_SHADOW_SCALE_DESC);
}
static void row_shadow_range(void) {
static const char *const vals[] = {"100", "150", "200", "300"};
static const char *const names[] = {"160 m (1x)", "240 m (1.5x)", "320 m (2x)", "480 m (3x)"};
opt_choice_list("TOTK_SHADOW_RANGE", UI_MENU_SHADOW_RANGE, vals, names, 4, UI_MENU_SHADOW_RANGE_DESC);
}
static void row_gpu(void) {
const char *vals[MAX_GPUS + 1], *names[MAX_GPUS + 1];
char idx[MAX_GPUS][4];
@@ -544,6 +563,11 @@ static void tab_screen(float scale) {
opt_slider("TOTK_SHARPNESS", UI_MENU_SHARPNESS, 0, 100, UI_MENU_SHARPNESS_DESC, 5, "%.0f %%");
static const UiTextId aspect[] = {UI_MENU_ASPECT_FIT, UI_MENU_ASPECT_STRETCH};
opt_choice("TOTK_ASPECT", UI_MENU_ASPECT, aspect, 2, UI_MENU_ASPECT_DESC);
row_aniso();
row_shadow_scale();
row_shadow_range();
opt_slider("TOTK_PCSS", UI_MENU_PCSS, 0, 200, UI_MENU_PCSS_DESC, 10, "%.0f %%");
opt_slider("TOTK_CONTACT_SHADOWS", UI_MENU_CONTACT_SHADOWS, 0, 100, UI_MENU_CONTACT_SHADOWS_DESC, 10, "%.0f %%");
opt_end();
}
+34
View File
@@ -69,6 +69,23 @@ static const char *const text_en[UI_TEXT_COUNT] = {
[UI_MENU_ASPECT_FIT] = "Black bars", [UI_MENU_ASPECT_STRETCH] = "Stretch",
[UI_MENU_SHARPNESS] = "FSR sharpness",
[UI_MENU_SHARPNESS_DESC] = "Sharpening after FSR scaling; 0 turns it off.",
[UI_MENU_ANISO] = "Anisotropic filtering",
[UI_MENU_ANISO_DESC] = "Keeps ground and walls seen at a steep angle sharp. The game's own level is the Switch's; "
"16x costs little on a desktop GPU.",
[UI_MENU_ANISO_GAME] = "The game's",
[UI_MENU_SHADOW_SCALE] = "Shadow resolution",
[UI_MENU_SHADOW_SCALE_DESC] = "Draws the sun's shadow maps this many times larger on each side than the game's 960x960: "
"sharper shadow edges and leaf shadows. Takes effect at the next start.",
[UI_MENU_PCSS] = "Soft shadows",
[UI_MENU_PCSS_DESC] = "Sun shadows sharp where an object meets the ground and softer the further the shadow falls "
"(PCSS). 0 keeps the game's filter. Takes effect at the next start.",
[UI_MENU_CONTACT_SHADOWS] = "Contact shadows",
[UI_MENU_CONTACT_SHADOWS_DESC] = "Small sun shadows the shadow maps miss: at feet, under stones, in grass and creases. "
"Traced on the screen after lighting. Takes effect at the next start.",
[UI_MENU_SHADOW_RANGE] = "Shadow cascade range",
[UI_MENU_SHADOW_RANGE_DESC] = "Moves the steps where shadows turn softer (the game's 20, 40, 80 and 160 m) further "
"away. Spreads the shadow map over more ground: raise Shadow resolution with it. "
"Takes effect at the next start.",
[UI_MENU_FPS_LIMIT] = "Frame rate limit",
[UI_MENU_FPS_LIMIT_DESC] = "The most frames per second the game draws. Above 60 is experimental and needs Game speed "
@@ -314,6 +331,23 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
[UI_MENU_ASPECT_FIT] = "Чёрные полосы", [UI_MENU_ASPECT_STRETCH] = "Растянуть",
[UI_MENU_SHARPNESS] = "Резкость FSR",
[UI_MENU_SHARPNESS_DESC] = "Повышение резкости после масштабирования FSR; 0 выключает его.",
[UI_MENU_ANISO] = "Анизотропная фильтрация",
[UI_MENU_ANISO_DESC] = "Сохраняет чёткость земли и стен под острым углом. Уровень самой игры — как на Switch; "
"16x почти ничего не стоит настольной видеокарте.",
[UI_MENU_ANISO_GAME] = "Как в игре",
[UI_MENU_SHADOW_SCALE] = "Разрешение теней",
[UI_MENU_SHADOW_SCALE_DESC] = "Карты теней от солнца рисуются во столько раз больше по каждой стороне, чем 960x960 "
"самой игры: края теней и тени листвы чётче. Действует после перезапуска.",
[UI_MENU_PCSS] = "Мягкие тени",
[UI_MENU_PCSS_DESC] = "Тени от солнца резкие там, где предмет касается земли, и мягче, чем дальше падает тень "
"(PCSS). 0 оставляет фильтр игры. Действует после перезапуска.",
[UI_MENU_CONTACT_SHADOWS] = "Контактные тени",
[UI_MENU_CONTACT_SHADOWS_DESC] = "Мелкие тени от солнца, которых нет на картах теней: у ног, под камнями, в траве и "
"складках. Считаются по экрану после освещения. Действует после перезапуска.",
[UI_MENU_SHADOW_RANGE] = "Дальность каскадов теней",
[UI_MENU_SHADOW_RANGE_DESC] = "Отодвигает границы, за которыми тени становятся мягче (в игре 20, 40, 80 и 160 м). "
"Карта теней растягивается на большую площадь, поэтому вместе с этим поднимайте "
"разрешение теней. Действует после перезапуска.",
[UI_MENU_FPS_LIMIT] = "Ограничение частоты кадров",
[UI_MENU_FPS_LIMIT_DESC] = "Наибольшее число кадров в секунду. Выше 60 — экспериментальный режим, нужна «Скорость "
+5
View File
@@ -37,6 +37,11 @@ typedef enum {
UI_MENU_UPSCALER, UI_MENU_UPSCALER_DESC, UI_MENU_UPSCALER_FSR, UI_MENU_UPSCALER_BILINEAR,
UI_MENU_ASPECT, UI_MENU_ASPECT_DESC, UI_MENU_ASPECT_FIT, UI_MENU_ASPECT_STRETCH,
UI_MENU_SHARPNESS, UI_MENU_SHARPNESS_DESC,
UI_MENU_ANISO, UI_MENU_ANISO_DESC, UI_MENU_ANISO_GAME,
UI_MENU_SHADOW_SCALE, UI_MENU_SHADOW_SCALE_DESC,
UI_MENU_SHADOW_RANGE, UI_MENU_SHADOW_RANGE_DESC,
UI_MENU_CONTACT_SHADOWS, UI_MENU_CONTACT_SHADOWS_DESC,
UI_MENU_PCSS, UI_MENU_PCSS_DESC,
// Game
UI_MENU_FPS_LIMIT, UI_MENU_FPS_LIMIT_DESC, UI_MENU_FPS_30, UI_MENU_FPS_40, UI_MENU_FPS_45, UI_MENU_FPS_60, UI_MENU_FPS_90,
+6
View File
@@ -102,6 +102,9 @@ void vk_present_blit(VkImage src, VkImageLayout layout, VkFormat fmt, int w, int
int vk_fsr_record(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat fmt, int sw, int sh, const int crop[4],
VkImage dst, const int out[4]);
void vk_fsr_release(void); // before the device goes away
// vk_contact.c: screen-space contact shadows on the game's composed scene colour (TOTK_CONTACT_SHADOWS)
int vk_contact_strength(void); // percent, 0 = off
void vk_contact_release(void); // before the device goes away
void vk_present_flip(void);
// Every submission to ctx->gfx_queue (and xfer_queue when shared) takes this lock; vk_present_image and the swapchain
// rebuild do. Recursive.
@@ -297,6 +300,9 @@ typedef struct {
uint8_t fb_usage; // created with ATTACHMENT_FEEDBACK_LOOP usage (VK_FEEDBACK_LAYOUT, attachable images)
uint8_t fb_learned; // a draw sampled it while it was a written attachment of the open pass: its passes
// attach it in ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT (vk_exec.c vk_exec_pass_begin)
uint8_t scale; // 1, or TOTK_SHADOW_SCALE for a shadow map made larger than the game's (vk_texture.c
// shadow_scale_of): width and height above are the larger ones, the game's regions
// are multiplied by it
} VkTexImage;
const VkTexImage *vk_texture_get(const tex_t *t); // created and made current; NULL for BUFFER or unmapped formats
// A texture slot; a slot whose texture was finalized follows the live texture at the same storage (hle_nvn.c).
+188
View File
@@ -0,0 +1,188 @@
// Screen-space contact shadows (TOTK_CONTACT_SHADOWS, percent strength, 0 = off). TotK fills a full-resolution
// G-buffer whose targets include a B10G11R11 scene colour, an R32F linear depth (view distance / far) and a D32F depth
// (is_gbuffer), lights it in half resolution, and then composites the lighting into that scene colour in one pass
// whose only target is the scene colour, with a D16 depth that sorts pixels into material classes (is_composite).
// When the pass after the composite begins, or a dispatch or copy comes first, runtime/shaders/contact_shadow.comp
// marches each pixel a short way toward the sun through the linear depth and darkens the scene colour where the ray
// passes behind nearby geometry: the small shadows at feet, under stones and in grass that the shadow maps are too
// coarse for. Transparent surfaces, the sky and post-processing come after, untouched.
//
// Inputs from the game, read from the fragment stage's uniform block c4 bound in the composite pass (guest memory,
// so it may be one frame newer than the replay): the projection scales c4[7].x and c4[8].y, the far plane c4[14].y,
// and the first sun cascade's depth row c4[44].xyz, whose negated direction points from a view-space position toward
// the sun (the G-buffer shaders compare dot(c4[44].xyz, pos) + c4[44].w with the cascade map). The scene colour images
// get STORAGE usage when the setting is on (vk_texture.c create_tex).
#include "vk.h"
#include <math.h>
#include <string.h>
#include "cfg.h"
#include "contact_shadow_spv.h"
#include "host.h"
#include "vk_exec.h"
static struct {
int tried, ok, strength, debug, logged;
PFN_vkCmdPushDescriptorSetKHR push;
VkDescriptorSetLayout dsl;
VkPipelineLayout layout;
VkPipeline pipe;
VkSampler sampler;
const VkTexImage *scene, *depth; // the last G-buffer's scene colour and linear depth
uint32_t w, h;
int pending; // the composite into scene ran; the march waits for the next pass
uint64_t c4;
} C;
int vk_contact_strength(void) {
if (!C.tried) {
int s = cfg_int("TOTK_CONTACT_SHADOWS");
C.strength = s < 0 ? 0 : s > 100 ? 100 : s;
}
return C.strength;
}
static int init(VkCtx *c) {
C.tried = 1;
vk_contact_strength();
if (!C.strength) return 0;
const char *e = cfg_get("TOTK_CONTACT_DEBUG"); // 1: the frame shows the contact shadow factor in grey
C.debug = e && *e && strcmp(e, "0");
C.push = (PFN_vkCmdPushDescriptorSetKHR)vkGetDeviceProcAddr(c->device, "vkCmdPushDescriptorSetKHR");
VkDescriptorSetLayoutBinding b[2] = {
{.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT},
{.binding = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT}};
VkDescriptorSetLayoutCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR,
.bindingCount = 2, .pBindings = b};
VkPushConstantRange pr = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 64};
VkPipelineLayoutCreateInfo li = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1,
.pSetLayouts = &C.dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pr};
VkSamplerCreateInfo si = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST,
.minFilter = VK_FILTER_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .maxLod = 0};
VkShaderModuleCreateInfo mi = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = sizeof contact_shadow_spv, .pCode = contact_shadow_spv};
VkShaderModule m = VK_NULL_HANDLE;
if (!C.push || vkCreateDescriptorSetLayout(c->device, &dci, NULL, &C.dsl) != VK_SUCCESS ||
vkCreatePipelineLayout(c->device, &li, NULL, &C.layout) != VK_SUCCESS ||
vkCreateSampler(c->device, &si, NULL, &C.sampler) != VK_SUCCESS ||
vkCreateShaderModule(c->device, &mi, NULL, &m) != VK_SUCCESS) {
rt_log("vk: contact shadows off: their pipeline could not be created");
return 0;
}
VkComputePipelineCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = m, .pName = "main"},
.layout = C.layout};
VkResult r = vkCreateComputePipelines(c->device, VK_NULL_HANDLE, 1, &ci, NULL, &C.pipe);
vkDestroyShaderModule(c->device, m, NULL);
if (r != VK_SUCCESS) {
rt_log("vk: contact shadows off: their pipeline could not be created");
return 0;
}
rt_log("vk: contact shadows on, strength %d%%%s", C.strength, C.debug ? " (debug view)" : "");
return C.ok = 1;
}
// A G-buffer pass: a B10G11R11 scene colour, an R32F linear depth and a D32F depth, all one size, at least 640 wide.
static int is_gbuffer(const VkExec *ex, const VkTexImage **scene, const VkTexImage **depth) {
*scene = *depth = NULL;
if (ex->pass_noatt || ex->pass_w < 640 || ex->depth.format != VK_FORMAT_D32_SFLOAT || ex->depth.level) return 0;
for (uint32_t i = 0; i < ex->pass_ncolor && i < 8; i++) {
const VkExecTarget *t = &ex->color[i];
if (!t->image || t->level || t->width != ex->pass_w || t->height != ex->pass_h) continue;
if (t->format == VK_FORMAT_B10G11R11_UFLOAT_PACK32 && !*scene) *scene = t->img;
if (t->format == VK_FORMAT_R32_SFLOAT && !*depth) *depth = t->img;
}
return *scene && *depth && ((*scene)->usage & VK_IMAGE_USAGE_STORAGE_BIT);
}
static void mem_barrier(VkCommandBuffer cb) {
VkMemoryBarrier2 mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
VkDependencyInfo d = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &mb};
vkCmdPipelineBarrier2(cb, &d);
}
void vk_contact_flush(VkExec *ex) {
if (!C.pending) return;
C.pending = 0;
if (ex->in_pass) return;
const float *u = (const float *)(uintptr_t)C.c4;
float lx = -u[44 * 4 + 0], ly = -u[44 * 4 + 1], lz = -u[44 * 4 + 2], ll = sqrtf(lx * lx + ly * ly + lz * lz);
float px = u[7 * 4 + 0], py = u[8 * 4 + 1], zfar = u[14 * 4 + 1];
if (!(ll > 1e-6f) || !(px > 0) || !(py > 0) || !(zfar > 1)) return;
VkImageView in = vk_exec_target_view(C.depth, VK_FORMAT_R32_SFLOAT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_COLOR_BIT,
0, 0, 1, 0);
VkImageView out = vk_exec_target_view(C.scene, VK_FORMAT_B10G11R11_UFLOAT_PACK32, VK_IMAGE_VIEW_TYPE_2D,
VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1, 0);
if (!in || !out) return;
VkCommandBuffer cb = ex->cb;
float pcv[16] = {px, py, zfar, (float)C.strength / 100.0f,
lx / ll, ly / ll, lz / ll, 0.6f,
(float)C.w, (float)C.h, 1.0f / (float)C.w, 1.0f / (float)C.h,
(float)C.debug, 0, 0, 0};
if (!C.logged) {
C.logged = 1;
rt_log("vk: contact shadows on a %ux%u scene, sun toward %.3f %.3f %.3f (view space), far %g", C.w, C.h,
pcv[4], pcv[5], pcv[6], zfar);
}
mem_barrier(cb); // the compose passes' writes, before the march reads and writes them
VkDescriptorImageInfo ii = {C.sampler, in, VK_IMAGE_LAYOUT_GENERAL}, oi = {VK_NULL_HANDLE, out, VK_IMAGE_LAYOUT_GENERAL};
VkWriteDescriptorSet w[2] = {
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &ii},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &oi}};
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, C.pipe);
rec_push(C.push, cb, VK_PIPELINE_BIND_POINT_COMPUTE, C.layout, 0, 2, w);
vkCmdPushConstants(cb, C.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof pcv, pcv);
vkCmdDispatch(cb, (C.w + 7) / 8, (C.h + 7) / 8, 1);
mem_barrier(cb); // and what follows sees the darkened colour
ex->recorded += 6;
vk_exec_draw_new_cb(); // the game's bound compute pipeline and descriptors were replaced: bind them again
}
// The lighting composite: the remembered scene colour as the only target, with a D16 depth of its size.
static int is_composite(const VkExec *ex) {
return C.scene && ex->pass_ncolor >= 1 && ex->color[0].img == C.scene && !ex->color[0].level &&
ex->depth.format == VK_FORMAT_D16_UNORM && ex->pass_w == C.w && ex->pass_h == C.h;
}
void vk_contact_pass_begin(VkExec *ex) {
if (!C.tried && !init(vk_ctx())) return;
if (!C.ok) return;
const VkTexImage *scene, *depth;
// The camera block: c4 of the fragment stage, 0x940 bytes. The composite binds it only after its pass begins, so
// the newest one seen at any pass start serves.
const p_bind_buf *b = &ex->ubo[1][1];
if (b->addr && b->size == 0x940) C.c4 = b->addr;
if (is_gbuffer(ex, &scene, &depth)) {
C.pending = 0;
C.scene = scene, C.depth = depth, C.w = ex->pass_w, C.h = ex->pass_h;
return;
}
if (is_composite(ex)) {
C.pending = C.c4 != 0;
return;
}
vk_contact_flush(ex);
}
void vk_contact_release(void) {
VkCtx *c = vk_ctx();
if (!c || !C.tried) return;
if (C.pipe) vkDestroyPipeline(c->device, C.pipe, NULL);
if (C.sampler) vkDestroySampler(c->device, C.sampler, NULL);
if (C.layout) vkDestroyPipelineLayout(c->device, C.layout, NULL);
if (C.dsl) vkDestroyDescriptorSetLayout(c->device, C.dsl, NULL);
memset(&C, 0, sizeof C);
}
+8 -5
View File
@@ -18,8 +18,8 @@
// as x/2+1/2, UNORM and sRGB bytes written as stored. Caps: MAX_PASSES passes copied, PNG_CAP bytes of PNGs.
// TOTK_DUMP_TEX=<id>|<w>x<h>[,...]: those texture slots, or 2D textures of that size, are copied where first sampled,
// whatever their format.
// TOTK_DUMP_UBO=1: each listed pipeline also prints the first 16 vec4 of its user uniform blocks (c3 and up), read from
// guest memory when the entry is listed. A block the submission rewrites with UpdateUniformBuffer shows its value from
// TOTK_DUMP_UBO=<n>: each listed pipeline also prints the first n vec4 of its user uniform blocks (c3 and up; 1 means 16,
// the cap is 4096), read from guest memory when the entry is listed. A block the submission rewrites with UpdateUniformBuffer shows its value from
// before the submission (vk_exec_draw.c upd_note).
// TOTK_DUMP_SKIP_RUNS=1: a render pass with the same targets as the render pass before it is logged but not copied
// (the terrain decals draw one pass each into the G-buffer and would use up both caps).
@@ -48,7 +48,7 @@ static uint64_t dump_frame, log_from; // log_from: TOTK_DUMP_LOG, first frame
static char dir[400];
static FILE *passes_f, *images_f;
static int seq, finished, copied; // copied: passes and dispatches of the dump frame whose images were copied
static int skip_runs, dump_ubo; // TOTK_DUMP_SKIP_RUNS, TOTK_DUMP_UBO
static int skip_runs, dump_ubo; // TOTK_DUMP_SKIP_RUNS, TOTK_DUMP_UBO (vec4 per block, 0 for none)
static uint32_t want_tex[16][2]; // TOTK_DUMP_TEX: slot id and 0, or width and height
static int nwant_tex;
static uint64_t last_targets; // targets_sig of the last render pass; 0 after a dispatch
@@ -576,7 +576,7 @@ static void describe_bindings(Str *b, const VkExec *ex, const VkPipeInfo *info)
if (u->addr && vk_mem_resolve(u->addr, 1, &ms)) {
uint64_t n = u->size && u->size < ms.avail ? u->size : ms.avail;
const float *f = (const float *)(uintptr_t)u->addr;
for (uint64_t k = 0; k < 16 && (k + 1) * 16 <= n; k++)
for (uint64_t k = 0; k < (uint64_t)dump_ubo && (k + 1) * 16 <= n; k++)
sp(b, "%s[%llu] %g %g %g %g", k % 4 ? " " : "\n ", (unsigned long long)k, f[4 * k], f[4 * k + 1],
f[4 * k + 2], f[4 * k + 3]);
} else {
@@ -870,7 +870,10 @@ void vk_dump_init(void) {
const char *sr = cfg_get("TOTK_DUMP_SKIP_RUNS");
skip_runs = sr && *sr && *sr != '0';
const char *du = cfg_get("TOTK_DUMP_UBO");
dump_ubo = du && *du && *du != '0';
dump_ubo = du && *du ? atoi(du) : 0;
if (dump_ubo == 1) dump_ubo = 16;
if (dump_ubo > 4096) dump_ubo = 4096;
if (dump_ubo < 0) dump_ubo = 0;
const char *wt = cfg_get("TOTK_DUMP_TEX");
for (char *q = (char *)wt; q && *q && nwant_tex < 16; q += *q == ',') {
uint32_t *w = want_tex[nwant_tex++];
+9 -3
View File
@@ -345,6 +345,7 @@ static void resolve_target(const tex_t *t, const view_t *v, int has_v, int depth
}
o->image = img->image;
o->img = img;
o->scale = img->scale ? img->scale : 1;
o->format = fmt;
o->view_type = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
o->aspect = depth_slot ? img->aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)
@@ -380,7 +381,7 @@ static void resolve_rt(VkExec *ex) {
ex->rt_resolved = 1;
const p_set_rt *r = &ex->rt;
int n = r->n < 0 ? 0 : r->n > 8 ? 8 : r->n;
uint32_t w = UINT32_MAX, h = UINT32_MAX, layers = UINT32_MAX, samples = 0;
uint32_t w = UINT32_MAX, h = UINT32_MAX, layers = UINT32_MAX, samples = 0, scale = 1;
for (int i = 0; i < 8; i++) {
VkExecTarget *t = &ex->color[i];
resolve_target(i < n ? r->color[i] : NULL, &r->cview[i], i < n && r->has_cview[i], 0, t);
@@ -391,6 +392,7 @@ static void resolve_rt(VkExec *ex) {
if (t->height < h) h = t->height;
if (t->layers < layers) layers = t->layers;
if (!samples) samples = t->samples;
if (t->scale > scale) scale = t->scale;
}
VkExecTarget *d = &ex->depth;
resolve_target(r->depth, &r->dview, r->has_dview, 1, d);
@@ -402,6 +404,7 @@ static void resolve_rt(VkExec *ex) {
if (d->height < h) h = d->height;
if (d->layers < layers) layers = d->layers;
if (!samples) samples = d->samples;
if (d->scale > scale) scale = d->scale;
}
ex->pass_noatt = w == UINT32_MAX;
if (ex->pass_noatt) {
@@ -413,6 +416,7 @@ static void resolve_rt(VkExec *ex) {
ex->vp[0][1], ex->vp[0][2], ex->vp[0][3]);
}
ex->pass_w = w, ex->pass_h = h, ex->pass_layers = layers;
ex->pass_scale = ex->pass_noatt ? 1 : scale;
ex->pass_ncolor = (uint32_t)n;
ex->pass_samples = samples ? samples : 1;
ex->pipe.samples = (uint8_t)ex->pass_samples;
@@ -571,6 +575,7 @@ void vk_exec_barrier(VkExec *ex) { barrier_to(ex, NULL, NULL, 0); }
void vk_exec_pass_begin(VkExec *ex) {
if (ex->in_pass) return;
resolve_rt(ex);
vk_contact_pass_begin(ex);
// Learned targets (VkTexImage.fb_learned) are attached in the feedback layout
VkImage want[9];
VkImageAspectFlags wasp[9];
@@ -667,8 +672,9 @@ VkBuffer vk_exec_inline_buffer(VkDeviceSize size) {
VkRect2D vk_exec_scissor(const VkExec *ex) {
int64_t w = ex->pass_w, h = ex->pass_h;
if (!ex->has_scissor) return (VkRect2D){{0, 0}, {(uint32_t)w, (uint32_t)h}};
int64_t x0 = ex->scissor.x, y0 = ex->scissor.y;
int64_t x1 = x0 + ex->scissor.w, y1 = y0 + ex->scissor.h;
int64_t k = ex->pass_scale > 1 ? ex->pass_scale : 1;
int64_t x0 = ex->scissor.x * k, y0 = ex->scissor.y * k;
int64_t x1 = x0 + (int64_t)ex->scissor.w * k, y1 = y0 + (int64_t)ex->scissor.h * k;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 > w) x1 = w;
+6
View File
@@ -35,6 +35,7 @@ typedef struct {
uint32_t width, height, samples; // extent of that level; with blocks, in blocks
int32_t nvn_format; // NVNformat of the view (VkPipeState.color_fmt / depth_fmt)
const VkTexImage *img; // the image (fb_usage, fb_learned)
uint8_t scale; // VkTexImage.scale
} VkExecTarget;
// The replay state. One instance lives for the run: NVN state is queue state and carries over from one submission to
@@ -52,6 +53,8 @@ typedef struct VkExec {
int rt_resolved; // color[] and depth hold this command buffer's images for rt
VkExecTarget color[8], depth; // resolved targets
uint32_t pass_w, pass_h, pass_layers; // render area and layer count of the open (or next) pass
uint32_t pass_scale; // its targets' VkTexImage.scale: game viewports and scissors
// are multiplied by it
int pass_noatt; // the pass has no attachment: its size follows the draws (vk_exec_noatt_dims)
VkFormat pass_color[8], pass_depth, pass_stencil; // attachment formats of the pass (VkPipelineRenderingCreateInfo);
// VK_FORMAT_UNDEFINED: none
@@ -175,6 +178,9 @@ VkImageView vk_exec_target_view(const VkTexImage *img, VkFormat format, VkImageV
// vk_exec_xfer.c: OP_COPY_B2B, OP_COPY_B2T, OP_COPY_T2B, OP_COPY_T2T, OP_CLEAR_TEX, OP_SIGNAL_EVENT, OP_REPORT_COUNTER,
// OP_RESET_COUNTER, OP_RENDER_ENABLE, OP_RENDER_COND, OP_CLEAR_BUF.
void vk_exec_op_xfer(VkExec *ex, uint32_t op, const void *payload);
// vk_contact.c: a pass about to begin (its targets resolved, no pass open), and a dispatch or copy about to be recorded
void vk_contact_pass_begin(VkExec *ex);
void vk_contact_flush(VkExec *ex);
// vk_exec_xfer.c, samples-passed counting: a draw about to be recorded in the open pass (begins an occlusion query
// when counting and none is active), and the open pass about to end (ends the active query).
void vk_exec_oq_draw(VkExec *ex);
+46 -4
View File
@@ -33,7 +33,7 @@
#define MAX_DESC 1024
#define CHUNK_BYTES (4u << 20)
#define ZERO_BYTES 65536u
#define SUPPORT_BYTES 1408u // Ryujinx SupportBuffer (std140): alpha_test, is_bgra[8], viewport_inverse, viewport_size, ...
#define SUPPORT_BYTES 1440u // Ryujinx SupportBuffer (std140): alpha_test, is_bgra[8], viewport_inverse, viewport_size, ...
#define NVN_STAGE_VS 0
#define NVN_STAGE_FS 1
#define NVN_STAGE_CS 5
@@ -1419,11 +1419,47 @@ static VkDescriptorSet alloc_set(VkExec *ex, VkDescriptorSetLayout layout) {
}
static void support_in(const VkExec *ex, float in[3]) { // what support_fill reads: viewport size, point size
in[0] = ex->in_pass && ex->has_vp ? ex->vp[0][2] : (float)ex->pass_w;
in[1] = ex->in_pass && ex->has_vp ? ex->vp[0][3] : (float)ex->pass_h;
float k = ex->pass_scale > 1 ? (float)ex->pass_scale : 1.0f;
in[0] = ex->in_pass && ex->has_vp ? ex->vp[0][2] * k : (float)ex->pass_w;
in[1] = ex->in_pass && ex->has_vp ? ex->vp[0][3] * k : (float)ex->pass_h;
in[2] = ex->point_size > 0 ? ex->point_size : 1.0f;
}
// TOTK_PCSS (percent, 0 = off): the pcss field of the support buffer, which translator patch 0010 reads in every
// explicit-LOD compare on a 2D array texture (the sun cascades). x: penumbra width in UV per unit of light-space depth
// between receiver and blocker (0 keeps the game's single compare), y and z: the narrowest and widest disc radius,
// w: the blocker search radius, all in UV of the cascade map. Radii are in UV, so they hold at any TOTK_SHADOW_SCALE.
// TOTK_PCSS_K overrides the depth-to-penumbra factor at 100 percent, for tuning.
static const float *pcss_params(void) {
static float p[4] = {-1, 0, 0, 0};
if (p[0] < 0) {
int pct = cfg_int("TOTK_PCSS");
pct = pct < 0 ? 0 : pct > 200 ? 200 : pct;
const char *k = cfg_get("TOTK_PCSS_K");
float k100 = k && *k ? (float)atof(k) : 0.5f;
p[0] = k100 * (float)pct / 100.0f;
p[1] = 0.35f / 960.0f, p[2] = 5.0f / 960.0f, p[3] = 4.0f / 960.0f;
if (pct) rt_log("vk: soft shadows (PCSS) at %d%% (depth factor %g)", pct, p[0]);
}
return p;
}
// The shadow_spread field of the support buffer: the factor translator patch 0011 applies to the lighting shaders' sun
// shadow filter offset (element 56 .xy of their lighting block), 1 / TOTK_SHADOW_SCALE. The game spaces its four
// compares for its own shadow map texels; with maps TOTK_SHADOW_SCALE times larger, the same offset in UV spans that
// many more texels and the near cascade shows four separate copies of each edge. Dividing by the scale keeps the game's
// spacing in texels. The offset is in UV of the shadow map, so the output resolution plays no part. The scale is read
// once, as vk_texture.c shadow_scale reads it: the maps keep the size they were made with.
static const float *shadow_spread_params(void) {
static float p[4] = {0, 0, 0, 0};
if (p[0] == 0) {
int scale = cfg_int("TOTK_SHADOW_SCALE");
scale = scale < 1 ? 1 : scale > 4 ? 4 : scale;
p[0] = 1.0f / (float)scale;
}
return p;
}
static void support_fill(uint8_t *b, float w, float h, float point_size) {
memset(b, 0, SUPPORT_BYTES);
float inv[4] = {w > 0 ? 2.0f / w : 0, h > 0 ? 2.0f / h : 0, 0, 0}, size[4] = {w, h, 0, 0};
@@ -1431,6 +1467,8 @@ static void support_fill(uint8_t *b, float w, float h, float point_size) {
memcpy(b + 160, size, 16);
const float one = 1.0f;
memcpy(b + 1392, &point_size, 4); // point_size (translator patch 0008): vertex shaders that write none output it
memcpy(b + 1408, pcss_params(), 16); // pcss (translator patch 0010): soft sun shadows in the cascade compares
memcpy(b + 1424, shadow_spread_params(), 16); // shadow_spread (translator patch 0011): sun shadow filter width
for (int i = 0; i < 73; i++) memcpy(b + 192 + 16 * i, &one, 4); // render scale 1: the host renders at guest size
}
@@ -1849,7 +1887,10 @@ static void record_fixed(VkExec *ex) {
VkCommandBuffer cb = ex->cb;
if (ex->dirty & VK_EXEC_DIRTY_VIEWPORT) {
float x = 0, y = 0, w = (float)ex->pass_w, h = (float)ex->pass_h;
if (ex->has_vp) x = ex->vp[0][0], y = ex->vp[0][1], w = ex->vp[0][2], h = ex->vp[0][3];
if (ex->has_vp) {
float k = ex->pass_scale > 1 ? (float)ex->pass_scale : 1.0f;
x = ex->vp[0][0] * k, y = ex->vp[0][1] * k, w = ex->vp[0][2] * k, h = ex->vp[0][3] * k;
}
if (w < 1) w = 1;
if (h < 1) h = 1;
VkViewport v = {x, y + h, w, -h, ex->depth_range[0], ex->depth_range[1]}; // the y flip: negative height
@@ -2244,6 +2285,7 @@ static void op_draw_idx_ind(VkExec *ex, const p_draw_idx_ind *d) {
static void op_dispatch(VkExec *ex, const p_dispatch *d) {
if (d->x <= 0 || d->y <= 0 || d->z <= 0) return;
if (!ex->prog) return warn_once(W_PROGRAM, "a dispatch without a bound program is skipped");
vk_contact_flush(ex);
cb_res(ex);
dummies_init();
int present;
+9 -3
View File
@@ -356,10 +356,11 @@ static int sub_resolve(const tex_t *t, const view_t *v, int has_v, const NVNcopy
s->level = has_v && v->set_levels ? v->base_level : 0;
s->layer0 = has_v && v->set_layers ? v->min_layer : 0;
uint32_t lw = max1(img->width >> s->level), lh = max1(img->height >> s->level);
s->x = r->xoffset, s->y = r->yoffset, s->z = r->zoffset;
int k = img->scale > 1 ? img->scale : 1; // a larger shadow map: the game's region in its texels
s->x = r->xoffset * k, s->y = r->yoffset * k, s->z = r->zoffset;
s->nz = r->depth > 0 ? (uint32_t)r->depth : 1;
s->w = r->width > 0 ? (uint32_t)r->width : defaults_to_level ? (s->x < (int32_t)lw ? lw - (uint32_t)s->x : 0) : 0;
s->h = r->height > 0 ? (uint32_t)r->height : defaults_to_level ? (s->y < (int32_t)lh ? lh - (uint32_t)s->y : 0) : 0;
s->w = r->width > 0 ? (uint32_t)r->width * k : defaults_to_level ? (s->x < (int32_t)lw ? lw - (uint32_t)s->x : 0) : 0;
s->h = r->height > 0 ? (uint32_t)r->height * k : defaults_to_level ? (s->y < (int32_t)lh ? lh - (uint32_t)s->y : 0) : 0;
int is3d = img->type == VK_IMAGE_TYPE_3D;
uint32_t layers = is3d ? 1 : (uint32_t)s->z + s->nz, first = is3d ? 0 : s->layer0;
if (s->level >= img->levels || s->x < 0 || s->y < 0 || s->z < 0 || !s->w || !s->h ||
@@ -415,6 +416,10 @@ static int buffer_side(const VkExec *ex, const Sub *s, uint64_t addr, uint64_t *
static void copy_buffer_image(VkExec *ex, const p_copy_b2t *p, int to_image) {
Sub s;
if (!sub_resolve(p->tex, &p->view, p->has_view, &p->r, 1, &s)) return;
if (s.img->scale > 1) { // the buffer holds the game's texel count, the image more
warn_once(W_REGION, "buffer copy of a shadow map enlarged by TOTK_SHADOW_SCALE: skipped");
return;
}
uint64_t size;
VkBufferImageCopy c;
VkMemSpan m;
@@ -591,6 +596,7 @@ static void clear_tex(VkExec *ex, const p_clear_tex *p) {
}
void vk_exec_op_xfer(VkExec *ex, uint32_t op, const void *payload) {
if (op != OP_RENDER_ENABLE && op != OP_SIGNAL_EVENT) vk_contact_flush(ex);
switch (op) {
case OP_COPY_B2B: copy_b2b(ex, payload); break;
case OP_COPY_B2T: copy_buffer_image(ex, payload, 1); break;
+56 -3
View File
@@ -291,6 +291,33 @@ static void init_image(Tex *e, VkCommandBuffer cb) {
}
}
// TOTK_SHADOW_SCALE (1 to 4): the game's shadow maps are made this many times larger on each side. TotK renders its
// sun cascades into a 960x960 DEPTH16 2D array of 4 layers and its static shadows into 1440x1440 and 720x720 arrays of 2,
// whatever the output resolution, and shrinks the cascades under load (872, 848, 800, 480 and so on). Those are made
// larger here: render passes into them scale the game's viewports and scissors (vk_exec.c resolve_rt, pass_scale),
// copies scale their regions (vk_exec_xfer.c sub_resolve), and shaders sample them with normalised coordinates, so the
// game sees nothing of it. A map whose storage the CPU writes (mirrored) or that aliases pool memory keeps its size,
// as its texels must match the guest layout.
static int shadow_scale(void) {
static int scale = -1;
if (scale < 0) {
int s = cfg_int("TOTK_SHADOW_SCALE");
s = s < 1 ? 1 : s > 4 ? 4 : s;
rt_log("vk: shadow map scale %d", s);
scale = s;
}
return scale;
}
static int shadow_scale_of(const nvn_texdesc *d, const TexVkFormat *f, uint32_t samples, int mirrored) {
int scale = shadow_scale();
if (scale == 1 || mirrored || samples > 1 || d->format != NVN_FORMAT_DEPTH16 || !f->depth) return 1;
if (d->target != NVN_TEXTURE_TARGET_2D_ARRAY || d->width != d->height || d->width < 256 || d->depth < 2 || d->depth > 4)
return 1;
uint32_t most = vk_ctx()->props.limits.maxImageDimension2D;
return d->width * (uint32_t)scale <= most ? scale : 1;
}
// defer_init: the caller records the image's first transition (init_image or alias_copy).
static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) {
static volatile int32_t n_unmapped, n_unsupported, n_failed, n_unbound, n_thick;
@@ -318,6 +345,8 @@ static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) {
case NVN_TEXTURE_TARGET_CUBEMAP: case NVN_TEXTURE_TARGET_CUBEMAP_ARRAY: layers = L.layers; break;
default: break;
}
int scale = shadow_scale_of(d, &f, d->samples > 1 ? (uint32_t)d->samples : 1, tex_mirrored(t));
w *= (uint32_t)scale, h *= (uint32_t)scale;
uint32_t levels = umax(d->levels, 1), most = 1;
for (uint32_t s = umax(umax(w, h), depth); s > 1; s >>= 1) most++;
if (levels > most) levels = most;
@@ -330,6 +359,10 @@ static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) {
if (!ds && (ff & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if ((d->flags & NVN_TEXTURE_FLAGS_IMAGE) && (ff & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) && samples == 1)
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
// contact shadows (vk_contact.c) darken the game's B10G11R11 scene colour in place from a compute shader
if (f.vk == VK_FORMAT_B10G11R11_UFLOAT_PACK32 && (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) && samples == 1 &&
(ff & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) && w >= 640 && vk_contact_strength())
usage |= VK_IMAGE_USAGE_STORAGE_BIT;
VkImageCreateFlags flags = ds ? 0 : VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; // format-reinterpreting views
// Cube compatible only for cube targets: the replay makes cube views of nothing else (vk_exec_draw.c get_view checks
// the image's own view type). The flag on every square array of 6+ layers changed how AMD's Windows driver lays such
@@ -387,7 +420,7 @@ static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) {
e->f = f;
e->pub.addr = k->addr;
VkImage img = VK_NULL_HANDLE;
if (nvn_gpu_only(k->addr)) { // storage in a physical or virtual pool: alias the pool memory (section 3.2)
if (scale == 1 && nvn_gpu_only(k->addr)) { // storage in a physical or virtual pool: alias the pool memory (section 3.2)
if (vkCreateImage(c->device, &ci, NULL, &img) == VK_SUCCESS && !bind_pool_memory(c, e, img, L.size)) {
vkDestroyImage(c->device, img, NULL);
img = VK_NULL_HANDLE;
@@ -429,6 +462,7 @@ static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) {
p->bound = e->bound_to != NULL;
p->block_views = (uint8_t)block_views;
p->fb_usage = (uint8_t)fb_usage;
p->scale = (uint8_t)scale;
if (!defer_init) {
init_image(e, vk_upload_begin());
@@ -1550,6 +1584,7 @@ static VkCompareOp compare_op(uint32_t f) {
}
static struct { int init, mirror_clamp_edge, minmax, border_no_format; } sf;
static float aniso_force; // TOTK_ANISO: 0 = the game's level, else the least level of mipmapped colour samplers
static void sampler_features(VkCtx *c) {
if (sf.init) return;
@@ -1561,6 +1596,9 @@ static void sampler_features(VkCtx *c) {
sf.mirror_clamp_edge = f12.samplerMirrorClampToEdge;
sf.minmax = f12.samplerFilterMinmax;
sf.border_no_format = c->has_custom_border_color && cbf.customBorderColors && cbf.customBorderColorWithoutFormat;
const char *an = cfg_value("TOTK_ANISO");
aniso_force = strcmp(an, "game") ? (float)atoi(an) : 0;
rt_log("vk: anisotropic filtering: %s", aniso_force > 1 ? an : "the game's");
sf.init = 1;
}
@@ -1595,13 +1633,28 @@ static VkSampler make_sampler(VkCtx *c, const samp_par *d) {
if (si.maxLod < si.minLod) si.maxLod = si.minLod;
float lim = c->props.limits.maxSamplerLodBias;
si.mipLodBias = fmaxf(-lim, fminf(lim, d->lod_bias));
if (d->aniso > 1) {
float aniso = d->aniso;
// TOTK_ANISO raises the level for filtered, mipmapped colour samplers that repeat on U and V only: the surface
// textures it is for tile. Depth-compare (shadow) samplers, nearest or unmipmapped ones (data maps, LUTs) and clamped
// ones keep what the game set. Clamped samplers read screen-sized buffers, whose coordinates can stretch steeply at
// an object's edge; there anisotropic taps reach across the edge and leave a speckled fringe (the inventory's
// character preview).
int tiled = (modes[0] == VK_SAMPLER_ADDRESS_MODE_REPEAT || modes[0] == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT) &&
(modes[1] == VK_SAMPLER_ADDRESS_MODE_REPEAT || modes[1] == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT);
if (aniso_force > 1 && tiled && !d->cmp_mode && si.magFilter == VK_FILTER_LINEAR && si.minFilter == VK_FILTER_LINEAR &&
(mf == NVN_MIN_FILTER_LINEAR_MIPMAP_NEAREST || mf == NVN_MIN_FILTER_LINEAR_MIPMAP_LINEAR) && aniso < aniso_force)
aniso = aniso_force;
if (aniso > 1) {
si.anisotropyEnable = VK_TRUE;
si.maxAnisotropy = fminf(d->aniso, c->props.limits.maxSamplerAnisotropy);
si.maxAnisotropy = fminf(aniso, c->props.limits.maxSamplerAnisotropy);
}
if (d->cmp_mode) {
si.compareEnable = VK_TRUE;
si.compareOp = compare_op(d->cmp_func);
// A depth-compare sampler set to nearest returns 0 or 1 per tap, and the game's shaders smooth shadow edges by
// a few taps spaced for its own shadow map texels. With TOTK_SHADOW_SCALE those taps cover a fraction of the
// area and the edges turn hard and stepped; linear filtering makes every tap a 2x2 PCF instead.
if (shadow_scale() > 1) si.magFilter = si.minFilter = VK_FILTER_LINEAR;
}
VkSamplerCustomBorderColorCreateInfoEXT cb = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT};
VkSamplerReductionModeCreateInfo rm = {.sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO};
+3 -3
View File
@@ -1,7 +1,7 @@
"""Build the shader translator: build/xlate/totk_xlate.exe (Ryujinx 1.3.2 + patches, NativeAOT, SPIR-V for Vulkan).
Copies tools/vendor/ryujinx to build/xlate/ryujinx, applies tools/xlate/patches/*.patch there (tools/vendor stays
byte-identical; 0001-0004 are MNG's, 0005-0009 are ours), then runs `dotnet publish`. Needs the .NET 10 SDK, the MSVC build
byte-identical; 0001-0004 are MNG's, 0005-0011 are ours), then runs `dotnet publish`. Needs the .NET 10 SDK, the MSVC build
tools and vswhere.exe (docs/building.md). Run with .venv. On Linux it builds build/xlate/totk_xlate for linux-x64, which
NativeAOT links with the system's clang (no MSVC); NativeAOT builds for the OS it runs on only.
@@ -99,8 +99,8 @@ def prepare_sources() -> Path:
for junk in list(src.glob("src/*/obj")) + list(src.glob("src/*/bin")):
shutil.rmtree(junk)
patches = sorted((XLATE / "patches").glob("*.patch"))
if len(patches) != 9:
sys.exit(f"expected 9 patches, found {len(patches)}")
if len(patches) != 11:
sys.exit(f"expected 11 patches, found {len(patches)}")
for patch in patches:
# git apply works outside a repository when given a directory; --check first so a failure names the patch.
for extra in (["--check"], []):
@@ -0,0 +1,135 @@
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -1456,7 +1456,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
else if (isShadow)
{
- if (hasLodLevel)
+ if (hasLodLevel && isArray && coordsCount == 2 && operandsMask == ImageOperandsMask.Lod)
+ {
+ result = GeneratePcssSample(context, declaration, image, pCoords, dRef, lod);
+ }
+ else if (hasLodLevel)
{
result = context.ImageSampleDrefExplicitLod(resultType, image, pCoords, dRef, operandsMask, operands);
}
@@ -1486,6 +1490,92 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return new OperationResult(swizzledResultType, result);
}
+ // TOTK: percentage-closer soft shadows for the sun. The deferred lighting shaders take the shadow as four
+ // explicit-LOD compares on a 2D array (the cascades) around the receiver; each of those becomes a PCSS lookup
+ // when the support buffer's pcss.x is above 0 (runtime/vk_exec_draw.c support_fill, TOTK_PCSS): a blocker
+ // search reads the raw depths at eight points within pcss.w (UV), the average depth of those nearer the light
+ // than the receiver gives the penumbra width (receiver - blocker) * pcss.x, clamped to pcss.y..pcss.z (UV), and
+ // eight compares on a disc of that radius average to the result. With pcss.x at 0 the original single compare
+ // runs. The cascades are orthographic, so the depth difference is a distance along the light.
+ private static readonly float[] PcssSearch = [-0.7f, -0.7f, 0.7f, -0.7f, -0.7f, 0.7f, 0.7f, 0.7f, 0f, -1f, 0f, 1f, -1f, 0f, 1f, 0f];
+ private static readonly float[] PcssDisk =
+ [
+ -0.9420f, -0.3991f, 0.9456f, -0.7689f, -0.0942f, -0.9294f, 0.3450f, 0.2939f,
+ -0.9159f, 0.4577f, -0.8154f, -0.8791f, -0.3828f, 0.2768f, 0.9748f, 0.7565f,
+ ];
+
+ private static SpvInstruction GeneratePcssSample(CodeGenContext context, SamplerDeclaration declaration, SpvInstruction image,
+ SpvInstruction pCoords, SpvInstruction dRef, SpvInstruction lod)
+ {
+ SpvInstruction f32 = context.TypeFP32(), s32 = context.TypeS32(), b = context.TypeBool();
+ SpvInstruction v2 = context.TypeVector(f32, 2), v3 = context.TypeVector(f32, 3), v4 = context.TypeVector(f32, 4);
+ SpvInstruction iv2 = context.TypeVector(s32, 2), iv3 = context.TypeVector(s32, 3);
+ SpvInstruction zero = context.Constant(f32, 0f), one = context.Constant(f32, 1f);
+
+ SpvInstruction prmPtr = context.AccessChain(context.TypePointer(StorageClass.Uniform, v4), context.ConstantBuffers[SupportBuffer.Binding],
+ context.Constant(s32, (int)SupportBufferField.Pcss));
+ SpvInstruction prm = context.Load(v4, prmPtr);
+ SpvInstruction lightSize = context.CompositeExtract(f32, prm, 0);
+ SpvInstruction minR = context.CompositeExtract(f32, prm, 1);
+ SpvInstruction maxR = context.CompositeExtract(f32, prm, 2);
+ SpvInstruction searchR = context.CompositeExtract(f32, prm, 3);
+
+ SpvInstruction pcssLabel = context.Label(), plainLabel = context.Label(), mergeLabel = context.Label();
+ SpvInstruction enabled = context.FOrdGreaterThan(b, lightSize, zero); // before the merge: it must precede the branch
+ context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone);
+ context.BranchConditional(enabled, pcssLabel, plainLabel);
+
+ context.AddLabel(plainLabel);
+ SpvInstruction plain = context.ImageSampleDrefExplicitLod(f32, image, pCoords, dRef, ImageOperandsMask.Lod, lod);
+ context.Branch(mergeLabel);
+
+ context.AddLabel(pcssLabel);
+ SpvInstruction raw = context.Image(declaration.ImageType, image);
+ SpvInstruction size3 = context.ImageQuerySizeLod(iv3, raw, context.Constant(s32, 0));
+ SpvInstruction sizeI = context.VectorShuffle(iv2, size3, size3, 0, 1);
+ SpvInstruction sizeF = context.ConvertSToF(v2, sizeI);
+ SpvInstruction maxI = context.ISub(iv2, sizeI, context.ConstantComposite(iv2, context.Constant(s32, 1), context.Constant(s32, 1)));
+ SpvInstruction zeroI = context.ConstantComposite(iv2, context.Constant(s32, 0), context.Constant(s32, 0));
+ SpvInstruction uv = context.VectorShuffle(v2, pCoords, pCoords, 0, 1);
+ SpvInstruction layerF = context.CompositeExtract(f32, pCoords, 2);
+ SpvInstruction layerI = context.ConvertFToS(s32, layerF);
+ SpvInstruction searchV = context.CompositeConstruct(v2, searchR, searchR);
+
+ SpvInstruction sum = zero, count = zero;
+ for (int i = 0; i < PcssSearch.Length; i += 2)
+ {
+ SpvInstruction off = context.ConstantComposite(v2, context.Constant(f32, PcssSearch[i]), context.Constant(f32, PcssSearch[i + 1]));
+ SpvInstruction p = context.FAdd(v2, uv, context.FMul(v2, off, searchV));
+ SpvInstruction t = context.GlslSClamp(iv2, context.ConvertFToS(iv2, context.FMul(v2, p, sizeF)), zeroI, maxI);
+ SpvInstruction c = context.CompositeConstruct(iv3, context.CompositeExtract(s32, t, 0), context.CompositeExtract(s32, t, 1), layerI);
+ SpvInstruction d = context.CompositeExtract(f32, context.ImageFetch(v4, raw, c, ImageOperandsMask.Lod, context.Constant(s32, 0)), 0);
+ SpvInstruction blocker = context.FOrdLessThan(b, d, dRef);
+ sum = context.FAdd(f32, sum, context.Select(f32, blocker, d, zero));
+ count = context.FAdd(f32, count, context.Select(f32, blocker, one, zero));
+ }
+
+ SpvInstruction avg = context.FDiv(f32, sum, context.GlslFMax(f32, count, one));
+ SpvInstruction penumbra = context.Select(f32, context.FOrdGreaterThan(b, count, zero),
+ context.FMul(f32, context.FSub(f32, dRef, avg), lightSize), zero);
+ SpvInstruction radius = context.GlslFClamp(f32, penumbra, minR, maxR);
+ SpvInstruction radiusV = context.CompositeConstruct(v2, radius, radius);
+
+ SpvInstruction acc = zero;
+ for (int i = 0; i < PcssDisk.Length; i += 2)
+ {
+ SpvInstruction off = context.ConstantComposite(v2, context.Constant(f32, PcssDisk[i]), context.Constant(f32, PcssDisk[i + 1]));
+ SpvInstruction p = context.FAdd(v2, uv, context.FMul(v2, off, radiusV));
+ SpvInstruction c = context.CompositeConstruct(v3, context.CompositeExtract(f32, p, 0), context.CompositeExtract(f32, p, 1), layerF);
+ acc = context.FAdd(f32, acc, context.ImageSampleDrefExplicitLod(f32, image, c, dRef, ImageOperandsMask.Lod, lod));
+ }
+
+ SpvInstruction pcss = context.FMul(f32, acc, context.Constant(f32, 2f / PcssDisk.Length));
+ context.Branch(mergeLabel);
+
+ context.AddLabel(mergeLabel);
+ return context.Phi(f32, pcss, pcssLabel, plain, plainLabel);
+ }
+
private static OperationResult GenerateTextureQuerySamples(CodeGenContext context, AstOperation operation)
{
AstTextureOperation texOp = (AstTextureOperation)operation;
--- a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
+++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
@@ -25,6 +25,7 @@ namespace Ryujinx.Graphics.Shader
TfeOffset,
TfeVertexCount,
PointSize,
+ Pcss,
}
public struct SupportBuffer
@@ -82,7 +83,8 @@ namespace Ryujinx.Graphics.Shader
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
new StructureField(AggregateType.S32, "tfe_vertex_count"),
- new StructureField(AggregateType.FP32, "point_size")
+ new StructureField(AggregateType.FP32, "point_size"),
+ new StructureField(AggregateType.Vector4 | AggregateType.FP32, "pcss")
]);
}
@@ -98,5 +100,6 @@ namespace Ryujinx.Graphics.Shader
public Vector4<int> TfeOffset;
public Vector4<int> TfeVertexCount;
public Vector4<float> PointSize;
+ public Vector4<float> Pcss;
}
}
@@ -0,0 +1,82 @@
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -853,7 +853,51 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
private static OperationResult GenerateLoad(CodeGenContext context, AstOperation operation)
{
- return GenerateLoadOrStore(context, operation, isStore: false);
+ OperationResult result = GenerateLoadOrStore(context, operation, isStore: false);
+ if (result.Type == AggregateType.FP32 && IsShadowSpread(context, operation))
+ {
+ SpvInstruction f32 = context.TypeFP32(), v4 = context.TypeVector(f32, 4);
+ SpvInstruction prmPtr = context.AccessChain(context.TypePointer(StorageClass.Uniform, v4), context.ConstantBuffers[SupportBuffer.Binding],
+ context.Constant(context.TypeS32(), (int)SupportBufferField.ShadowSpread));
+ SpvInstruction factor = context.CompositeExtract(f32, context.Load(v4, prmPtr), 0);
+ result = new OperationResult(AggregateType.FP32, context.FMul(f32, result.Value, factor));
+ }
+
+ return result;
+ }
+
+ // TOTK: the spread of the sun shadow filter. The deferred lighting shaders take the sun shadow as four compares
+ // at the receiver ± h on the cascade map, h = 0.5 * c[56].xy (UV) of their lighting block (fp_c9, or fp_c5 in
+ // some), the same in every cascade. In the near cascade the map is sharp enough that the four copies of an edge
+ // show apart. In a fragment shader that has a 2D array shadow sampler, reads of element 56 .x and .y from any
+ // constant buffer but fp_c4 (which holds the cascade matrices, row 56 being cascade 3's depth row) are multiplied
+ // by the support buffer's shadow_spread.x (runtime/vk_exec_draw.c support_fill, TOTK_SHADOW_SPREAD; 1 keeps the
+ // game's spread).
+ private static bool IsShadowSpread(CodeGenContext context, AstOperation load)
+ {
+ if (context.Definitions.Stage != ShaderStage.Fragment ||
+ load.StorageKind != StorageKind.ConstantBuffer ||
+ load.SourcesCount != 4 ||
+ load.GetSource(0) is not AstOperand { Type: OperandType.Constant } binding ||
+ load.GetSource(1) is not AstOperand { Type: OperandType.Constant, Value: 0 } ||
+ load.GetSource(2) is not AstOperand { Type: OperandType.Constant, Value: 56 } ||
+ load.GetSource(3) is not AstOperand { Type: OperandType.Constant, Value: < 2 } ||
+ !context.Properties.ConstantBuffers.TryGetValue(binding.Value, out BufferDefinition buffer) ||
+ buffer.Name == "fp_c4")
+ {
+ return false;
+ }
+
+ foreach (TextureDefinition texture in context.Properties.Textures.Values)
+ {
+ if ((texture.Type & (SamplerType.Mask | SamplerType.Array | SamplerType.Shadow)) ==
+ (SamplerType.Texture2D | SamplerType.Array | SamplerType.Shadow))
+ {
+ return true;
+ }
+ }
+
+ return false;
}
private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation)
--- a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
+++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.Shader
TfeVertexCount,
PointSize,
Pcss,
+ ShadowSpread,
}
public struct SupportBuffer
@@ -84,7 +85,8 @@ namespace Ryujinx.Graphics.Shader
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
new StructureField(AggregateType.S32, "tfe_vertex_count"),
new StructureField(AggregateType.FP32, "point_size"),
- new StructureField(AggregateType.Vector4 | AggregateType.FP32, "pcss")
+ new StructureField(AggregateType.Vector4 | AggregateType.FP32, "pcss"),
+ new StructureField(AggregateType.Vector4 | AggregateType.FP32, "shadow_spread")
]);
}
@@ -101,5 +103,6 @@ namespace Ryujinx.Graphics.Shader
public Vector4<int> TfeVertexCount;
public Vector4<float> PointSize;
public Vector4<float> Pcss;
+ public Vector4<float> ShadowSpread;
}
}
+20
View File
@@ -140,6 +140,26 @@
# window is no longer stretched as it used to be; stretch fills the window. Changes while the game runs take effect at
# once.
# aspect = fit
# Anisotropic filtering of mipmapped textures: game (the default: the level the game asks for, as on the Switch) or 2, 4,
# 8, 16, which keep ground and walls seen at a steep angle sharp. Depth-compare (shadow) and nearest-filtered samplers
# keep the game's setting. Read at start.
# aniso = game
# Shadow map resolution: the game's sun cascades are 960x960 (and its static shadow maps 1440 and 720) whatever the game
# resolution; 2 to 4 draws them that many times larger on each side, for sharper shadows. 1 = the game's. The game's
# shadow edge filter is narrowed by the same factor (translator patch 0011), so close shadow edges don't look doubled.
# Read at start.
# shadow_scale = 1
# Sun shadow cascade range in percent of the game's (its cascades end at 20, 40, 80 and 160 m), 50 to 400: higher moves
# the steps where shadows turn softer further away, and spreads the shadow map over more ground. Read at start.
# shadow_range = 100
# Screen-space contact shadows, strength in percent (0 = off): after the game lights its scene, each pixel traces a
# short way toward the sun through the depth buffer and darkens where something close blocks it, adding the small
# shadows at feet, under stones and in grass that the shadow maps miss. Read at start.
# contact_shadows = 0
# Soft sun shadows (PCSS), percent of the default penumbra width (0 to 200; 0 = the game's own four-tap filter): sharp
# where an object meets the ground and softer the further its shadow falls. Done in the translated shaders (translator
# patch 0010), so the shader cache is rebuilt once after an update. Read at start.
# pcss = 0
[language]
# Language: a Windows locale such as de-DE, or a Switch code (en-US, ja, zh-Hant, ...). Default: the Windows locale.