Skip to content

feat: add shader sets and quick switch shortcut - #796

Open
sinedied wants to merge 9 commits into
LoveRetro:mainfrom
sinedied:shaderset
Open

feat: add shader sets and quick switch shortcut#796
sinedied wants to merge 9 commits into
LoveRetro:mainfrom
sinedied:shaderset

Conversation

@sinedied

@sinedied sinedied commented Aug 4, 2026

Copy link
Copy Markdown

Adds global shader sets while preserving NextUI’s simplicity and existing behavior, allowing quick toggle between presets of shaders.

Includes Retro and Sharp presets that work out of the box, can be adjusted directly on-device, and can be quickly cycled or disabled through a configurable shortcut.

The included shaders were built and tuned specifically for NextUI, then thoroughly tested across many resolutions, aspect ratios, and integer-scaling scenarios. I also created RetroShader Lab to preview and test every shader and set in one place.

I took extra care was taken to maintain compatibility and avoid changing existing configuration behavior 🙂 Of course I tested this on hardware extensively (Trimui Brick).

Screenshots

See here: https://github.com/sinedied/perfect-retroshaders#screenshots, or open the lab directly to test and tweak the shaders and see the presets: https://sinedied.github.io/retroshader-lab/

@sinedied sinedied changed the title Shaderset feat: add shader sets and quick switch shortcut Aug 4, 2026
@frysee

frysee commented Aug 4, 2026

Copy link
Copy Markdown
Member

Thanks, just saw your post on reddit!

@sinedied

sinedied commented Aug 5, 2026

Copy link
Copy Markdown
Author

I forgot to explain the base idea for the shaders sets: it allows you to set base shaders at the root for the set, then override it if needed for a machine, switching between sets or regular settings at any time. This PR provides sharp and retro sets, meaning on any emulator you can toggle for exemple the "retro" set, then all machine you play will use this set. If you toggle "sharp", all emulators with use the sharp config. And if set is disabled, the regular config comes back.

It allows to quickly change at once the look for all emulators. And if you have a set enabled, saving the settings for the current console will create a new cfg override with the front-end and shaders settings for the set and that machine only.

Hope it's more clear, I should have made a demo video 😆

@frysee

frysee commented Aug 5, 2026

Copy link
Copy Markdown
Member

Sounds interesting, although it kinda breaks the convention of settings in minarch being either in game or core scope. Ill have to set some time aside to actually Look at your code, but it definitely looks promising visually.

@sinedied

sinedied commented Aug 5, 2026

Copy link
Copy Markdown
Author

Yes, that's why a set works as an override of the game or core settings (just like regular shaders configs BTW), but I wanted to provide a way to create a set override on device directly.

If you prefer I can revert the saving part and keep the sets as pure overrides, that way it doesn't break the game/core saving and keep it simpler.

@pvaibhav

pvaibhav commented Aug 5, 2026

Copy link
Copy Markdown

Hi @sinedied I looked at the code and here is my feedback:

First off this is a feature I had been wanting for a while. Knulli and Rocknix have it. But I need more info and have a suggestion at the end.

  1. Can you provide a slightly more detailed high-level overview of the changes? If this is a shader pack + shader sets why do we need changes to platform-agnostic generic video and shader code? You've added some vertex and fragment shader support, but there seems to be at least one memory leak bug fix also rolled in. Since it's a large PR, having you walk us through will be helpful. I am more interested in the changes to the minarch rendering layer and not the settings layer which is clear.
  2. Related to the above I believe the entire functionality can be externalised to a PAK that backs up and overwrites configs without requiring changes to core NextUI code. Do you agree? Unless minarch doesn't support a feature that must be added, I am seeing at least 3 parts in this PR: a shader pack, improvements to minarch rendering path, and changes to the settings engine. Since this would be a major new feature I really recommend doing it in steps: first a shader pack plus maybe a PAK, and if it doesn't offer good UX, or minarch needs to be updated, then and only then a NextUI modification.

I would really like to try your optimised shaders first. For me that is a bigger highlight than native shader set support inside NextUI.

Also @frysee since this modifies minarch it may be worthwhile to decide how to proceed with the threaded-video branch. Review/test it or drop it entirely?

@sinedied

sinedied commented Aug 6, 2026

Copy link
Copy Markdown
Author

Thanks a lot for taking the time to actually read the code @pvaibhav! Let me try to unpack it and help a bit with the review, I should have done in the first place.

What's in the PR

Code-wise the modification are these areas:

1) Shader pack — skeleton/BASE/Shaders/
4 new GLSL shaders (pixel-perfect, crt-perfect, lcd-perfect, dmg-perfect) + the sets/ folder with the Retro/Sharp presets. That's the easy part :)

2) shader_sets.c/h (new & self-contained)
Tiny module with no minarch dependency: list the sets on disk, read/write the active one (a one-line text file in SHARED_USERDATA_PATH/shader-set.txt), cycle to the next. That's it.

