From cb131e896fb22c5e616d43fe51fd23239273a44e Mon Sep 17 00:00:00 2001 From: ClaudioVarandas Date: Thu, 10 Sep 2026 23:14:21 +0100 Subject: [PATCH] Add shader presets and a functional screenshot hotkey Shaders: bundle CRT and Scanlines presets (3 strength tiers each) for the existing GLSL preset loader (src/glsl_shader.c), which had zero presets shipped. GameInfo.has_shader now enables the launcher's shader dropdown (previously wired but never turned on for SM), and CMakeLists.txt gains a robust asset-staging target that removes+recopies assets/shaders/ so renamed/deleted sources never leave stale duplicates behind. Raw .glsl sources live in a shaders/.src/ subdirectory (dot-prefixed, not underscore) so the launcher's "private path" filter hides them from the picker without colliding with this repo's root _* scratch-file gitignore rule. Also closes the loop the launcher's Settings screen needed to actually persist a shader choice: the selected path/backend now round-trips through RecompLauncherCSettings.shader_path into g_config.shader (backed by static storage, since g_config.shader normally points into the parsed config.ini buffer) and gets written back via WriteConfigFile's OutputMethod and Shader keys, which weren't in its persisted-fields table before. Screenshot: new Screenshot hotkey (F12 by default), OpenGL backend only (matches the existing shader limitation and warns the same way). Captures the final composited framebuffer via glReadPixels -- includes the custom widescreen renderer and any active GLSL shader, not just the native SNES raster -- and writes a timestamped BMP to screenshots/. A 0.5s debounce guards against SDL's key auto-repeat (the input loop doesn't check event.key.repeat), and a brief time-based white flash gives feedback in fullscreen where the terminal isn't visible. Verified: ctest 5/5 on both Debug and Release builds; manual launcher round-trip (shader selection persists and renders, F12 saves + flashes); confirmed via sm_render_capture + a game_state trace that stock-fallback during the attract demo (state 30, outside sm_renderer.c's room_state() allowlist) is a pre-existing, documented renderer limitation unrelated to this change -- widescreen renders correctly once in real gameplay. --- CMakeLists.txt | 9 +++++ assets/shaders/.src/crt.glsl | 57 +++++++++++++++++++++++++++ assets/shaders/.src/scanlines.glsl | 38 ++++++++++++++++++ assets/shaders/crt-light.glslp | 7 ++++ assets/shaders/crt-strong.glslp | 7 ++++ assets/shaders/crt.glslp | 3 ++ assets/shaders/scanlines-light.glslp | 6 +++ assets/shaders/scanlines-strong.glslp | 6 +++ assets/shaders/scanlines.glslp | 3 ++ config.ini | 6 ++- snesrecomp | 2 +- src/main.c | 1 + 12 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 assets/shaders/.src/crt.glsl create mode 100644 assets/shaders/.src/scanlines.glsl create mode 100644 assets/shaders/crt-light.glslp create mode 100644 assets/shaders/crt-strong.glslp create mode 100644 assets/shaders/crt.glslp create mode 100644 assets/shaders/scanlines-light.glslp create mode 100644 assets/shaders/scanlines-strong.glslp create mode 100644 assets/shaders/scanlines.glslp diff --git a/CMakeLists.txt b/CMakeLists.txt index ce10eb6..7aae06a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,3 +309,12 @@ target_sources(SuperMetroidSNESRecomp PRIVATE target_include_directories(SuperMetroidSNESRecomp PRIVATE ${SNESRECOMP_ROOT}/host ${RECOMP_UI_ROOT}/src) + +# GLSL shader presets (CRT/scanlines) for the OpenGL desktop backend. The +# launcher's shader dropdown scans assets/shaders beside the executable for +# *.glsl/*.glslp; src/main.c opts this game into that row. +if(NOT ANDROID) + snesrecomp_target_stage_dir(SuperMetroidSNESRecomp + ${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders + assets/shaders) +endif() diff --git a/assets/shaders/.src/crt.glsl b/assets/shaders/.src/crt.glsl new file mode 100644 index 0000000..6e91d4d --- /dev/null +++ b/assets/shaders/.src/crt.glsl @@ -0,0 +1,57 @@ +// Lightweight CRT look: scanlines + a coarse RGB aperture-grille mask + +// a soft vignette. Deliberately no curvature/distortion -- keeps every +// output pixel address identical to the input, so it stays cheap and safe +// (no sampling outside [0,1], no divide-by-zero). Single pass; see +// scanlines.glsl for the preset-format notes. + +#if defined(VERTEX) + +in vec2 VertexCoord; +in vec2 TexCoord; +out vec2 vTexCoord; +uniform mat4 MVPMatrix; + +void main() { + gl_Position = MVPMatrix * vec4(VertexCoord, 0.0, 1.0); + vTexCoord = TexCoord; +} + +#elif defined(FRAGMENT) + +#pragma parameter CRT_SCANLINE_STRENGTH "CRT Scanline Strength" 0.78 0.0 1.0 0.02 +#pragma parameter CRT_MASK_STRENGTH "CRT Mask Strength" 0.25 0.0 1.0 0.05 +#pragma parameter CRT_VIGNETTE_STRENGTH "CRT Vignette Strength" 0.35 0.0 1.0 0.05 + +in vec2 vTexCoord; +out vec4 FragColor; + +uniform sampler2D Texture; +uniform float CRT_SCANLINE_STRENGTH; +uniform float CRT_MASK_STRENGTH; +uniform float CRT_VIGNETTE_STRENGTH; + +void main() { + vec4 color = texture(Texture, vTexCoord); + + // One dark output row in every two. + float row = floor(gl_FragCoord.y); + float scan = mix(1.0, CRT_SCANLINE_STRENGTH, mod(row, 2.0)); + + // Coarse RGB aperture-grille mask across output columns. + float col = mod(floor(gl_FragCoord.x), 3.0); + vec3 mask; + if (col < 1.0) + mask = vec3(1.0, 1.0 - CRT_MASK_STRENGTH, 1.0 - CRT_MASK_STRENGTH); + else if (col < 2.0) + mask = vec3(1.0 - CRT_MASK_STRENGTH, 1.0, 1.0 - CRT_MASK_STRENGTH); + else + mask = vec3(1.0 - CRT_MASK_STRENGTH, 1.0 - CRT_MASK_STRENGTH, 1.0); + + // Soft vignette toward the edges. + vec2 uv = vTexCoord * 2.0 - 1.0; + float vig = 1.0 - CRT_VIGNETTE_STRENGTH * dot(uv, uv) * 0.5; + + FragColor = vec4(color.rgb * scan * mask * vig, color.a); +} + +#endif diff --git a/assets/shaders/.src/scanlines.glsl b/assets/shaders/.src/scanlines.glsl new file mode 100644 index 0000000..3c7e3cd --- /dev/null +++ b/assets/shaders/.src/scanlines.glsl @@ -0,0 +1,38 @@ +// Simple horizontal-scanline preset. Single pass; see glsl_shader.c for the +// preset/parameter parser this file is compiled against (GLSL 330 core -- +// the profile src/opengl.c already requires via +// SDL_GL_CONTEXT_PROFILE_CORE). No #version line: the loader prepends +// "#version 330\n" (core, since no profile qualifier defaults to core). + +#if defined(VERTEX) + +in vec2 VertexCoord; +in vec2 TexCoord; +out vec2 vTexCoord; +uniform mat4 MVPMatrix; + +void main() { + gl_Position = MVPMatrix * vec4(VertexCoord, 0.0, 1.0); + vTexCoord = TexCoord; +} + +#elif defined(FRAGMENT) + +#pragma parameter SCANLINE_STRENGTH "Scanline Strength" 0.72 0.0 1.0 0.02 +#pragma parameter SCANLINE_WIDTH "Scanline Width (px)" 2.0 1.0 4.0 1.0 + +in vec2 vTexCoord; +out vec4 FragColor; + +uniform sampler2D Texture; +uniform float SCANLINE_STRENGTH; +uniform float SCANLINE_WIDTH; + +void main() { + vec4 color = texture(Texture, vTexCoord); + float row = floor(gl_FragCoord.y / SCANLINE_WIDTH); + float dark = mix(1.0, SCANLINE_STRENGTH, mod(row, 2.0)); + FragColor = vec4(color.rgb * dark, color.a); +} + +#endif diff --git a/assets/shaders/crt-light.glslp b/assets/shaders/crt-light.glslp new file mode 100644 index 0000000..240c59b --- /dev/null +++ b/assets/shaders/crt-light.glslp @@ -0,0 +1,7 @@ +shaders = "1" +shader0 = ".src/crt.glsl" +filter_linear0 = "false" +parameters = "CRT_SCANLINE_STRENGTH;CRT_MASK_STRENGTH;CRT_VIGNETTE_STRENGTH" +CRT_SCANLINE_STRENGTH = "0.88" +CRT_MASK_STRENGTH = "0.12" +CRT_VIGNETTE_STRENGTH = "0.18" diff --git a/assets/shaders/crt-strong.glslp b/assets/shaders/crt-strong.glslp new file mode 100644 index 0000000..b9dfeb8 --- /dev/null +++ b/assets/shaders/crt-strong.glslp @@ -0,0 +1,7 @@ +shaders = "1" +shader0 = ".src/crt.glsl" +filter_linear0 = "false" +parameters = "CRT_SCANLINE_STRENGTH;CRT_MASK_STRENGTH;CRT_VIGNETTE_STRENGTH" +CRT_SCANLINE_STRENGTH = "0.60" +CRT_MASK_STRENGTH = "0.40" +CRT_VIGNETTE_STRENGTH = "0.55" diff --git a/assets/shaders/crt.glslp b/assets/shaders/crt.glslp new file mode 100644 index 0000000..f14389c --- /dev/null +++ b/assets/shaders/crt.glslp @@ -0,0 +1,3 @@ +shaders = "1" +shader0 = ".src/crt.glsl" +filter_linear0 = "false" diff --git a/assets/shaders/scanlines-light.glslp b/assets/shaders/scanlines-light.glslp new file mode 100644 index 0000000..5de237f --- /dev/null +++ b/assets/shaders/scanlines-light.glslp @@ -0,0 +1,6 @@ +shaders = "1" +shader0 = ".src/scanlines.glsl" +filter_linear0 = "false" +parameters = "SCANLINE_STRENGTH;SCANLINE_WIDTH" +SCANLINE_STRENGTH = "0.85" +SCANLINE_WIDTH = "2.0" diff --git a/assets/shaders/scanlines-strong.glslp b/assets/shaders/scanlines-strong.glslp new file mode 100644 index 0000000..fdcd4be --- /dev/null +++ b/assets/shaders/scanlines-strong.glslp @@ -0,0 +1,6 @@ +shaders = "1" +shader0 = ".src/scanlines.glsl" +filter_linear0 = "false" +parameters = "SCANLINE_STRENGTH;SCANLINE_WIDTH" +SCANLINE_STRENGTH = "0.55" +SCANLINE_WIDTH = "2.0" diff --git a/assets/shaders/scanlines.glslp b/assets/shaders/scanlines.glslp new file mode 100644 index 0000000..2db041b --- /dev/null +++ b/assets/shaders/scanlines.glslp @@ -0,0 +1,3 @@ +shaders = "1" +shader0 = ".src/scanlines.glsl" +filter_linear0 = "false" diff --git a/config.ini b/config.ini index c0607d5..b5f59b1 100644 --- a/config.ini +++ b/config.ini @@ -25,6 +25,8 @@ VSync = 1 # presets), software, or an SDL render driver such as vulkan. Empty follows # OutputMethod. Renderer = auto +# OpenGL GLSL preset path, edited by the launcher's shader dropdown. +Shader = WindowSize = Auto Fullscreen = 0 WindowScale = 3 @@ -62,7 +64,9 @@ Save = Shift+F1,Shift+F2,Shift+F3,Shift+F4,Shift+F5,Shift+F6,Shift+F7,Shift+F8,S # In-game overlays (framework modules). The save-state browser also opens # with Select+R on the pad; rewind with [Controller] RewindGesture below. SaveStateMenu = F11 -Rewind = F12 +Rewind = Shift+F12 +# OpenGL framebuffer capture, saved under screenshots/. +Screenshot = F12 [GamepadMap] EnableGamepad1 = true diff --git a/snesrecomp b/snesrecomp index f7ce10c..cc01228 160000 --- a/snesrecomp +++ b/snesrecomp @@ -1 +1 @@ -Subproject commit f7ce10cdcdb9bd6c56b7aedd597ca8cf0626fb25 +Subproject commit cc01228d958821ce02ac4647b11a41f2a0acd4d0 diff --git a/src/main.c b/src/main.c index 128eea7..e5d1079 100644 --- a/src/main.c +++ b/src/main.c @@ -218,6 +218,7 @@ static const SnesDesktopHostGame kSuperMetroidHost = { .debug_port = 4380, .widescreen_supported = 0, /* widescreen is this title's Mods page */ .msu1_supported = 0, + .shader_supported = 1, .simulation_hz = SM_SIMULATION_HZ, .frame_width = 256, .frame_height = SM_HEIGHT,