From 309e412f1f823708a8a2ce2c5baf1a7ba8a5ca14 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Wed, 11 Nov 2026 14:13:34 -0500 Subject: [PATCH] Make HDR one fixed look with true blacks, and keep only its on switch and the three whites as settings. HDR on is the game's scene through the observer-response mapper, anchored to the game's own mid grey and tone slope, with half of the hue bend of the game's clamp: the defaults the menu had. The tone mapper choice, the mid grey, the tone slope and its match, the hue clip, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare, hue, grade and gamma are gone from the menu, the settings table and the example ini; a file that still sets them works as before, the keys read by nothing and logging nothing. The menu's HDR row is Off and On, with the peak, game and HUD whites under it. The game's own HDR is still there for a file that sets hdr = native, and the row lists it only then. The luminance mapper and its grading code, which no setting reaches any more, are removed from the tonemap library, and the support buffer carries one vec4 for it. The dark tones of a dark scene sat far above black: the present showed the game's sRGB-encoded picture as that encode decodes, linear near black, while the game is graded for a display of gamma about 2.2, and since the observer-response mapper became the default the 2.2 correction ran with the luminance mapper only. A scene value of 0.003 came out at 0.29 nits at a white of 350 nits, where a 2.2 display shows the game at 0.015. The present now always shows the picture as a 2.2 display would, and the tonemap aims its peak lower by that correction's inverse, so the brightest light still reaches the display's peak; the same value comes out at 0.017 nits. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- THIRD_PARTY_NOTICES.md | 4 +- docs/README-BuildKit.md | 2 +- docs/architecture.md | 7 +- nx2navi.ini.example | 41 +--- runtime/cfg.c | 36 ++- runtime/shaders/hdr_tonemap.frag | 380 ++++++------------------------- runtime/ui/menu.cpp | 44 +--- runtime/ui_text.c | 148 +----------- runtime/ui_text.h | 13 +- runtime/vk.h | 4 +- runtime/vk_exec_draw.c | 6 +- runtime/vk_hdr.c | 62 ++--- tools/menu_preview.cpp | 2 +- 14 files changed, 131 insertions(+), 620 deletions(-) diff --git a/README.md b/README.md index e8ed75b6..84d28456 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Report problems as issues, following [CONTRIBUTING.md](CONTRIBUTING.md). bars stay clean. The frame limit counts the frames the game draws, and frame generation adds its frames on top of it. The frame limit can also be handed to RivaTuner Statistics Server -- HDR on HDR displays: the game's own native HDR, or its scene tone mapped to the display's peak and matched to the game's mid grey and tone, with exposure, highlights, shadows, contrast and saturation in the menu +- HDR on HDR displays: the game's scene tone mapped to the display's peak, matched to the game's mid grey and tone, with true blacks; peak, game and HUD brightness in the menu - The game's known shaders prepared when the game is built, and the pipelines met in play built again in the background at each start, so play rarely stops to compile them; a shader or pipeline met for the first time can still hold a frame, and a draw that must wait for its pipeline has it built on worker threads, never on the game's diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 730cb572..6148c482 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -38,8 +38,8 @@ Code copied or adapted from another project is listed here with its source, the | libvpx 1.17.0 (MSYS2 `mingw-w64-ucrt-x86_64-libvpx`), static link (`libvpx.a`), no DLL shipped; the release notices must carry its BSD licence text and the WebM Project's `PATENTS` grant | https://chromium.googlesource.com/webm/libvpx, Copyright (c) 2010, The WebM Project authors | BSD-3-Clause | `runtime/ov_movie.c` (VP9 decoding of the `Event/Movie` WebM files) | | MacNativeGames NX2navi resolution patches: output, swapchain and scene size sites and instruction words for 1.2.1 and 1.4.3, the data-page layout, the HeapDef heap names and sizes | https://github.com/ly05010419/MacNativeGames @ `aaf6ba1f494fabaa209d049e528d6f923dfe822a` (`runtime/game_patches.c`, `game_patches_v143.c`, `game_version.h`, `scripts/make_output_res_overlay.py`), Copyright (c) 2026 MacNativeGames contributors | MIT | `recomp/patches-v121.txt`, `recomp/patches-v143.txt`, `runtime/resolution.c` | | AMD FidelityFX Super Resolution 1.0 (`ffx_a.h`, `ffx_fsr1.h`, unmodified; licence text in `runtime/shaders/ffx/LICENSE.txt`) | https://github.com/GPUOpen-Effects/FidelityFX-FSR @ `a21ffb8f6c13233ba336352bdff293894c706575`, Copyright (c) 2021 Advanced Micro Devices, Inc. | MIT | `runtime/shaders/ffx/`, `runtime/shaders/fsr_easu.comp`, `runtime/shaders/fsr_rcas.comp` (compiled into `runtime/fsr_easu_spv.h`, `runtime/fsr_rcas_spv.h`) | -| RenoDX shader library: the RenoDRT tone mapper (Daniele curve, OkLab hue and chrominance, highlights, shadows, contrast, flare, saturation, highlight saturation, blowout), the luminance upgrade of an SDR grade, its colour matrices, and the swap chain pass's gamma correction, gamut compression, peak clamp and PQ encode, ported from HLSL to GLSL | https://github.com/clshortfuse/renodx @ `bc0ead39917634e95c534f7e8cfb59ab82880747` (`src/shaders/tonemap.hlsl`, `tonemap/reno_drt.hlsl`, `tonemap/daniele.hlsl`, `draw.hlsl`, `colorgrade.hlsl`, `colorcorrect.hlsl`, `color/oklab.hlsl`, `color/rgb.hlsl`, `color/pq.hlsl`, `color/srgb.hlsl`, `color/gamma.hlsl`; setting names, ranges and defaults of `src/templates/settings.hpp`), Copyright (c) 2025 Carlos Lopez Jr. | MIT | `runtime/shaders/hdr_tonemap.frag` and `runtime/shaders/hdr_encode.comp` (compiled into `runtime/hdr_tonemap_spv.h`, `runtime/hdr_encode_spv.h`); the HDR settings of `runtime/cfg.c`, `runtime/vk_hdr.c` and `runtime/ui/menu.cpp` | -| RenoDX scene mapper for this game and its PsychoV35 tone mapper: the finite cone response in LMS with its mean, deficit and midpoint chroma, the highlights, shadows and purity grading, the exact fit to the target gamut, the mid grey anchor and tone slope of the game's curve, and the hue clip emulation, ported from HLSL and Slang to GLSL (the paths its scene mapper takes; without eye adaptation) | https://github.com/clshortfuse/renodx @ `59bd6f606a5d105bc6de52627332703c840443be` (`src/shaders/tonemap/psychov/test35.hlsl`, `src/games/zelda-oot-oz/scene_tonemap.slangi`, `shared.h`; setting names, ranges and defaults of its `addon.cpp`), Copyright (C) 2026 Carlos Lopez | MIT | `runtime/shaders/hdr_tonemap.frag` (compiled into `runtime/hdr_tonemap_spv.h`); the HDR settings of `runtime/cfg.c`, `runtime/vk_hdr.c` and `runtime/ui/menu.cpp` | +| RenoDX shader library: the swap chain pass's gamma correction, gamut compression, peak clamp and PQ encode, and the gamma correction of the tone mapper's peak, ported from HLSL to GLSL | https://github.com/clshortfuse/renodx @ `bc0ead39917634e95c534f7e8cfb59ab82880747` (`src/shaders/draw.hlsl`, `tonemap.hlsl`, `colorcorrect.hlsl`, `color/pq.hlsl`, `color/srgb.hlsl`, `color/gamma.hlsl`), Copyright (c) 2025 Carlos Lopez Jr. | MIT | `runtime/shaders/hdr_tonemap.frag` and `runtime/shaders/hdr_encode.comp` (compiled into `runtime/hdr_tonemap_spv.h`, `runtime/hdr_encode_spv.h`) | +| RenoDX scene mapper for this game and its PsychoV35 tone mapper: the finite cone response in LMS with its mean, deficit and midpoint chroma, the exact fit to the target gamut, the mid grey anchor and tone slope of the game's curve, and the hue clip emulation, ported from HLSL and Slang to GLSL (the path of its default settings; without eye adaptation) | https://github.com/clshortfuse/renodx @ `59bd6f606a5d105bc6de52627332703c840443be` (`src/shaders/tonemap/psychov/test35.hlsl`, `src/games/zelda-oot-oz/scene_tonemap.slangi`, `shared.h`, `addon.cpp`), Copyright (C) 2026 Carlos Lopez | MIT | `runtime/shaders/hdr_tonemap.frag` (compiled into `runtime/hdr_tonemap_spv.h`) | | AMD Display Library (ADL) declarations: `AdapterInfo`, `ADLSingleSensorData`, `ADLPMLogDataOutput`, the `ADL_PMLOG_*` sensor numbers and the `ADL2_*` function signatures | https://github.com/GPUOpen-LibrariesAndSDKs/display-library @ `84c881cb2136f04287eb3af12de00a9e2867aa9e` (`include/adl_structures.h`, `include/adl_defines.h`, `include/adl_sdk.h`), Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc. | MIT | `runtime/perfmon.c` | | Zstandard 1.5.7 (MSYS2 `mingw-w64-ucrt-x86_64-zstd`), static link | https://github.com/facebook/zstd, Copyright (c) Meta Platforms, Inc. and affiliates | BSD-3-Clause | `runtime/resolution.c` (the HeapDef table), `runtime/ov_zstd.c` (the game's zstd frames decoded on the host) | | musl `fma.c`, `fmaf.c` and `scalbn.c`, adapted into one file for the x86-64-v2 build | https://github.com/kraj/musl @ `5a56ad13e1e0790c06122978aad2a14d6b9efc1a` (mirror of https://git.musl-libc.org/cgit/musl), Copyright (c) 2005-2020 Rich Felker and contributors | MIT | `runtime/fma.c` (the correctly rounded fma/fmaf the FP lowering needs on CPUs without FMA3) | diff --git a/docs/README-BuildKit.md b/docs/README-BuildKit.md index 2c1ed488..33525c4c 100644 --- a/docs/README-BuildKit.md +++ b/docs/README-BuildKit.md @@ -172,7 +172,7 @@ beside the game. FSR 3.1 on any card where `amd_fidelityfx_vk.dll` loads, FSR 4 on the Radeon cards AMD's FSR 4 libraries list it for), Frame generation (NVIDIA DLSS on RTX 40 and 50 cards, with a DLSS multiplier of 2x, or 3x and 4x on RTX 50 cards; FSR 3.1 on any card where it loads; it needs DLSS or FSR 3.1 - upscaling on; the frame limit counts the frames the game draws and the generated ones come on top), HDR (the game's picture tone mapped to your display, with its own grading rows, or Native, the game's own HDR; turn HDR on in Windows first) and the + upscaling on; the frame limit counts the frames the game draws and the generated ones come on top), HDR (the game's picture tone mapped to your display, with its peak, game and HUD brightness; turn HDR on in Windows first) and the resolution. A setting that takes effect only when the game starts again says "after restart" once you change it, until you set it back, and the Restart button at the bottom then counts the settings waiting for it. - **Controls** in the menu has Button prompts: the game's own, or Xbox, PlayStation, Steam Deck or keyboard and mouse ones when diff --git a/docs/architecture.md b/docs/architecture.md index 0eceb60a..6e9a9f40 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -191,8 +191,11 @@ keeps per-core data without locks and relies on that, so the host models it (`ru target and copies that into the 8-bit sRGB window texture it presents. With `NX2NAVI_HDR=1` the tonemap shader gets the function of `shaders/hdr_tonemap.frag` spliced in where its SDR curve clamps each channel, so the float picture holds the HDR picture relative to the HUD's white (the function gets the values the curve takes, and the scene - before the game's exposure gain with that gain, which its default tone mapper anchors mid grey on); the game's FSR 1 pass loses its output clamp; the present reads - the float picture in place of the window texture and `shaders/hdr_encode.comp` writes PQ for an HDR10 swapchain. + before the game's exposure gain with that gain, which it anchors mid grey on); the game's FSR 1 pass loses its output + clamp; the present reads the float picture in place of the window texture and `shaders/hdr_encode.comp` writes PQ + for an HDR10 swapchain, showing the picture as a display of gamma 2.2 would (the game is graded for one: decoded as + its sRGB encode, the darkest tones would sit far above black). The look is fixed; the settings are the peak, game and + HUD whites. `NX2NAVI_HDR=native` reports an HDR10 display to the game, which then renders and presents its own PQ picture. The menu switches between off and `1` during a run: the splice, the HDR extensions and the settings in the support buffer are there from the start whatever is set (`vk_hdr_armed`), the spliced tonemap returns the game's SDR result diff --git a/nx2navi.ini.example b/nx2navi.ini.example index c20be19f..73b08f80 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -513,11 +513,12 @@ # take effect at once. # framegen_multiplier = 2 -# HDR output: hdr = 1 sends the picture as HDR10 through the HDR tone mapper, hdr = native as the game renders it in -# its own HDR mode (both need HDR on in Windows, "Use HDR", or a Wayland compositor with colour management; without it -# the picture stays SDR, the game's own). hdr_peak_nits is your display's peak; hdr_game_nits the picture's white -# (paper white, 203 is the HDR reference) and hdr_ui_nits the HUD's and the menu's. Everything applies at once, also -# hdr between 0 and 1; only hdr = native, which the game itself takes up, waits for the next start. +# HDR output: hdr = 1 sends the picture as HDR10, the game's scene tone mapped to your display (needs HDR on in +# Windows, "Use HDR", or a Wayland compositor with colour management; without it the picture stays SDR, the game's own). +# The look is fixed; hdr_peak_nits is your display's peak, hdr_game_nits the picture's white (paper white, 203 is the +# HDR reference) and hdr_ui_nits the HUD's and the menu's. Everything applies at once. hdr = native shows the game's +# own HDR mode instead, which the game takes up at the next start. The settings of the look of earlier versions +# (hdr_tonemap, hdr_exposure, hdr_gamma and the rest) are read by nothing now and do no harm. # hdr = 0 # hdr_live = 0 leaves the HDR path out of a run that starts with hdr = 0, as before hdr followed the menu: switching # HDR on then takes a restart. Only worth setting if the picture differs with it at 1. @@ -525,36 +526,6 @@ # hdr_peak_nits = 1000 # hdr_game_nits = 203 # hdr_ui_nits = 203 -# With hdr = 1, the tone mapper: vanilla is the game's own curve at SDR brightness; psychov35 sends the scene through a -# model of the eye's response, matched to the game's mid grey and tone, up to the peak; renodrt carries the game's SDR -# look up to the peak by brightness. -# hdr_tonemap = psychov35 -# With psychov35: where the scene's mid grey lands (vanilla: where the game's curve puts it; render: at the scene's -# value); whether the mid tones get the contrast of the game's curve; the tone curve's steepness through mid grey from -# 0 to 100 (50 unchanged); and in percent how much bright colours turn as where the game's SDR picture clips (fire -# towards yellow and white). -# hdr_midgray = vanilla -# hdr_match_slope = 1 -# hdr_tone_slope = 50 -# hdr_hue_clip = 50 -# With psychov35 and renodrt: exposure in percent (100 unchanged); highlights, shadows, contrast and saturation from 0 -# to 100 (50 unchanged). -# hdr_exposure = 100 -# hdr_highlights = 50 -# hdr_shadows = 50 -# hdr_contrast = 50 -# hdr_saturation = 50 -# With renodrt: gamma correction (off, 2.2 or 2.4) shows the picture as a display of that power would, off keeps the -# sRGB curve; highlight saturation from 0 to 100 (50 unchanged); blowout (bright parts towards white) and flare (lifted -# blacks) from 0 to 100 (0 none); hue correction in percent (how much bright colours keep their hue); grade, the -# percentage of the game's own colour grading the picture keeps. -# hdr_gamma = 2.2 -# hdr_highlight_sat = 50 -# hdr_blowout = 0 -# hdr_flare = 0 -# hdr_hue = 100 -# hdr_grade = 100 - [language] # Language: a Windows locale such as de-DE, or a Switch code (en-US, ja, zh-Hant, ...). Default: the Windows locale. # locale = en-US diff --git a/runtime/cfg.c b/runtime/cfg.c index c1c3b179..5748008a 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -47,10 +47,13 @@ static const char *const known[] = { "DESC_TEMPLATE", "DEVICE_PROFILE", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_CAM_KEEP", "DLSS_IDLE_FREE", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TARGET_KEEP", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG", "DUMP_RAW", "DUMP_SHADERS", "DUMP_FILE", "DS3", "LOG_CS_BINDS", "LOG_TEX_FLAGS", "LOG_MESH", "MESH", "POOL", "NATIVE_TENSOR", "LOG_RCOND", "LOG_RT_SIZES", "DUMP_WHITE", "DUMP_AFTER", "DUMP_MOVIE", "SUBMIT_FINISH", "ACQUIRE_WAIT", "CRASH_FILTER_GUARD", "CS_FALLBACK", "EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES", "FFX_PIPE_CACHE", "FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLIGHT_EVERY", "FLUSH_SPAN", "FLUSH_UPLOAD", "FOG_CULL", "FOLIAGE_SHADOW_CULL", "FOLIAGE_SHADOW_VRS", "FOV", "FOV_PATCH", "FG_HUDLESS_SHOW", "FG_MULT", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FRAMEGEN_MULTIPLIER", "FREEZE_IDLE", "FREEZE_BUDGET_US", "FREE_CORES", - "FULLSCREEN", "FXAA", "GAME_ASPECT", "GAME_RES", "GPA", "GPA_EMPTY", "GPA_TWIN", "GPA_TWIN_MAX_KB", "GPL", "GPL_DRAW_BUILD", "GPL_FEEDBACK", "PIPE_AHEAD", "PIPE_FAST_WORKERS", "PIPE_FRAME_WAIT_MS", "PIPE_LINK_DRAW_MS", "PIPE_SKIP_STEADY", "PROG_LEAD", "GPU", "GPU_INDEX", "GPU_PASSES", "GPU_NAME", "HDR", "HDR_BLOWOUT", "HDR_CONTRAST", - "HDR_EXPOSURE", "HDR_FLARE", "HDR_GAMMA", "HDR_GAME_NITS", "HDR_GRADE", "HDR_HIGHLIGHT_SAT", "HDR_HIGHLIGHTS", "HDR_HUE", - "HDR_HUE_CLIP", "HDR_LIVE", "HEAP_LAZY", "HDR_MATCH_SLOPE", "HDR_MIDGRAY", - "HDR_PEAK_NITS", "HDR_SATURATION", "HDR_SHADOWS", "HDR_TONEMAP", "HDR_TONE_SLOPE", "HDR_UI_NITS", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD", + "FULLSCREEN", "FXAA", "GAME_ASPECT", "GAME_RES", "GPA", "GPA_EMPTY", "GPA_TWIN", "GPA_TWIN_MAX_KB", "GPL", "GPL_DRAW_BUILD", "GPL_FEEDBACK", "PIPE_AHEAD", "PIPE_FAST_WORKERS", "PIPE_FRAME_WAIT_MS", "PIPE_LINK_DRAW_MS", "PIPE_SKIP_STEADY", "PROG_LEAD", "GPU", "GPU_INDEX", "GPU_PASSES", "GPU_NAME", "HDR", + "HDR_GAME_NITS", "HDR_LIVE", "HEAP_LAZY", "HDR_PEAK_NITS", "HDR_UI_NITS", + // retired: the HDR look's settings of earlier versions, now fixed (vk_hdr.c, shaders/hdr_tonemap.frag); read by + // nothing, listed so that a file which still sets them logs no unknown key + "HDR_BLOWOUT", "HDR_CONTRAST", "HDR_EXPOSURE", "HDR_FLARE", "HDR_GAMMA", "HDR_GRADE", "HDR_HIGHLIGHT_SAT", + "HDR_HIGHLIGHTS", "HDR_HUE", "HDR_HUE_CLIP", "HDR_MATCH_SLOPE", "HDR_MIDGRAY", "HDR_SATURATION", "HDR_SHADOWS", + "HDR_TONEMAP", "HDR_TONE_SLOPE", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD", "BIG_BUILD_FREE_MB", "BIG_BUILD_MAX_MB", "BUILD_FREE_MB", "BUILD_RESERVE_MB", "LIB_PREBUILD_BIG", "LIB_PREBUILD_BIG_BG", "LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LIB_WARM", "PIPE_LINK_MS", "FRAME_HOLD_MS", "PACE_SMOOTH", "OQ_BATCH", "GATE_WAIT_MS", "LOCALE", "LOD_GUARD", "LSTICK_DEADZONE", "MEMORY", "MEM_CENSUS", "MEM_NEED_MB", "MENU_LANG", "MENU_SOUND_VOLUME", "MENU_TEST", "MENU_CURSOR", "MENU_CURSOR_LOG", "MENU_TEST_CLICK", "MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MICROPHONE", "MIRROR_GAP", "MODS_DIR", "MODS_DISABLED", "MODS_ORDER", "MODS_REBUILD", "MONITOR", "MOTION", "MOUSE_MODE", "MOUSE_DIRECT_PATCH", "MOUSE_SENS", "MOUSE_TRACE", "MOVIE_HOST", "MOVIE_PACE", "MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK", @@ -95,11 +98,7 @@ static const struct { const char *key, *section; } sections[] = { {"COLOR_PRESET", "display"}, {"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"}, {"COLOR_GAMMA", "display"}, {"HDR", "display"}, {"HDR_LIVE", "display"}, {"HDR_PEAK_NITS", "display"}, {"HDR_GAME_NITS", "display"}, - {"HDR_UI_NITS", "display"}, {"HDR_GAMMA", "display"}, {"HDR_TONEMAP", "display"}, {"HDR_EXPOSURE", "display"}, - {"HDR_HIGHLIGHTS", "display"}, {"HDR_SHADOWS", "display"}, {"HDR_CONTRAST", "display"}, {"HDR_SATURATION", "display"}, - {"HDR_HIGHLIGHT_SAT", "display"}, {"HDR_BLOWOUT", "display"}, {"HDR_FLARE", "display"}, {"HDR_HUE", "display"}, - {"HDR_GRADE", "display"}, {"HDR_MIDGRAY", "display"}, {"HDR_MATCH_SLOPE", "display"}, {"HDR_TONE_SLOPE", "display"}, - {"HDR_HUE_CLIP", "display"}, {"SHADOW_RES", "display"}, + {"HDR_UI_NITS", "display"}, {"SHADOW_RES", "display"}, {"DLAA", "display"}, {"FSR4", "display"}, {"FSR4_FG", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_IDLE_FREE", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"}, {"FRAMEGEN", "display"}, {"FRAMEGEN_MULTIPLIER", "display"}, {"FG_MULT", "display"}, {"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"}, @@ -187,22 +186,13 @@ static const CfgMeta metas[] = { {"COLOR_PRESET", "default", "default|vivid|clarity|custom", LIVE}, {"COLOR_BRIGHTNESS", "0", "-30..30", LIVE}, {"COLOR_CONTRAST", "100", "70..150", LIVE}, {"COLOR_SATURATION", "100", "50..200", LIVE}, {"COLOR_GAMMA", "1", "0.8..1.2", LIVE}, - // HDR output (vk_hdr.c): off and the tone mapper apply at once (the HDR tonemap is spliced into the game's as - // shaders load whatever is set, vk_hdr_armed); native is the game's own and is asked for once, at start, which the - // menu's row says (vk_hdr_needs_restart). HDR_LIVE = 0 leaves the HDR path out of a run that starts with HDR off. - // The grading sliders: 50 leaves the picture as it is, exposure in percent. + // HDR output (vk_hdr.c): on and off apply at once (the HDR tonemap is spliced into the game's as shaders load + // whatever is set, vk_hdr_armed); native, a value only the file sets, is the game's own and is asked for once, at + // start (vk_hdr_needs_restart). HDR_LIVE = 0 leaves the HDR path out of a run that starts with HDR off. The look is + // fixed: only the three whites are settings. {"HDR", "0", "0|1|native", LIVE}, {"HDR_LIVE", "1", "0|1", RESTART}, {"HDR_PEAK_NITS", "1000", "400..10000", LIVE}, {"HDR_GAME_NITS", "203", "48..500", LIVE}, - {"HDR_UI_NITS", "203", "48..500", LIVE}, {"HDR_GAMMA", "2.2", "off|2.2|2.4", LIVE}, - {"HDR_TONEMAP", "psychov35", "vanilla|psychov35|renodrt", LIVE}, {"HDR_EXPOSURE", "100", "0..200", LIVE}, - // the observer-response mapper's: where scene grey lands, its slope there matched to the game's curve, that slope - // (50 unchanged) and how much of the game's hue bend at its clamp bright colours get - {"HDR_MIDGRAY", "vanilla", "render|vanilla", LIVE}, {"HDR_MATCH_SLOPE", "1", "0|1", LIVE}, - {"HDR_TONE_SLOPE", "50", "0..100", LIVE}, {"HDR_HUE_CLIP", "50", "0..100", LIVE}, - {"HDR_HIGHLIGHTS", "50", "0..100", LIVE}, {"HDR_SHADOWS", "50", "0..100", LIVE}, {"HDR_CONTRAST", "50", "0..100", LIVE}, - {"HDR_SATURATION", "50", "0..100", LIVE}, {"HDR_HIGHLIGHT_SAT", "50", "0..100", LIVE}, - {"HDR_BLOWOUT", "0", "0..100", LIVE}, {"HDR_FLARE", "0", "0..100", LIVE}, {"HDR_HUE", "100", "0..100", LIVE}, - {"HDR_GRADE", "100", "0..100", LIVE}, + {"HDR_UI_NITS", "203", "48..500", LIVE}, {"DEVICE_PROFILE", "auto", "auto|desktop|deck", RESTART}, {"SHADOW_RES", "game", "game|256|512|768|1024|1536|2048", RESTART}, {"SHADOW_SCALE", "1", "1..4", RESTART}, {"SHADOW_COMPARE", "1", "0|1", RESTART}, // vk_texture.c diff --git a/runtime/shaders/hdr_tonemap.frag b/runtime/shaders/hdr_tonemap.frag index b7e95c41..a0029d5e 100644 --- a/runtime/shaders/hdr_tonemap.frag +++ b/runtime/shaders/hdr_tonemap.frag @@ -21,273 +21,97 @@ // The HDR tonemap, as a function that runtime/vk_hdr.c (vk_hdr_shader_swap) splices into the game's own tonemap // shader. nx_tonemap takes x, the three values the game feeds its fitted ACES curve (its scene light after its colour // matrix, bloom, exposure and, in the gameplay shader, local exposure), and scene, the same values without local -// exposure (rgb) with the exposure gain in them (a; 1 where the splice found none). It returns the colour the shader then stores, linear Rec. 709 -// relative to the HUD's white, which the present encodes to PQ (shaders/hdr_encode.comp). main only exists so the -// compiler keeps the functions; vk_hdr.c drops it. build_shaders.py compiles this file without optimisation ("keep -// functions" above), as inlining would leave no function to splice. +// exposure (rgb) with the exposure gain in them (a; 1 where the splice found none). It returns the colour the shader +// then stores, linear Rec. 709 relative to the HUD's white, which the present encodes to PQ (shaders/hdr_encode.comp). +// main only exists so the compiler keeps the functions; vk_hdr.c drops it. build_shaders.py compiles this file without +// optimisation ("keep functions" above), as inlining would leave no function to splice. // -// Three tone mappers (p0.w): -// - 0, the game's curve: its SDR result at the picture's white. -// - 9, the observer-response mapper (scene_tonemap, psycho35): the scene light goes through a finite cone response -// in LMS, anchored so that scene grey 0.18 lands where the game's curve puts it (or at 0.18 times the gain) and, -// with the tone slope matched, rising through it as steeply as the game's curve does at its steepest; the response -// saturates at the display's peak. Chroma is the response's residual around its mean, scaled back just enough to -// fit the target gamut. It takes the scene without local exposure. Rec. 709 is the target here: the game's scene -// target is r11g11b10f, which holds no negative component, so a wider fit would be clipped channel by channel. -// - 3, the luminance mapper (drt): the game's SDR result is the colour grade, upgraded to the HDR range by luminance -// against a neutral SDR curve of the same input, then a luminance curve towards the peak with exposure, -// highlights, shadows, contrast and flare, and in OkLab the hue of the input, saturation, highlight saturation and -// blowout. -// Settings (vk_hdr.c vk_hdr_support, support buffer bytes 1440..1519): -// p0: peak nits, game nits (white of the picture), HUD nits, tone mapper (-1: the game's own SDR picture, as when the -// swapchain is not HDR10; else as above) -// p1: exposure, highlights, shadows, contrast -// p2: saturation, highlight saturation, blowout, flare (parsed: 1 = unchanged for the first two) -// p3: hue correction, grade strength, gamma correction (0 off, 1: 2.2, 2: 2.4; applied on the present), tone slope -// p4: hue clip emulation (0..1), mid grey (0: 0.18 times the gain, 1: the game's curve at 0.18), match tone slope -// (0 or 1), unused +// The HDR picture (scene_tonemap, psycho35): the scene light goes through a finite cone response in LMS, anchored so +// that scene grey 0.18 lands where the game's curve puts it, and rising through it as steeply as the game's curve +// does at its steepest; the response saturates at the display's peak. Chroma is the response's residual around its +// mean, scaled back just enough to fit Rec. 709 (the game's scene target is r11g11b10f, which holds no negative +// component, so a wider fit would be clipped channel by channel). Bright colours get half of the hue bend the game's +// clamp gives them. It takes the scene without local exposure. The present then shows the picture as a 2.2 display +// would (hdr_encode.comp); the peak given to the response is lowered by that correction's inverse, so that the +// brightest light still lands on the display's peak. +// Settings (vk_hdr.c vk_hdr_support, support buffer bytes 1440..1455): p0 = peak nits, game nits (white of the +// picture), HUD nits, and -1 for the game's own SDR picture (as when the swapchain is not HDR10) or 1 for the HDR one. layout(location = 0) in vec4 a0; layout(location = 1) in vec4 a1; layout(location = 2) in vec4 a2; -layout(location = 3) in vec4 a3; -layout(location = 4) in vec4 a4; -layout(location = 5) in vec4 a5; -layout(location = 6) in vec4 a6; layout(location = 0) out vec4 o; // Matrices are written row by row; mulm(m, v) is the row-vector product of that layout. vec3 mulm(mat3 m, vec3 v) { return v * m; } -const mat3 BT709_TO_AP1 = mat3(0.6130974024, 0.3395231462, 0.0473794514, - 0.0701937225, 0.9163538791, 0.0134523985, - 0.0206155929, 0.1095697729, 0.8698146342); -const mat3 AP1_TO_BT709 = mat3(1.7050509927, -0.6217921207, -0.0832588720, - -0.1302564175, 1.1408047366, -0.0105483191, - -0.0240033568, -0.1289689761, 1.1529723329); -const mat3 BT709_TO_OKLABLMS = mat3(0.4122214708, 0.5363325363, 0.0514459929, - 0.2119034982, 0.6806995451, 0.1073969566, - 0.0883024619, 0.2817188376, 0.6299787005); -const mat3 OKLABLMS_TO_OKLAB = mat3(0.2104542553, 0.7936177850, -0.0040720468, - 1.9779984951, -2.4285922050, 0.4505937099, - 0.0259040371, 0.7827717662, -0.8086757660); -const mat3 OKLAB_TO_OKLABLMS = mat3(1.0, 0.3963377774, 0.2158037573, - 1.0, -0.1055613458, -0.0638541728, - 1.0, -0.0894841775, -1.2914855480); -const mat3 OKLABLMS_TO_BT709 = mat3(4.0767416621, -3.3077115913, 0.2309699292, - -1.2684380046, 2.6097574011, -0.3413193965, - -0.0041960863, -0.7034186147, 1.7076147010); - float y709(vec3 c) { return dot(c, vec3(0.2126390059, 0.7151686788, 0.0721923154)); } -float divide_safe(float a, float b, float fallback) { return b == 0.0 ? fallback : a / b; } - vec3 copy_sign(vec3 mag, vec3 s) { vec3 a = abs(mag); return mix(a, -a, lessThan(s, vec3(0.0))); } -vec3 cbrt(vec3 x) { return copy_sign(pow(abs(x), vec3(1.0 / 3.0)), x); } - -vec3 oklab_from_bt709(vec3 c) { return mulm(OKLABLMS_TO_OKLAB, cbrt(mulm(BT709_TO_OKLABLMS, c))); } - -vec3 bt709_from_oklab(vec3 lab) { - vec3 lms = mulm(OKLAB_TO_OKLABLMS, lab); - return mulm(OKLABLMS_TO_BT709, lms * lms * lms); -} - -vec3 clamp_ap1(vec3 c) { return mulm(AP1_TO_BT709, max(mulm(BT709_TO_AP1, c), vec3(0.0))); } - -// The hue (OkLab a, b direction) of correct with the chrominance of incorrect. -vec3 hue_oklab(vec3 incorrect, vec3 correct) { - vec3 il = oklab_from_bt709(incorrect), cl = oklab_from_bt709(correct); - float pre = length(il.yz); - vec2 ab = cl.yz; - ab *= divide_safe(pre, length(ab), 1.0); - il.yz = ab; - return clamp_ap1(bt709_from_oklab(il)); -} - float srgb_decode(float c) { return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4); } -// pow_to_srgb: a value of a gamma-g picture as the sRGB value of the same code (the peak's correction). +// A value of a gamma-g picture as the sRGB value of the same code (the peak's correction). float gamma_to_srgb(float c, float g) { return srgb_decode(pow(c, 1.0 / g)); } -float smoother(float t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } - -float highlights(float x, float h, float mid) { - if (h == 1.0) return x; - float t = 0.0; - if (x > mid) t = clamp(log2(x / mid) / log2(1.0 / mid), 0.0, 1.0); - t = smoother(t); - if (h > 1.0) return mix(x, mid * pow(x / mid, h), t); - float b = mid * pow(x / mid, 2.0 - h); - return divide_safe(x * x, mix(x, b, t), x); -} - -float shadows(float x, float s, float mid) { - if (s == 1.0) return x; - float ratio = max(divide_safe(x, mid, 0.0), 0.0); - float base_term = x * mid; - float base_scale = divide_safe(base_term, ratio, 0.0); - float shadow_floor = mid / 16.0; - float t = 1.0; - if (x > shadow_floor) t = clamp(log2(x / mid) / log2(shadow_floor / mid), 0.0, 1.0); - t = smoother(t); - if (s > 1.0) { - float raised = x * (1.0 + divide_safe(base_term, pow(ratio, s), 0.0)); - return x + (raised - x * (1.0 + base_scale)) * t; - } - float lowered = x * (1.0 - divide_safe(base_term, pow(ratio, 2.0 - s), 0.0)); - return x + (lowered - x * (1.0 - base_scale)) * t; -} - -// The luminance curve: n peak nits, g contrast, c_d mid grey in nits, t_1 flare; 100 nits reference, scene grey 0.18. -float curve(float x, float n, float g, float c_d, float t_1) { - const float n_r = 100.0, c = 0.18, w_g = 0.0, r_hit_min = 128.0, r_hit_max = 256.0; - float m_0 = n / n_r; - float m_1 = 0.5 * (m_0 + sqrt(m_0 * (m_0 + 4.0 * t_1))); - float r_hit = r_hit_min + (r_hit_max - r_hit_min) * (log(m_0) / log(10000.0 / 100.0)); - float u = pow((r_hit / m_1) / ((r_hit / m_1) + 1.0), g); - float m = m_1 / u; - float w_i = log(n / 100.0) / log(2.0); - float c_t = (c_d / n_r) * (1.0 + w_i * w_g); - float g_ip = 0.5 * (c_t + sqrt(c_t * (c_t + 4.0 * t_1))); - float g_ipp2 = -m_1 * pow(g_ip / m, 1.0 / g) / (pow(g_ip / m, 1.0 / g) - 1.0); - float w_2 = c / g_ipp2; - float s_2 = w_2 * m_1; - float u_2 = pow((r_hit / m_1) / ((r_hit / m_1) + w_2), g); - float m_2 = m_1 / u_2; - float ts = pow(max(0.0, x) / (x + s_2), g) * m_2; - return max(0.0, (ts * ts) / (ts + t_1)); -} - -struct Drt { - float nits_peak, exposure, highlights, shadows, contrast, saturation, dechroma, flare, hue, blowout; - float mid_gray_nits; - bool clamp_zero; // clamp to Rec. 709 (no negative channel) before the peak -}; - -// The tone mapper by luminance, then hue, saturation and blowout in OkLab; clamped to the peak in Rec. 709. -vec3 drt(vec3 bt709, Drt d) { - const float mid = 0.18; - float peak = d.nits_peak / 100.0; - float y_original = y709(bt709); - float y = y_original * d.exposure; - y = highlights(y, d.highlights, mid); - y = shadows(y, d.shadows, mid); - float y_new = clamp(curve(y, d.nits_peak, d.contrast, d.mid_gray_nits, d.flare), 0.0, peak); - vec3 color = bt709 * (y_original > 0.0 ? y_new / y_original : 0.0); - if (d.dechroma != 0.0 || d.saturation != 1.0 || d.hue != 0.0 || d.blowout != 0.0) { - vec3 lab = oklab_from_bt709(color); - if (d.hue != 0.0) { - vec3 old_lab = oklab_from_bt709(bt709); - float pre = length(lab.yz); - lab.yz = mix(lab.yz, old_lab.yz, d.hue); - lab.yz *= divide_safe(pre, length(lab.yz), 1.0); - } - if (d.dechroma != 0.0) lab.yz *= mix(1.0, 0.0, clamp(pow(y_original / 100.0, 1.0 - d.dechroma), 0.0, 1.0)); - if (d.blowout != 0.0) { - float percent_max = clamp(y_original * 100.0 / 10000.0, 0.0, 1.0); - float change = pow(1.0 - percent_max, 100.0 * abs(d.blowout)); - if (d.blowout < 0.0) change = 2.0 - change; - lab.yz *= change; - } - lab.yz *= d.saturation; - color = bt709_from_oklab(lab); - } - if (d.clamp_zero) color = max(color, vec3(0.0)); - return min(color, vec3(peak)); -} - -// The same curve at SDR range with nothing changed: what the SDR grade is measured against. -vec3 neutral_sdr(vec3 c) { - Drt d = Drt(100.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 18.0, true); - return drt(c, d); -} - -// The graded SDR colour carried to the HDR range: its luminance gains what the untonemapped colour has above the -// neutral SDR curve, in the graded colour's hue. -vec3 upgrade(vec3 untonemapped, vec3 neutral, vec3 graded, float strength) { - float yu = y709(untonemapped), yn = y709(neutral), yg = y709(graded); - float ratio; - if (yu < yn) { - ratio = yu / yn; - } else { - float y_new = yg + max(0.0, yu - yn); - ratio = yg > 0.0 ? y_new / yg : 0.0; - } - vec3 scaled = hue_oklab(graded * ratio, graded); - return mix(untonemapped, scaled, strength); -} - vec3 aces_fitted(vec3 x) { // the game's curve and clamp return clamp(x * (2.51 * x + 0.03) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); } // ---- the observer-response mapper -// Rec. 709 (D65) to the cone fundamentals LMS of the two-degree observer and back, and LMS to Rec. 2020; rows. The -// products of the XYZ matrices of each, worked out in double precision. +// Rec. 709 (D65) to the cone fundamentals LMS of the two-degree observer and back; rows. The products of the XYZ +// matrices of each, worked out in double precision. const mat3 PSY_BT709_TO_LMS = mat3(0.2896057766, 0.6972466442, 0.0763712786, 0.0901837576, 0.7073490257, 0.1122031464, 0.0155287401, 0.0536093073, 0.5097978305); const mat3 PSY_LMS_TO_BT709 = mat3(4.9676196505, -4.9223795294, 0.3391991812, -0.6196828788, 2.0517506515, -0.3587439844, -0.0861520040, -0.0658193830, 1.9889544833); -const mat3 PSY_LMS_TO_BT2020 = mat3(2.9089213537, -2.4155642448, 0.1808342948, - -0.2275532730, 1.5457966613, -0.2838427127, - -0.0502712321, 0.0409489517, 1.7552839121); -const vec3 PSY_YF_WEIGHTS = vec3(0.68990272, 0.34832189, 0.0); // luminance of the observer from L and M const vec3 PSY_D65_WHITE_LMS = vec3(1.0632236993, 0.9097359297, 0.5789358779); // PSY_BT709_TO_LMS of white // Exact mean on equal-component vectors. float psy_mean(vec3 v) { return v.x + ((v.y - v.x) + (v.z - v.x)) / 3.0; } -// A residual in white-normalised LMS as the displacement it is in the target's RGB (0: Rec. 709, else Rec. 2020). -vec3 psy_target_residual(vec3 normalized_lms, int target) { - vec3 physical_lms = normalized_lms * PSY_D65_WHITE_LMS; - return target == 0 ? mulm(PSY_LMS_TO_BT709, physical_lms) : mulm(PSY_LMS_TO_BT2020, physical_lms); -} +// A residual in white-normalised LMS as the displacement it is in Rec. 709. +vec3 psy_rgb_residual(vec3 normalized_lms) { return mulm(PSY_LMS_TO_BT709, normalized_lms * PSY_D65_WHITE_LMS); } -// The finite response u of one cone in the log domain: log_u, log_tail = log(1 - u^h) and log_deficit = log(1 - u), -// kept as logarithms so that neither end underflows. -void psy_response_logs(float log_q, float k, float n, float h, out float log_u, out float log_tail, out float log_deficit) { +// The finite response u of one cone in the log domain: log_u, and log_deficit = log(1 - u), kept as logarithms so +// that neither end underflows. +void psy_response_logs(float log_q, float k, float n, out float log_u, out float log_deficit) { float latent = log(k) - log(1.0 - k) + n * log_q; float correction = log(1.0 + exp(-abs(latent))); - log_u = (min(latent, 0.0) - correction) / h; - log_tail = -max(latent, 0.0) - correction; - log_deficit = latent > 20.0 ? log_tail - log(h) + log_u = min(latent, 0.0) - correction; + float log_tail = -max(latent, 0.0) - correction; + log_deficit = latent > 20.0 ? log_tail : log(abs(log_u) < 0.01 ? -log_u * (1.0 + log_u * (0.5 + log_u * (1.0 / 6.0 + log_u / 24.0))) : 1.0 - exp(log_u)); } -struct PsyReference { - vec3 source_ratio; // the input against its adaptation anchor, largest component 1 - vec3 log_source_ratio; - vec3 anchor_power; - vec3 input_exponent; - float response_compression; - float peak; -}; - -struct PsySample { - float mean; // the response's mean over the cones, in peak units times the peak - float headroom; // what is left up to the peak, from the deficits - vec3 residual; // the chroma: the response around its mean, its direction half way to the source's -}; - -// The response of the three cones at exposure_stops above the reference's own drive. -PsySample psy_reference_at(PsyReference reference, float exposure_stops) { +// Extended linear Rec. 709 in, relative to reference white; the same out, inside Rec. 709 and below peak. +// anchor_in is the scene grey the eye is adapted to, anchor_out where it lands; cone is the response's exponent. +vec3 psycho35(vec3 bt709, float peak, float cone, float anchor_in_gray, float anchor_out_gray) { + vec3 anchor_in = mulm(PSY_BT709_TO_LMS, vec3(anchor_in_gray)); + vec3 anchor_out = mulm(PSY_BT709_TO_LMS, vec3(anchor_out_gray)); + vec3 source_ratio = mulm(PSY_BT709_TO_LMS, bt709) / anchor_in; // the input against its adaptation + vec3 anchor_power = anchor_out / (PSY_D65_WHITE_LMS * peak); + vec3 input_exponent = cone / (1.0 - anchor_power); + if (all(equal(source_ratio, vec3(0.0)))) return vec3(0.0); + // the exposure factored out: source_ratio = drive * ratio, the largest component of ratio 1 + float drive = max(abs(source_ratio.x), max(abs(source_ratio.y), abs(source_ratio.z))); + vec3 ratio = source_ratio / drive; + vec3 log_ratio = log(vec3(ratio.x != 0.0 ? ratio.x : 1.0, ratio.y != 0.0 ? ratio.y : 1.0, ratio.z != 0.0 ? ratio.z : 1.0)); vec3 response, deficit; for (int i = 0; i < 3; ++i) { - if (reference.source_ratio[i] == 0.0) { + if (ratio[i] == 0.0) { response[i] = 0.0; deficit[i] = 1.0; } else { - float log_u, log_tail, log_deficit; - psy_response_logs(reference.log_source_ratio[i] + exposure_stops * log(2.0), reference.anchor_power[i], - reference.input_exponent[i], reference.response_compression, log_u, log_tail, log_deficit); + float log_u, log_deficit; + psy_response_logs(log_ratio[i] + log(drive), anchor_power[i], input_exponent[i], log_u, log_deficit); response[i] = exp(log_u); deficit[i] = exp(log_deficit); } @@ -300,87 +124,25 @@ PsySample psy_reference_at(PsyReference reference, float exposure_stops) { float scaled_radius = length(scaled_residual); float radius = residual_scale * scaled_radius; vec3 direction = scaled_radius > 0.0 ? scaled_residual / scaled_radius : vec3(0.0); - vec3 source = reference.source_ratio - psy_mean(reference.source_ratio); + // the chroma's direction half way to the source's + vec3 source = ratio - psy_mean(ratio); float source_radius = length(source); if (source_radius > 0.0 && radius > 0.0) { vec3 midpoint_direction = source / source_radius + direction; float midpoint_length = length(midpoint_direction); if (midpoint_length > 0.0) direction = midpoint_direction / midpoint_length; } - PsySample s; - s.mean = reference.peak * response_mean; - s.headroom = reference.peak * psy_mean(deficit); - s.residual = reference.peak * radius * direction; - return s; -} - -// Extended linear Rec. 709 in, relative to reference white; the same out, inside the target gamut and below peak. -// anchor_in is the scene grey the eye is adapted to, anchor_out where it lands; cone is the response's exponent. -vec3 psycho35(vec3 bt709, float peak, float highlights, float shadows, float contrast, float purity_scale, float cone, - float anchor_in_gray, float anchor_out_gray, int target) { - vec3 input_lms = mulm(PSY_BT709_TO_LMS, bt709); - vec3 anchor_in = mulm(PSY_BT709_TO_LMS, vec3(anchor_in_gray)); - vec3 anchor_out = anchor_out_gray == anchor_in_gray ? anchor_in : mulm(PSY_BT709_TO_LMS, vec3(anchor_out_gray)); - if (highlights != 1.0 || shadows != 1.0) { - float input_yf = dot(PSY_YF_WEIGHTS, input_lms); - float anchor_yf = dot(PSY_YF_WEIGHTS, anchor_in); - float graded_y = input_yf; - if (highlights != 1.0) { - float ratio = graded_y / anchor_yf; - float blend = graded_y > anchor_yf ? clamp(log2(ratio) / log2(1.0 / anchor_yf), 0.0, 1.0) : 0.0; - blend = smoother(blend); - float shaped = pow(ratio, abs(highlights - 1.0)); - graded_y = highlights > 1.0 ? graded_y * mix(1.0, shaped, blend) : graded_y / mix(1.0, shaped, blend); - } - if (shadows != 1.0) { - float ratio = graded_y / anchor_yf; - float blend = graded_y > anchor_yf * exp2(-4.0) ? clamp(log2(ratio) / -4.0, 0.0, 1.0) : 1.0; - blend = smoother(blend); - float target_y = shadows > 1.0 - ? graded_y * (1.0 - anchor_yf * anchor_yf) + anchor_yf * anchor_yf * anchor_yf * pow(ratio, 2.0 - shadows) - : graded_y * (1.0 + anchor_yf * anchor_yf) - anchor_yf * anchor_yf * anchor_yf * pow(ratio, shadows); - graded_y = mix(graded_y, target_y, blend); - } - if (input_yf != 0.0) input_lms *= graded_y / input_yf; - } - // purity (saturation) around the neutral of the same luminance, relative to the adaptation - vec3 source_ratio = input_lms / anchor_in; - if (purity_scale != contrast) { - float neutral_ratio = dot(PSY_YF_WEIGHTS, input_lms) / dot(PSY_YF_WEIGHTS, anchor_in); - vec3 purity_direction = source_ratio - neutral_ratio; - float purity_amount = purity_scale / contrast; - // more purity must not turn a valid cone input negative: the common amount is bounded, not single cones - if (all(greaterThanEqual(source_ratio, vec3(0.0))) && neutral_ratio > 0.0 && purity_amount > 1.0) { - for (int c = 0; c < 3; ++c) - if (purity_direction[c] < 0.0) purity_amount = min(purity_amount, neutral_ratio / -purity_direction[c]); - source_ratio = max(neutral_ratio + purity_amount * purity_direction, 0.0); - } else { - source_ratio = neutral_ratio + purity_amount * purity_direction; - } - } - PsyReference reference; - float response_power = contrast * cone; - reference.response_compression = 1.0; - reference.peak = peak; - reference.anchor_power = pow(anchor_out / (PSY_D65_WHITE_LMS * reference.peak), vec3(reference.response_compression)); - reference.input_exponent = response_power * reference.response_compression / (1.0 - reference.anchor_power); - if (all(equal(source_ratio, vec3(0.0)))) return vec3(0.0); - // the exposure factored out: source_ratio = drive * reference.source_ratio - float drive = max(abs(source_ratio.x), max(abs(source_ratio.y), abs(source_ratio.z))); - reference.source_ratio = source_ratio / drive; - reference.log_source_ratio = log(vec3(reference.source_ratio.x != 0.0 ? reference.source_ratio.x : 1.0, - reference.source_ratio.y != 0.0 ? reference.source_ratio.y : 1.0, - reference.source_ratio.z != 0.0 ? reference.source_ratio.z : 1.0)); - PsySample actual = psy_reference_at(reference, log2(drive)); - if (all(equal(actual.residual, vec3(0.0)))) return vec3(actual.mean); // neutral: nothing to fit - // the exact fit: the chroma scaled until the nearest face of the target's cube (0 or the peak) is touched - vec3 target_displacement = psy_target_residual(actual.residual, target); + float mean = peak * response_mean, headroom = peak * psy_mean(deficit); + vec3 residual = peak * radius * direction; + if (all(equal(residual, vec3(0.0)))) return vec3(mean); // neutral: nothing to fit + // the exact fit: the chroma scaled until the nearest face of the cube 0..peak is touched + vec3 displacement = psy_rgb_residual(residual); float scale = 1.0; for (int channel = 0; channel < 3; ++channel) { - if (target_displacement[channel] > 0.0) scale = min(scale, actual.headroom / target_displacement[channel]); - else if (target_displacement[channel] < 0.0) scale = min(scale, actual.mean / -target_displacement[channel]); + if (displacement[channel] > 0.0) scale = min(scale, headroom / displacement[channel]); + else if (displacement[channel] < 0.0) scale = min(scale, mean / -displacement[channel]); } - return actual.mean + psy_target_residual(actual.residual * scale, 0); + return mean + psy_rgb_residual(residual * scale); } // The bend the game's clamp gives a bright colour (a clipped channel stops, the others catch up, the hue turns towards @@ -403,36 +165,26 @@ vec3 hue_clip(vec3 untonemapped, float amount) { return untonemapped; } -// The scene (before the exposure gain) through the observer-response mapper, in picture whites. -vec3 scene_tonemap(vec3 untonemapped, float gain, vec4 p0, vec4 p1, vec4 p2, vec4 p3, vec4 p4) { - if (p1.x == 0.0) return vec3(0.0); // zero exposure is black; the mapper needs a positive anchor - float peak_nits = max(p0.x, p0.y); - float reference_exposure = p4.y == 1.0 ? aces_fitted(vec3(0.18)).x / 0.18 : 1.0; - if (p4.x > 0.0) untonemapped = hue_clip(untonemapped, p4.x); +// The scene (before the exposure gain) mapped onto the display, in picture whites. peak: the display's peak in +// picture whites, as the response is to reach it. +vec3 scene_tonemap(vec3 untonemapped, float gain, float peak) { + untonemapped = hue_clip(untonemapped, 0.5); // the log-log slope x V'(x) / V(x) of the game's curve where V''(x) = 0, at x = 0.120305925 - float cone = p4.z != 0.0 ? 1.3355037565264822 : 1.0; - float anchor_in = 0.18; - float anchor_out = anchor_in * gain * reference_exposure * p1.x; - return psycho35(untonemapped, peak_nits / p0.y, p1.y, p1.z, p1.w, p2.x, cone * p3.w, anchor_in, anchor_out, 0); + const float cone = 1.3355037565264822; + // scene grey where the game's curve puts it: the curve's value at 0.18 times the gain + float anchor_out = aces_fitted(vec3(0.18)).x * gain; + return psycho35(untonemapped, peak, cone, 0.18, anchor_out); } -vec3 nx_tonemap(vec3 x, vec4 scene, vec4 p0, vec4 p1, vec4 p2, vec4 p3, vec4 p4) { +vec3 nx_tonemap(vec3 x, vec4 scene, vec4 p0) { vec3 vanilla = aces_fitted(x); - if (p0.w < 0.0) return vanilla; - vec3 color = vanilla; - if (p0.w == 9.0) { - // the mapper takes the light before the gain; without a gain there is no light - color = scene.a > 0.0 ? scene_tonemap(scene.rgb / scene.a, scene.a, p0, p1, p2, p3, p4) : vec3(0.0); - } else if (p0.w > 0.0) { - vec3 graded = p3.y == 0.0 ? x : upgrade(x, neutral_sdr(x), vanilla, p3.y); - float peak = p0.x / p0.y; // the display's peak in picture whites; corrected for the present's gamma - if (p3.z != 0.0) peak = gamma_to_srgb(peak, p3.z == 1.0 ? 2.2 : 2.4); - Drt d = Drt(peak * 100.0, p1.x, p1.y, p1.z, p1.w, p2.x, p2.z, 0.10 * pow(p2.w, 10.0), p3.x, -(p2.y - 1.0), - 18.0, false); - color = drt(graded, d); - } + if (p0.w < 0.0) return vanilla; // the game's own SDR picture + // the mapper takes the light before the gain; without a gain there is no light + if (!(scene.a > 0.0)) return vec3(0.0); + float peak = gamma_to_srgb(max(p0.x, p0.y) / p0.y, 2.2); // in picture whites, before the present's correction + vec3 color = scene_tonemap(scene.rgb / scene.a, scene.a, peak); color *= p0.y / p0.z; // the picture's white against the HUD's return mix(color, vec3(0.0), isnan(color)); } -void main() { o = vec4(nx_tonemap(a0.xyz, a1, a2, a3, a4, a5, a6), 1.0); } +void main() { o = vec4(nx_tonemap(a0.xyz, a1, a2), 1.0); } diff --git a/runtime/ui/menu.cpp b/runtime/ui/menu.cpp index fb28ae41..185ef662 100644 --- a/runtime/ui/menu.cpp +++ b/runtime/ui/menu.cpp @@ -1003,48 +1003,18 @@ static const char *hdr_note(void) { return note; } -// HDR (vk_hdr.c): off or the HDR tone mapper over the game's picture, switched at once, or the game's native HDR, -// which the game takes up at its start. The brightness rows follow while it is on; the tone mapper row only with the -// first (native HDR is the game's picture as it is), then the rows of the tone mapper chosen: none for the game's -// curve, the grading rows of both others between the rows each has alone. +// HDR (vk_hdr.c): off or on, switched at once; on is the game's picture tone mapped to the display, one fixed look. The +// game's own HDR (native) is a value only the file sets: the row lists it only while the file does, so that it reads as +// what runs. The three brightness rows follow while HDR is on. static void row_hdr(void) { static const char *const values[] = {"0", "1", "native"}; - const char *names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), menu_text(UI_MENU_HDR_TONEMAPPED), menu_text(UI_MENU_HDR_NATIVE)}; - opt_choice_list("NX2NAVI_HDR", UI_MENU_HDR, values, names, 3, UI_MENU_HDR_DESC, -1, hdr_note()); + const char *names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), menu_text(UI_MENU_ON), menu_text(UI_MENU_HDR_NATIVE)}; + const int n = !strcmp(cfg_value("NX2NAVI_HDR"), "native") ? 3 : 2; + opt_choice_list("NX2NAVI_HDR", UI_MENU_HDR, values, names, n, UI_MENU_HDR_DESC, -1, hdr_note()); if (!cfg_bool("NX2NAVI_HDR")) return; opt_slider("NX2NAVI_HDR_PEAK_NITS", UI_MENU_HDR_PEAK, 400, 10000, UI_MENU_HDR_PEAK_DESC, 50, "%.0f nits"); opt_slider("NX2NAVI_HDR_GAME_NITS", UI_MENU_HDR_GAME, 48, 500, UI_MENU_HDR_GAME_DESC, 1, "%.0f nits"); opt_slider("NX2NAVI_HDR_UI_NITS", UI_MENU_HDR_UI, 48, 500, UI_MENU_HDR_UI_DESC, 1, "%.0f nits"); - if (!strcmp(cfg_value("NX2NAVI_HDR"), "native")) return; - static const char *const curves[] = {"vanilla", "psychov35", "renodrt"}; - static const char *const curve_names[] = {"Vanilla", "PsychoV35", "RenoDRT"}; - opt_choice_list("NX2NAVI_HDR_TONEMAP", UI_MENU_HDR_TONEMAP, curves, curve_names, 3, UI_MENU_HDR_TONEMAP_DESC); - const char *curve = cfg_value("NX2NAVI_HDR_TONEMAP"); - if (!strcmp(curve, "vanilla")) return; - const bool luminance = !strcmp(curve, "renodrt"); // else the observer-response mapper, as vk_hdr.c reads the setting - if (luminance) { - static const char *const gammas[] = {"off", "2.2", "2.4"}; - const char *gamma_names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), "2.2", "BT.1886 (2.4)"}; - opt_choice_list("NX2NAVI_HDR_GAMMA", UI_MENU_HDR_GAMMA, gammas, gamma_names, 3, UI_MENU_HDR_GAMMA_DESC); - } else { - static const char *const greys[] = {"render", "vanilla"}; - static const char *const grey_names[] = {"Render", "Vanilla"}; - opt_choice_list("NX2NAVI_HDR_MIDGRAY", UI_MENU_HDR_MIDGRAY, greys, grey_names, 2, UI_MENU_HDR_MIDGRAY_DESC); - opt_toggle("NX2NAVI_HDR_MATCH_SLOPE", UI_MENU_HDR_MATCH_SLOPE, UI_MENU_HDR_MATCH_SLOPE_DESC); - opt_slider("NX2NAVI_HDR_TONE_SLOPE", UI_MENU_HDR_TONE_SLOPE, 0, 100, UI_MENU_HDR_TONE_SLOPE_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_HUE_CLIP", UI_MENU_HDR_HUE_CLIP, 0, 100, UI_MENU_HDR_HUE_CLIP_DESC, 5, "%.0f %%"); - } - opt_slider("NX2NAVI_HDR_EXPOSURE", UI_MENU_HDR_EXPOSURE, 0, 200, UI_MENU_HDR_EXPOSURE_DESC, 5, "%.0f %%"); - opt_slider("NX2NAVI_HDR_HIGHLIGHTS", UI_MENU_HDR_HIGHLIGHTS, 0, 100, UI_MENU_HDR_HIGHLIGHTS_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_SHADOWS", UI_MENU_HDR_SHADOWS, 0, 100, UI_MENU_HDR_SHADOWS_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_CONTRAST", UI_MENU_HDR_CONTRAST, 0, 100, UI_MENU_HDR_CONTRAST_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_SATURATION", UI_MENU_HDR_SATURATION, 0, 100, UI_MENU_HDR_SATURATION_DESC, 1, "%.0f"); - if (!luminance) return; - opt_slider("NX2NAVI_HDR_HIGHLIGHT_SAT", UI_MENU_HDR_HIGHLIGHT_SAT, 0, 100, UI_MENU_HDR_HIGHLIGHT_SAT_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_BLOWOUT", UI_MENU_HDR_BLOWOUT, 0, 100, UI_MENU_HDR_BLOWOUT_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_FLARE", UI_MENU_HDR_FLARE, 0, 100, UI_MENU_HDR_FLARE_DESC, 1, "%.0f"); - opt_slider("NX2NAVI_HDR_HUE", UI_MENU_HDR_HUE, 0, 100, UI_MENU_HDR_HUE_DESC, 5, "%.0f %%"); - opt_slider("NX2NAVI_HDR_GRADE", UI_MENU_HDR_GRADE, 0, 100, UI_MENU_HDR_GRADE_DESC, 5, "%.0f %%"); } // DLSS frame generation's multiplier (NX2NAVI_FRAMEGEN_MULTIPLIER, vk_dlssg.c, at once): 2x, 3x or 4x as far as this @@ -1891,7 +1861,7 @@ static void perf_picture(const MenuTarget *t, PerfPanel *p) { else snprintf(v[7], sizeof v[7], "%sp", r); } const char *hdr = cfg_value("NX2NAVI_HDR"); - snprintf(v[8], sizeof v[8], "%s", menu_text(!strcmp(hdr, "native") ? UI_MENU_HDR_NATIVE : cfg_bool("NX2NAVI_HDR") ? UI_MENU_HDR_TONEMAPPED : UI_MENU_FRAMEGEN_OFF)); + snprintf(v[8], sizeof v[8], "%s", menu_text(!strcmp(hdr, "native") ? UI_MENU_HDR_NATIVE : cfg_bool("NX2NAVI_HDR") ? UI_MENU_ON : UI_MENU_FRAMEGEN_OFF)); const char *fg = cfg_value("NX2NAVI_FRAMEGEN"); const bool fsr_fg = !strcmp(fg, "fsr3") && vk_ffx_framegen_available(); // set but not loadable: off snprintf(v[9], sizeof v[9], "%s", fsr_fg ? (vk_fg4_active() ? fg4_label() : "AMD FSR 3.1") diff --git a/runtime/ui_text.c b/runtime/ui_text.c index f442b227..6fe23853 100644 --- a/runtime/ui_text.c +++ b/runtime/ui_text.c @@ -116,44 +116,6 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_MENU_HDR_GAME_DESC] = "Brightness of white in the game, in nits.", [UI_MENU_HDR_UI] = "HUD and menu brightness", [UI_MENU_HDR_UI_DESC] = "Brightness of the HUD and this menu, in nits.", - [UI_MENU_HDR_HUE] = "Hue correction", - [UI_MENU_HDR_HUE_DESC] = "How much bright colours keep the hue they have before the tone mapper; 0 lets them shift " - "as the curve takes them.", - [UI_MENU_HDR_GAMMA] = "Gamma correction", - [UI_MENU_HDR_GAMMA_DESC] = "Shows the picture as a 2.2 or BT.1886 (2.4) display would, as the game was made to be " - "seen; Off keeps the sRGB curve, with lighter shadows.", - [UI_MENU_HDR_TONEMAP] = "Tone mapper", - [UI_MENU_HDR_TONEMAP_DESC] = "Vanilla: the game's own curve at SDR brightness. PsychoV35: the scene through a model " - "of the eye's response, matched to the game's mid grey and tone, up to your display's " - "peak. RenoDRT: the game's SDR look carried up to the peak by brightness.", - [UI_MENU_HDR_EXPOSURE] = "Exposure", - [UI_MENU_HDR_EXPOSURE_DESC] = "Brightness of the whole picture before the tone mapper; 100 % leaves it unchanged.", - [UI_MENU_HDR_HIGHLIGHTS] = "Highlights", - [UI_MENU_HDR_HIGHLIGHTS_DESC] = "Brightness of the bright parts; 50 leaves them unchanged.", - [UI_MENU_HDR_SHADOWS] = "Shadows", - [UI_MENU_HDR_SHADOWS_DESC] = "Brightness of the dark parts; 50 leaves them unchanged.", - [UI_MENU_HDR_CONTRAST] = "Contrast", - [UI_MENU_HDR_CONTRAST_DESC] = "Contrast around mid grey; 50 leaves it unchanged.", - [UI_MENU_HDR_SATURATION] = "Saturation", - [UI_MENU_HDR_SATURATION_DESC] = "Strength of all colours; 50 leaves it unchanged.", - [UI_MENU_HDR_HIGHLIGHT_SAT] = "Highlight saturation", - [UI_MENU_HDR_HIGHLIGHT_SAT_DESC] = "Strength of the colours of the brightest parts; 50 leaves it unchanged.", - [UI_MENU_HDR_BLOWOUT] = "Blowout", - [UI_MENU_HDR_BLOWOUT_DESC] = "Turns the brightest parts towards white; 0 keeps their colour.", - [UI_MENU_HDR_FLARE] = "Flare", - [UI_MENU_HDR_FLARE_DESC] = "Lifts the darkest parts as a little stray light would; 0 keeps black black.", - [UI_MENU_HDR_GRADE] = "Colour grading", - [UI_MENU_HDR_GRADE_DESC] = "How much of the game's own colour grading the HDR picture keeps; 0 is the scene without it.", - [UI_MENU_HDR_MIDGRAY] = "Mid grey", - [UI_MENU_HDR_MIDGRAY_DESC] = "Where the scene's mid grey lands. Vanilla: where the game's own curve puts it. Render: " - "at the scene's value, a little darker.", - [UI_MENU_HDR_MATCH_SLOPE] = "Match tone slope", - [UI_MENU_HDR_MATCH_SLOPE_DESC] = "Gives the mid tones the contrast of the game's own curve.", - [UI_MENU_HDR_TONE_SLOPE] = "Tone slope", - [UI_MENU_HDR_TONE_SLOPE_DESC] = "Steepness of the tone curve through mid grey; 50 leaves it unchanged.", - [UI_MENU_HDR_HUE_CLIP] = "Hue clip emulation", - [UI_MENU_HDR_HUE_CLIP_DESC] = "How much bright colours turn as they do where the game's SDR picture clips: fire " - "towards yellow and white. 0 keeps their hue.", [UI_MENU_SHADOW_SCALE] = "Shadow resolution", [UI_MENU_SHADOW_SCALE_DESC] = "Sharper shadows. Higher costs more GPU power.", [UI_MENU_PCSS] = "Soft shadows", @@ -705,7 +667,7 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_MENU_FRAMEGEN] = "Frame generation", [UI_MENU_FRAMEGEN_DESC] = "Adds generated frames for smoother motion. Needs DLSS or FSR 3.1.", [UI_MENU_FRAMEGEN_OFF] = "Off", - [UI_MENU_HDR_TONEMAPPED] = "RenoDX", [UI_MENU_HDR_NATIVE] = "Native (the game's)", + [UI_MENU_HDR_NATIVE] = "Native (the game's)", [UI_MENU_FRAMEGEN_NOTE] = "needs DLSS or FSR 3.1", [UI_MENU_FRAMEGEN_NEEDS] = "choose a DLSS or FSR 3.1 mode under Upscaling first", [UI_MENU_FRAMEGEN_NEEDS_FSR] = "choose an FSR 3.1 mode under Upscaling first", @@ -833,44 +795,6 @@ static const char *const text_ru[UI_TEXT_COUNT] = { [UI_MENU_HDR_GAME_DESC] = "Яркость белого в игре, в нитах.", [UI_MENU_HDR_UI] = "Яркость интерфейса и меню", [UI_MENU_HDR_UI_DESC] = "Яркость интерфейса и этого меню, в нитах.", - [UI_MENU_HDR_HUE] = "Коррекция оттенка", - [UI_MENU_HDR_HUE_DESC] = "Насколько яркие цвета сохраняют оттенок, который был до тональной кривой; при 0 они " - "смещаются вместе с кривой.", - [UI_MENU_HDR_GAMMA] = "Коррекция гаммы", - [UI_MENU_HDR_GAMMA_DESC] = "Показывает картинку так, как её показал бы экран с гаммой 2.2 или BT.1886 (2.4), для " - "которого игра делалась; «Выкл.» оставляет кривую sRGB с более светлыми тенями.", - [UI_MENU_HDR_TONEMAP] = "Тональная кривая", - [UI_MENU_HDR_TONEMAP_DESC] = "Vanilla: собственная кривая игры с яркостью SDR. PsychoV35: сцена через модель отклика " - "глаза, согласованная со средним серым и тоном игры, до пиковой яркости экрана. RenoDRT: " - "SDR-вид игры, доведённый до пика по яркости.", - [UI_MENU_HDR_EXPOSURE] = "Экспозиция", - [UI_MENU_HDR_EXPOSURE_DESC] = "Яркость всей картинки до тональной кривой; 100 % оставляет её без изменений.", - [UI_MENU_HDR_HIGHLIGHTS] = "Света", - [UI_MENU_HDR_HIGHLIGHTS_DESC] = "Яркость светлых участков; 50 оставляет их без изменений.", - [UI_MENU_HDR_SHADOWS] = "Тени", - [UI_MENU_HDR_SHADOWS_DESC] = "Яркость тёмных участков; 50 оставляет их без изменений.", - [UI_MENU_HDR_CONTRAST] = "Контраст", - [UI_MENU_HDR_CONTRAST_DESC] = "Контраст вокруг среднего серого; 50 оставляет его без изменений.", - [UI_MENU_HDR_SATURATION] = "Насыщенность", - [UI_MENU_HDR_SATURATION_DESC] = "Сила всех цветов; 50 оставляет её без изменений.", - [UI_MENU_HDR_HIGHLIGHT_SAT] = "Насыщенность светов", - [UI_MENU_HDR_HIGHLIGHT_SAT_DESC] = "Сила цвета самых ярких участков; 50 оставляет её без изменений.", - [UI_MENU_HDR_BLOWOUT] = "Выгорание", - [UI_MENU_HDR_BLOWOUT_DESC] = "Уводит самые яркие участки к белому; 0 сохраняет их цвет.", - [UI_MENU_HDR_FLARE] = "Засветка", - [UI_MENU_HDR_FLARE_DESC] = "Приподнимает самые тёмные участки, как немного рассеянного света; 0 оставляет чёрное чёрным.", - [UI_MENU_HDR_GRADE] = "Цветокоррекция игры", - [UI_MENU_HDR_GRADE_DESC] = "Сколько собственной цветокоррекции игры сохраняет HDR-картинка; 0 — сцена без неё.", - [UI_MENU_HDR_MIDGRAY] = "Средний серый", - [UI_MENU_HDR_MIDGRAY_DESC] = "Куда попадает средний серый сцены. Vanilla: туда же, куда его ставит кривая игры. " - "Render: на значение сцены, чуть темнее.", - [UI_MENU_HDR_MATCH_SLOPE] = "Наклон тона как в игре", - [UI_MENU_HDR_MATCH_SLOPE_DESC] = "Даёт средним тонам контраст собственной кривой игры.", - [UI_MENU_HDR_TONE_SLOPE] = "Наклон тона", - [UI_MENU_HDR_TONE_SLOPE_DESC] = "Крутизна тональной кривой у среднего серого; 50 оставляет её без изменений.", - [UI_MENU_HDR_HUE_CLIP] = "Имитация сдвига оттенка", - [UI_MENU_HDR_HUE_CLIP_DESC] = "Насколько яркие цвета сдвигаются так, как при обрезке в SDR-картинке игры: огонь к " - "жёлтому и белому. 0 сохраняет их оттенок.", [UI_MENU_SHADOW_SCALE] = "Разрешение теней", [UI_MENU_SHADOW_SCALE_DESC] = "Более чёткие тени. Выше — больше нагрузка на видеокарту.", [UI_MENU_PCSS] = "Мягкие тени", @@ -1848,44 +1772,6 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = { [UI_MENU_WALK_SCALE_DESC] = "A inclinação dos analógicos do teclado enquanto a tecla de andar está segura. Metade " "é andar, a inclinação total é correr.", [UI_MENU_OV_SCALE_DOWN] = "bilinear (nitidez FSR 0)", - [UI_MENU_HDR_HUE] = "Correção de matiz", - [UI_MENU_HDR_HUE_DESC] = "Quanto as cores brilhantes mantêm o matiz que tinham antes da curva de tons; 0 deixa que " - "mudem com a curva.", - [UI_MENU_HDR_GAMMA] = "Correção de gama", - [UI_MENU_HDR_GAMMA_DESC] = "Mostra a imagem como uma tela de gama 2.2 ou BT.1886 (2.4) mostraria, como o jogo foi " - "feito para ser visto; Desligado mantém a curva sRGB, com sombras mais claras.", - [UI_MENU_HDR_TONEMAP] = "Curva de tons", - [UI_MENU_HDR_TONEMAP_DESC] = "Vanilla: a curva do próprio jogo, com brilho SDR. PsychoV35: a cena por um modelo da " - "resposta do olho, ajustada ao cinza médio e ao tom do jogo, até o pico da sua tela. " - "RenoDRT: o visual SDR do jogo levado até o pico pelo brilho.", - [UI_MENU_HDR_EXPOSURE] = "Exposição", - [UI_MENU_HDR_EXPOSURE_DESC] = "Brilho de toda a imagem antes da curva de tons; 100 % a deixa como está.", - [UI_MENU_HDR_HIGHLIGHTS] = "Realces", - [UI_MENU_HDR_HIGHLIGHTS_DESC] = "Brilho das partes claras; 50 as deixa como estão.", - [UI_MENU_HDR_SHADOWS] = "Sombras", - [UI_MENU_HDR_SHADOWS_DESC] = "Brilho das partes escuras; 50 as deixa como estão.", - [UI_MENU_HDR_CONTRAST] = "Contraste", - [UI_MENU_HDR_CONTRAST_DESC] = "Contraste em torno do cinza médio; 50 o deixa como está.", - [UI_MENU_HDR_SATURATION] = "Saturação", - [UI_MENU_HDR_SATURATION_DESC] = "Força de todas as cores; 50 a deixa como está.", - [UI_MENU_HDR_HIGHLIGHT_SAT] = "Saturação dos realces", - [UI_MENU_HDR_HIGHLIGHT_SAT_DESC] = "Força das cores das partes mais claras; 50 a deixa como está.", - [UI_MENU_HDR_BLOWOUT] = "Estouro", - [UI_MENU_HDR_BLOWOUT_DESC] = "Leva as partes mais claras para o branco; 0 mantém a cor delas.", - [UI_MENU_HDR_FLARE] = "Flare", - [UI_MENU_HDR_FLARE_DESC] = "Ergue as partes mais escuras como um pouco de luz difusa faria; 0 mantém o preto preto.", - [UI_MENU_HDR_GRADE] = "Gradação de cor", - [UI_MENU_HDR_GRADE_DESC] = "Quanto da gradação de cor do próprio jogo a imagem HDR mantém; 0 é a cena sem ela.", - [UI_MENU_HDR_MIDGRAY] = "Cinza médio", - [UI_MENU_HDR_MIDGRAY_DESC] = "Onde fica o cinza médio da cena. Vanilla: onde a curva do próprio jogo o coloca. " - "Render: no valor da cena, um pouco mais escuro.", - [UI_MENU_HDR_MATCH_SLOPE] = "Igualar inclinação do tom", - [UI_MENU_HDR_MATCH_SLOPE_DESC] = "Dá aos tons médios o contraste da curva do próprio jogo.", - [UI_MENU_HDR_TONE_SLOPE] = "Inclinação do tom", - [UI_MENU_HDR_TONE_SLOPE_DESC] = "Inclinação da curva de tons no cinza médio; 50 a deixa como está.", - [UI_MENU_HDR_HUE_CLIP] = "Emulação do corte de matiz", - [UI_MENU_HDR_HUE_CLIP_DESC] = "Quanto as cores brilhantes viram como onde a imagem SDR do jogo estoura: o fogo " - "para o amarelo e o branco. 0 mantém o matiz delas.", [UI_MENU_SHADOW_SCALE] = "Resolução das sombras", [UI_MENU_SHADOW_SCALE_DESC] = "Sombras mais nítidas. Mais alto exige mais da placa de vídeo.", [UI_MENU_SHADOW_RANGE] = "Alcance das cascatas de sombra", @@ -2663,38 +2549,6 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = { [UI_MENU_WALK_SCALE] = "步行幅度", [UI_MENU_WALK_SCALE_DESC] = "按住步行键时键盘摇杆的推动幅度。一半为走路,推满为奔跑。", [UI_MENU_OV_SCALE_DOWN] = "双线性(FSR 锐度 0)", - [UI_MENU_HDR_HUE] = "色调校正", - [UI_MENU_HDR_HUE_DESC] = "明亮色彩保留色调映射前色调的程度;0 让它们随曲线偏移。", - [UI_MENU_HDR_GAMMA] = "伽马校正", - [UI_MENU_HDR_GAMMA_DESC] = "按游戏设计时的观看方式,以 2.2 或 BT.1886(2.4)显示器的效果呈现画面;关闭则保留 sRGB 曲线,暗部更亮。", - [UI_MENU_HDR_TONEMAP] = "色调映射", - [UI_MENU_HDR_TONEMAP_DESC] = "Vanilla:游戏自身的曲线,SDR 亮度。PsychoV35:场景经过人眼响应模型,与游戏的中灰和影调对齐,直到显示器的峰值亮度。RenoDRT:按亮度把游戏的 SDR 观感延伸到峰值。", - [UI_MENU_HDR_EXPOSURE] = "曝光", - [UI_MENU_HDR_EXPOSURE_DESC] = "色调映射前整个画面的亮度;100 % 保持不变。", - [UI_MENU_HDR_HIGHLIGHTS] = "高光", - [UI_MENU_HDR_HIGHLIGHTS_DESC] = "明亮部分的亮度;50 保持不变。", - [UI_MENU_HDR_SHADOWS] = "阴影", - [UI_MENU_HDR_SHADOWS_DESC] = "暗部的亮度;50 保持不变。", - [UI_MENU_HDR_CONTRAST] = "对比度", - [UI_MENU_HDR_CONTRAST_DESC] = "中灰周围的对比度;50 保持不变。", - [UI_MENU_HDR_SATURATION] = "饱和度", - [UI_MENU_HDR_SATURATION_DESC] = "所有色彩的强度;50 保持不变。", - [UI_MENU_HDR_HIGHLIGHT_SAT] = "高光饱和度", - [UI_MENU_HDR_HIGHLIGHT_SAT_DESC] = "最亮部分的色彩强度;50 保持不变。", - [UI_MENU_HDR_BLOWOUT] = "过曝", - [UI_MENU_HDR_BLOWOUT_DESC] = "让最亮的部分趋向白色;0 保留其颜色。", - [UI_MENU_HDR_FLARE] = "眩光", - [UI_MENU_HDR_FLARE_DESC] = "像少量杂散光一样提亮最暗的部分;0 保持纯黑。", - [UI_MENU_HDR_GRADE] = "调色", - [UI_MENU_HDR_GRADE_DESC] = "HDR 画面保留游戏自身调色的程度;0 为不含调色的场景。", - [UI_MENU_HDR_MIDGRAY] = "中灰", - [UI_MENU_HDR_MIDGRAY_DESC] = "场景中灰落在哪里。Vanilla:游戏自身曲线给出的位置。Render:场景的原值,稍暗。", - [UI_MENU_HDR_MATCH_SLOPE] = "匹配影调斜率", - [UI_MENU_HDR_MATCH_SLOPE_DESC] = "让中间调具有游戏自身曲线的对比度。", - [UI_MENU_HDR_TONE_SLOPE] = "影调斜率", - [UI_MENU_HDR_TONE_SLOPE_DESC] = "影调曲线在中灰处的陡峭程度;50 保持不变。", - [UI_MENU_HDR_HUE_CLIP] = "色调裁切模拟", - [UI_MENU_HDR_HUE_CLIP_DESC] = "明亮色彩像游戏 SDR 画面裁切处那样偏转的程度:火焰偏向黄色和白色。0 保持其色调。", [UI_MENU_SHADOW_SCALE] = "阴影分辨率", [UI_MENU_SHADOW_SCALE_DESC] = "让阴影更清晰。越高越吃显卡。", [UI_MENU_SHADOW_RANGE] = "阴影级联范围", diff --git a/runtime/ui_text.h b/runtime/ui_text.h index e774f526..8d66d9a7 100644 --- a/runtime/ui_text.h +++ b/runtime/ui_text.h @@ -53,16 +53,7 @@ typedef enum { UI_MENU_COLOR_SATURATION, UI_MENU_COLOR_SATURATION_DESC, UI_MENU_COLOR_GAMMA, UI_MENU_COLOR_GAMMA_DESC, UI_MENU_HDR_GROUP, // group heading over the HDR rows UI_MENU_HDR, UI_MENU_HDR_DESC, UI_MENU_HDR_PEAK, UI_MENU_HDR_PEAK_DESC, UI_MENU_HDR_GAME, UI_MENU_HDR_GAME_DESC, - UI_MENU_HDR_UI, UI_MENU_HDR_UI_DESC, UI_MENU_HDR_HUE, UI_MENU_HDR_HUE_DESC, - UI_MENU_HDR_GAMMA, UI_MENU_HDR_GAMMA_DESC, UI_MENU_HDR_TONEMAP, UI_MENU_HDR_TONEMAP_DESC, - UI_MENU_HDR_EXPOSURE, UI_MENU_HDR_EXPOSURE_DESC, UI_MENU_HDR_HIGHLIGHTS, UI_MENU_HDR_HIGHLIGHTS_DESC, - UI_MENU_HDR_SHADOWS, UI_MENU_HDR_SHADOWS_DESC, UI_MENU_HDR_CONTRAST, UI_MENU_HDR_CONTRAST_DESC, - UI_MENU_HDR_SATURATION, UI_MENU_HDR_SATURATION_DESC, UI_MENU_HDR_HIGHLIGHT_SAT, UI_MENU_HDR_HIGHLIGHT_SAT_DESC, - UI_MENU_HDR_BLOWOUT, UI_MENU_HDR_BLOWOUT_DESC, UI_MENU_HDR_FLARE, UI_MENU_HDR_FLARE_DESC, - UI_MENU_HDR_GRADE, UI_MENU_HDR_GRADE_DESC, - UI_MENU_HDR_MIDGRAY, UI_MENU_HDR_MIDGRAY_DESC, UI_MENU_HDR_MATCH_SLOPE, UI_MENU_HDR_MATCH_SLOPE_DESC, - UI_MENU_HDR_TONE_SLOPE, UI_MENU_HDR_TONE_SLOPE_DESC, UI_MENU_HDR_HUE_CLIP, UI_MENU_HDR_HUE_CLIP_DESC, - UI_MENU_SHADOW_SCALE, UI_MENU_SHADOW_SCALE_DESC, + UI_MENU_HDR_UI, UI_MENU_HDR_UI_DESC, 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, @@ -320,7 +311,7 @@ typedef enum { // the settings menu's Picture rows for this game (ui/menu.cpp tab_screen): one upscaling choice (bilinear, FSR 1 // or a DLSS mode) and the picture's resolution UI_MENU_UPSCALING, UI_MENU_UPSCALING_DESC, UI_MENU_FRAMEGEN, UI_MENU_FRAMEGEN_DESC, - UI_MENU_FRAMEGEN_OFF, UI_MENU_FRAMEGEN_NOTE, UI_MENU_FRAMEGEN_NEEDS, UI_MENU_HDR_TONEMAPPED, UI_MENU_HDR_NATIVE, + UI_MENU_FRAMEGEN_OFF, UI_MENU_FRAMEGEN_NOTE, UI_MENU_FRAMEGEN_NEEDS, UI_MENU_HDR_NATIVE, UI_MENU_GAME_RES, UI_MENU_GAME_RES_DESC, UI_MENU_GAME_RES_NATIVE, // the same rows' texts on a GPU DLSS does not run on (not NVIDIA): the menu leaves DLSS out there UI_MENU_UPSCALING_DESC_FSR, UI_MENU_FRAMEGEN_DESC_FSR, UI_MENU_FRAMEGEN_NOTE_FSR, diff --git a/runtime/vk.h b/runtime/vk.h index 9bf48181..e2940910 100644 --- a/runtime/vk.h +++ b/runtime/vk.h @@ -211,9 +211,9 @@ void vk_hdr_to_srgb8(const uint8_t *in, uint8_t *out, size_t px, int a2r); // percentiles and the brightest pixel in nits, and the share of pixels under 0.01 nits (vk_hdr.c). void vk_hdr_luminance_stats(const uint8_t *in, int stride, const int rect[4], int a2r, float out[7]); void vk_hdr_release(void); // before the device goes away -// The support buffer's HDR settings, five vec4 at 1440 (vk_exec_draw.c support_fill; read by the game's tonemap with +// The support buffer's HDR settings, a vec4 at 1440 (vk_exec_draw.c support_fill; read by the game's tonemap with // shaders/hdr_tonemap.frag spliced in, vk_hdr.c tonemap_splice). -void vk_hdr_support(float out[20]); +void vk_hdr_support(float out[4]); // vk_contact.c: screen-space contact shadows on the game's composed scene colour (NX2NAVI_CONTACT_SHADOWS) int vk_contact_strength(void); // percent, 0 = off void vk_contact_release(void); // before the device goes away diff --git a/runtime/vk_exec_draw.c b/runtime/vk_exec_draw.c index 7ba76997..fa20d2c8 100644 --- a/runtime/vk_exec_draw.c +++ b/runtime/vk_exec_draw.c @@ -38,8 +38,8 @@ #define MAX_DESC 1024 #define CHUNK_BYTES (4u << 20) #define ZERO_BYTES 65536u -#define SUPPORT_BYTES 1520u // the translator's SupportBuffer (std140): alpha_test, is_bgra[8], viewport_inverse, viewport_size, ...; - // 1408 pcss and 1424 shadow_spread (translator patches 0010, 0011), 1440..1519 HDR (support_fill) +#define SUPPORT_BYTES 1456u // the translator's SupportBuffer (std140): alpha_test, is_bgra[8], viewport_inverse, viewport_size, ...; + // 1408 pcss and 1424 shadow_spread (translator patches 0010, 0011), 1440..1455 HDR (support_fill) #define NVN_STAGE_VS 0 #define NVN_STAGE_FS 1 #define NVN_STAGE_CS 5 @@ -2863,7 +2863,7 @@ static void support_fill(uint8_t *b, float w, float h, float point_size) { 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 if (vk_hdr_armed()) { // read by the game's tonemap only, once vk_hdr.c tonemap_splice has grown its block past the translator's - float hdr[20]; + float hdr[4]; vk_hdr_support(hdr); memcpy(b + 1440, hdr, sizeof hdr); } diff --git a/runtime/vk_hdr.c b/runtime/vk_hdr.c index 8138435d..6063922d 100644 --- a/runtime/vk_hdr.c +++ b/runtime/vk_hdr.c @@ -1,12 +1,10 @@ // HDR output (NX2NAVI_HDR=1): an HDR10 swapchain (Rec. 2020 primaries, PQ) and a game picture tone mapped to the -// display's range. Settings: NX2NAVI_HDR, off and the tone mapper following the menu during a run (vk_hdr_wanted), the -// game's own HDR (native) from the start only; the nits NX2NAVI_HDR_PEAK_NITS (the display's peak), -// NX2NAVI_HDR_GAME_NITS (the picture's white, "paper white") and NX2NAVI_HDR_UI_NITS (the HUD's and this menu's white); -// NX2NAVI_HDR_TONEMAP with what each tone mapper takes (NX2NAVI_HDR_MIDGRAY, NX2NAVI_HDR_MATCH_SLOPE, -// NX2NAVI_HDR_TONE_SLOPE and NX2NAVI_HDR_HUE_CLIP, or NX2NAVI_HDR_GAMMA and NX2NAVI_HDR_HIGHLIGHT_SAT .. -// NX2NAVI_HDR_GRADE) and the grading sliders NX2NAVI_HDR_EXPOSURE .. NX2NAVI_HDR_SATURATION of both, all read -// when the settings change (vk_hdr_support). With NX2NAVI_HDR=0 the swapchain is SDR, the spliced tonemap gives the -// game's own SDR picture (vk_hdr_support, p0.w = -1) and the present is what it was. +// display's range, one fixed look (shaders/hdr_tonemap.frag says which). Settings: NX2NAVI_HDR, on and off following +// the menu during a run (vk_hdr_wanted), the game's own HDR (native, a value of the file only) from the start only; and +// the nits NX2NAVI_HDR_PEAK_NITS (the display's peak), NX2NAVI_HDR_GAME_NITS (the picture's white, "paper white") and +// NX2NAVI_HDR_UI_NITS (the HUD's and this menu's white), read when the settings change (vk_hdr_support). With +// NX2NAVI_HDR=0 the swapchain is SDR, the spliced tonemap gives the game's own SDR picture (vk_hdr_support, p0.w = -1) +// and the present is what it was. // // The game's picture (docs/architecture.md, "HDR"). The game tonemaps its scene into an r11g11b10f target, upscales it // and draws the HUD into a second one (float, nothing in these passes clamps), and one last draw copies that into the @@ -14,9 +12,8 @@ // 1. vk_hdr_shader_swap, on each fragment shader as it loads (in memory only; the cache keeps the translator's): 1.1.1's // tonemap ends in the fitted ACES curve and a clamp to 0..1 per channel. tonemap_splice links the function // nx_tonemap of shaders/hdr_tonemap.frag into that shader and stores its result in place of the three clamped -// channels: the scene mapped onto the display by the tone mapper chosen (that file says how each does it), from the -// values the curve takes and the scene before the game's exposure gain, with the settings in five vec4 the support -// buffer gains at 1440. Recognised by the curve's constants, not by a hash, so +// channels: the scene mapped onto the display (that file says how), from the values the curve takes and the scene +// before the game's exposure gain, with the settings in a vec4 the support buffer gains at 1440. Recognised by the curve's constants, not by a hash, so // another translator still finds it. The game's FSR 1 upscale after it would clamp the picture to 1 again; // fsr_unclamp drops that clamp in its SPIR-V. // 2. vk_hdr_draw / vk_hdr_present_source: a draw into a presented window texture that samples one float texture of @@ -103,10 +100,17 @@ void vk_hdr_off(void) { g_native = 0, g_armed = 0; } // The settings, read again when they change. The replay (every draw's support buffer) and the present thread read // them: an update fills the snapshot not in use and then publishes it. +// The game's picture is graded for a display of gamma about 2.2, and its sRGB encode is linear near black: shown as +// that encode decodes, its darkest tones sit up to a hundred times above where such a display puts them (a scene +// value of 0.001, 0.08 nits at a white of 350 nits against 0.001), the floor of a dark scene lifted to grey. So the +// present shows the picture as a 2.2 display would (hdr_encode.comp, hdr_ui.frag: the power 2.2 of the sRGB code), +// and the tonemap aims its peak below the display's by that correction's inverse (hdr_tonemap.frag nx_tonemap). +#define HDR_GAMMA 1.0f // the present's correction: 0 none, 1: 2.2, 2: 2.4 + typedef struct { float peak, game, ui; // nits - float gamma; // 0 none, 1: 2.2, 2: 2.4 (the present's correction, hdr_encode.comp) - float tm[20]; // the support buffer's five vec4 (shaders/hdr_tonemap.frag p0..p4) + float gamma; // HDR_GAMMA (hdr_encode.comp, hdr_ui.frag) + float tm[4]; // the support buffer's vec4 (shaders/hdr_tonemap.frag p0) } Nits; static struct { unsigned seen; int cur; Nits snap[2]; PlatMutex lock; } N = {.seen = ~0u, .lock = PLAT_MUTEX_INIT}; @@ -121,32 +125,8 @@ static Nits nits(void) { n->peak = clampf((float)cfg_double("NX2NAVI_HDR_PEAK_NITS"), 80.0f, 10000.0f); n->game = clampf((float)cfg_double("NX2NAVI_HDR_GAME_NITS"), 48.0f, 500.0f); n->ui = clampf((float)cfg_double("NX2NAVI_HDR_UI_NITS"), 48.0f, 500.0f); - // the tone mapper as shaders/hdr_tonemap.frag numbers it: the game's curve, the luminance mapper, the - // observer-response mapper - const char *tm = cfg_value("NX2NAVI_HDR_TONEMAP"); - float mapper = !strcmp(tm, "vanilla") ? 0.0f : !strcmp(tm, "renodrt") ? 3.0f : 9.0f; - // the gamma correction belongs to the luminance mapper: the other two give the picture as it is encoded - const char *g = cfg_value("NX2NAVI_HDR_GAMMA"); - n->gamma = mapper != 3.0f ? 0.0f : !strcmp(g, "2.2") ? 1.0f : !strcmp(g, "2.4") ? 2.0f : 0.0f; - // the sliders' 0..100 with 50 unchanged become factors around 1; blowout, hue and hue clip are fractions - float pct[10]; - static const char *const keys[10] = {"NX2NAVI_HDR_HIGHLIGHTS", "NX2NAVI_HDR_SHADOWS", "NX2NAVI_HDR_CONTRAST", - "NX2NAVI_HDR_SATURATION", "NX2NAVI_HDR_HIGHLIGHT_SAT", "NX2NAVI_HDR_BLOWOUT", - "NX2NAVI_HDR_FLARE", "NX2NAVI_HDR_HUE", "NX2NAVI_HDR_TONE_SLOPE", - "NX2NAVI_HDR_HUE_CLIP"}; - for (int i = 0; i < 10; i++) pct[i] = clampf((float)cfg_double(keys[i]), 0.0f, 100.0f); - float *t = n->tm; - t[0] = n->peak, t[1] = n->game, t[2] = n->ui; - t[3] = mapper; - t[4] = clampf((float)cfg_double("NX2NAVI_HDR_EXPOSURE"), 0.0f, 200.0f) / 100.0f; - t[5] = pct[0] * 0.02f, t[6] = pct[1] * 0.02f, t[7] = pct[2] * 0.02f; - t[8] = pct[3] * 0.02f, t[9] = pct[4] * 0.02f, t[10] = pct[5] * 0.01f, t[11] = pct[6] * 0.02f; - t[12] = pct[7] * 0.01f; - t[13] = clampf((float)cfg_double("NX2NAVI_HDR_GRADE"), 0.0f, 100.0f) / 100.0f; - t[14] = n->gamma, t[15] = pct[8] * 0.02f; - t[16] = pct[9] * 0.01f; - t[17] = !strcmp(cfg_value("NX2NAVI_HDR_MIDGRAY"), "render") ? 0.0f : 1.0f; - t[18] = cfg_bool("NX2NAVI_HDR_MATCH_SLOPE") ? 1.0f : 0.0f, t[19] = 0; + n->gamma = HDR_GAMMA; + n->tm[0] = n->peak, n->tm[1] = n->game, n->tm[2] = n->ui, n->tm[3] = 1.0f; __atomic_store_n(&N.cur, !N.cur, __ATOMIC_RELEASE); __atomic_store_n(&N.seen, v, __ATOMIC_RELEASE); } @@ -155,7 +135,7 @@ static Nits nits(void) { return N.snap[__atomic_load_n(&N.cur, __ATOMIC_ACQUIRE)]; } -void vk_hdr_support(float out[20]) { +void vk_hdr_support(float out[4]) { Nits n = nits(); memcpy(out, n.tm, sizeof n.tm); if (!vk_hdr_active()) out[3] = -1.0f; // no HDR10 swapchain (yet): the game's own SDR picture @@ -440,7 +420,7 @@ static int spv_top_level(const uint32_t *w, uint64_t first_label, uint64_t end, // translator's SupportBuffer ends there). The library's types are matched to the game's where the same (SPIR-V allows // one declaration of each), its constants and functions appended with new ids. Gives up (0) on any shape it does not // expect, and the shader stays as it was. -enum { HDR_SETS = 5, HDR_PARAMS = HDR_SETS + 2 }; // nx_tonemap(vec3 x, vec4 scene, vec4 settings[HDR_SETS]) +enum { HDR_SETS = 1, HDR_PARAMS = HDR_SETS + 2 }; // nx_tonemap(vec3 x, vec4 scene, vec4 settings[HDR_SETS]) static int tonemap_splice(uint32_t **spirv, uint64_t *words) { uint32_t *w = *spirv; uint64_t n = *words; diff --git a/tools/menu_preview.cpp b/tools/menu_preview.cpp index 187de365..500cab6b 100644 --- a/tools/menu_preview.cpp +++ b/tools/menu_preview.cpp @@ -263,7 +263,7 @@ static void scene_overlay(float w, float h, float scale) { snprintf(p.picture[5], 64, "%s", menu_text(UI_MENU_ON)); snprintf(p.picture[6], 64, "21:9"); snprintf(p.picture[7], 64, "1440p"); - snprintf(p.picture[8], 64, "%s", menu_text(UI_MENU_HDR_TONEMAPPED)); + snprintf(p.picture[8], 64, "%s", menu_text(UI_MENU_ON)); snprintf(p.picture[9], 64, "%s", menu_text(UI_MENU_FRAMEGEN_OFF)); p.scene_known = false, p.fg_fps = 60, p.under_menu = false; perf_panel_draw(&p, w, scale);