3) ma_config.c, the settings layer (most of the changes)
A set is just one more override layer, that's read after system / default / user cfg:

Config_readOptionsString(config.system_cfg);
Config_readOptionsString(config.default_cfg);
Config_readOptionsString(config.user_cfg);
Config_readSetOptionsString(config.shader_set_cfg, 1);          // <sets>/Retro.cfg
Config_readSetOptionsString(config.shader_set_override_cfg, 1); // <sets>/GB/Retro.cfg

Nothing else in the resolution order changed. The rest of that diff is the write path (Config_write split into small helpers) so that saving with a set active writes to the set override instead of stomping your normal cfg.

As I said in my earlier comment, I'm completely open to drop the config write part, I was unsure whether it was a good idea to complicate the save path but wanted to give it a try. Just let me know if you want me to remove it and keep sets as pure read-only overrides.

Now for the minarch touch:

Creating a pak-only PR was my first idea if you look at the first commits, but after testing and fixing some of the shader issue discovered I think adding the menu entry directly in the shader config menu was way better than a separate pak with a single item. And it also was needed to add the shortcut :)

1) ma_frontend_opts.c / ma_input.c
Menu entry + the SHORTCUT_NEXT_SHADER_SET shortcut, with a notification toast.

Menu entry: Dr  Mario 2026-07-27-23-47-52
New shortcut: Dr  Mario 2026-07-27-23-49-07

2) minarch.c has_pending_opt_change moved to a global so a live set switch can request an AV re-check, skipped while core options are still being applied, plus renderer.dst_p = 0 so the scaler re-inits at the new geometry. And the startup Config_free() was dropped because the cfg buffers are now needed for later reloads (still freed in Config_init/Config_restore, so it's not a leak!).

3) generic_video.c, the the part you flagged, see below

Why generic_video.c had to change

To be clear: I didn't add vertex/fragment shader support, that was already there (load_shader_from_file with GL_VERTEX_SHADER/GL_FRAGMENT_SHADER). What changed is the lifetime of those objects.

I meant to keep my changes as minimal as possible, but this one was a prerequisite. Before this PR, init_shader_program() effectively ran a couple of times at boot, so the bugs were invisible. Shader sets call it repeatedly while the game is running, and then:

  • Fatal: glDeleteProgram(shader->shader_p) ran before linking the new one. Any compile error on the new shader left you with a deleted/0 program → black screen, no way back. Now it links into a temp program and only swaps on success, so a bad shader just keeps the current one. Only discoverable if you try running bad GLSL hence why it was probably unnoticed before.
  • Leak: the vertex/fragment GLuints were never glDeleteShader'd after linking → leaked on every reload.
  • Leak: loadShaderPragmas() calloc'd a fresh array each call without freeing the previous one.
  • Leak: load_shader_source()'s buffer was never freed, same for the strdup'd filename.

(The blank_shader_program.filename = NULL change is just because filename is now heap-owned, and you can't free() a string literal)

Same story for freeShaderSettings() / the config.shaders_preset free in ma_config.c: reloading presets at runtime leaked the whole option array otherwise.

Note

for complete transparency here: these issues were all found while I was testing the changes and investigating/reviewing my changes with Opus 5/xhigh. I did a cross review with GPT5.6 Sol/xhigh regarding the findings, and a final manual review of the changes proposed here to be sure it wasn't some slop or wrong findings. Some more leaks were found in other areas of the codebase, but I left them out as it wasn't directly related to my PR. Sidenote: I'm lucky to have unlimited access to frontier models, so let me know if you're open to separate PRs for hardening the codebase, I'll be happy to help.

Could this be a PAK instead?

In full honesty that was my initial idea and goal, but it turned out to not be realistic for the behavior I wanted:

  • A PAK can only back up and overwrite cfg files. That's destructive, so any tweak you make while a set is active gets clobbered on restore, and it has to touch every console/game cfg instead of layering on top.
    The whole point of sets is that they're non-destructive override, you can disable the set and your original config is untouched, because it was never rewritten.
  • minarch caches shader state in memory, so a cfg swapped from outside only applies at next launch. No live toggle was possible this way, which is the main UX win (with the shortcut mid-game, you can cycle Retro → Sharp → off, see it instantly).
  • The rendering fixes in were needed for the live reload regardless of where the config comes from.

Now even with the added details, it's still a big PR and if you prefer I can try splitting it in samller separate PRs, for example:

  1. Shader pack only, the 4 new GLSL.
  2. generic_video.c reload hardening: standalone bug fixes, good on their own merit.
  3. Shader sets + shortcut, with the retro/sharp presets.
  4. (optional) On-device saving of set overrides: the part @frysee flagged as bending the game/core scope convention, and that can be dropped if you prefer.

I'm currently away from my work laptop so this would have to wait for ~10 days though, juste let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants