From 11728cd8d42305ab2705fefef3f851a50932a6ac Mon Sep 17 00:00:00 2001 From: sinedied Date: Mon, 27 Jul 2026 11:59:12 +0200 Subject: [PATCH 1/9] feat: add shader set management --- makefile | 1 + skeleton/BASE/README.txt | 11 + skeleton/BASE/Shaders/sets/GBA/Retro.cfg | 12 + skeleton/BASE/Shaders/sets/Retro.cfg | 7 + skeleton/BASE/Shaders/sets/Sharp.cfg | 7 + .../Tools/desktop/Shader Sets.pak/launch.sh | 4 + .../Tools/tg5040/Shader Sets.pak/launch.sh | 4 + .../Tools/tg5050/Shader Sets.pak/launch.sh | 4 + workspace/all/common/generic_video.c | 100 ++++--- workspace/all/common/shader_sets.c | 272 +++++++++++++++++ workspace/all/common/shader_sets.h | 31 ++ workspace/all/minarch/ma_config.c | 280 ++++++++++++++++-- workspace/all/minarch/ma_config.h | 1 + workspace/all/minarch/ma_input.c | 37 +++ workspace/all/minarch/ma_internal.h | 3 + workspace/all/minarch/makefile | 2 +- workspace/all/minarch/minarch.c | 2 - workspace/all/shadersets/Makefile | 41 +++ workspace/all/shadersets/shadersets.c | 193 ++++++++++++ workspace/makefile | 3 + 20 files changed, 946 insertions(+), 69 deletions(-) create mode 100644 skeleton/BASE/Shaders/sets/GBA/Retro.cfg create mode 100644 skeleton/BASE/Shaders/sets/Retro.cfg create mode 100644 skeleton/BASE/Shaders/sets/Sharp.cfg create mode 100755 skeleton/EXTRAS/Tools/desktop/Shader Sets.pak/launch.sh create mode 100755 skeleton/EXTRAS/Tools/tg5040/Shader Sets.pak/launch.sh create mode 100755 skeleton/EXTRAS/Tools/tg5050/Shader Sets.pak/launch.sh create mode 100644 workspace/all/common/shader_sets.c create mode 100644 workspace/all/common/shader_sets.h create mode 100644 workspace/all/shadersets/Makefile create mode 100644 workspace/all/shadersets/shadersets.c diff --git a/makefile b/makefile index fed3da9f6..4279c445e 100644 --- a/makefile +++ b/makefile @@ -101,6 +101,7 @@ endif cp ./workspace/all/minarch/build/$(PLATFORM)/minarch.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/nextval/build/$(PLATFORM)/nextval.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/clock/build/$(PLATFORM)/clock.elf ./build/EXTRAS/Tools/$(PLATFORM)/Clock.pak/ + cp ./workspace/all/shadersets/build/$(PLATFORM)/shadersets.elf ./build/EXTRAS/Tools/$(PLATFORM)/Shader\ Sets.pak/ cp ./workspace/all/minput/build/$(PLATFORM)/minput.elf ./build/EXTRAS/Tools/$(PLATFORM)/Input.pak/ cp ./workspace/all/settings/build/$(PLATFORM)/settings.elf ./build/EXTRAS/Tools/$(PLATFORM)/Settings.pak/ ifneq (,$(filter $(PLATFORM),tg5040 tg5050)) diff --git a/skeleton/BASE/README.txt b/skeleton/BASE/README.txt index 5f6b965bd..2faa1d2f8 100644 --- a/skeleton/BASE/README.txt +++ b/skeleton/BASE/README.txt @@ -97,6 +97,17 @@ Cheats use RetroArch .cht file format. Many cheat files are here pragmas) { + free(shader->pragmas); + shader->pragmas = NULL; + shader->num_pragmas = 0; + } + if (!shaderSource) + return; + shader->pragmas = calloc(MAX_SHADER_PRAGMAS, sizeof(ShaderParam)); if (!shader->pragmas) { fprintf(stderr, "Out of memory allocating pragmas for %s\n", shader->filename); @@ -491,57 +499,75 @@ void init_shader_program(ShaderProgram * shader, const char * path, const char * char filepath[512]; snprintf(filepath, sizeof(filepath), "%s/%s", path, filename); - const char *shaderSource = load_shader_source(filepath); - loadShaderPragmas(shader,shaderSource); + char *shaderSource = load_shader_source(filepath); + if (!shaderSource) + return; GLuint vertex_shader1 = load_shader_from_file(GL_VERTEX_SHADER, filepath); GLuint fragment_shader1 = load_shader_from_file(GL_FRAGMENT_SHADER, filepath); - - // Link the shader program - if (shader->shader_p != 0) { - LOG_info("Deleting previous shader %i\n",shader->shader_p); - glDeleteProgram(shader->shader_p); + if (!vertex_shader1 || !fragment_shader1) { + if (vertex_shader1) glDeleteShader(vertex_shader1); + if (fragment_shader1) glDeleteShader(fragment_shader1); + free(shaderSource); + return; } - shader->shader_p = link_program(vertex_shader1, fragment_shader1, filename); + GLuint new_program = link_program(vertex_shader1, fragment_shader1, filename); + glDeleteShader(vertex_shader1); + glDeleteShader(fragment_shader1); - if (shader->shader_p == 0) { + if (new_program == 0) { LOG_info("Shader linking failed for %s\n", filename); + free(shaderSource); + return; } GLint success = 0; - glGetProgramiv(shader->shader_p, GL_LINK_STATUS, &success); + glGetProgramiv(new_program, GL_LINK_STATUS, &success); if (!success) { char infoLog[512]; - glGetProgramInfoLog(shader->shader_p, 512, NULL, infoLog); + glGetProgramInfoLog(new_program, 512, NULL, infoLog); LOG_info("Shader Program Linking Failed: %s\n", infoLog); - } else { - LOG_info("Shader Program Linking Success %s shader ID is %i\n", filename,shader->shader_p); - - // Populate uniforms and pragma uniforms - shader->u_FrameDirection = glGetUniformLocation( shader->shader_p, "FrameDirection"); - shader->u_FrameCount = glGetUniformLocation( shader->shader_p, "FrameCount"); - shader->u_OutputSize = glGetUniformLocation( shader->shader_p, "OutputSize"); - shader->u_TextureSize = glGetUniformLocation( shader->shader_p, "TextureSize"); - shader->u_InputSize = glGetUniformLocation( shader->shader_p, "InputSize"); - shader->u_OrigTextureSize = glGetUniformLocation( shader->shader_p, "OrigTextureSize"); - shader->u_OrigInputSize = glGetUniformLocation( shader->shader_p, "OrigInputSize"); - shader->u_Texture = glGetUniformLocation(shader->shader_p, "Texture"); - shader->u_OrigTexture = glGetUniformLocation(shader->shader_p, "OrigTexture"); - shader->u_texelSize = glGetUniformLocation(shader->shader_p, "texelSize"); - for (int i = 0; i < shader->num_pragmas; ++i) { - shader->pragmas[i].uniformLocation = glGetUniformLocation(shader->shader_p, shader->pragmas[i].name); - shader->pragmas[i].value = shader->pragmas[i].def; - - LOG_info("Param: %s = %f (min: %f, max: %f, step: %f)\n", - shader->pragmas[i].name, - shader->pragmas[i].def, - shader->pragmas[i].min, - shader->pragmas[i].max, - shader->pragmas[i].step); - } + glDeleteProgram(new_program); + free(shaderSource); + return; + } + if (shader->shader_p != 0) { + LOG_info("Deleting previous shader %i\n",shader->shader_p); + glDeleteProgram(shader->shader_p); } + shader->shader_p = new_program; + loadShaderPragmas(shader, shaderSource); + free(shaderSource); + + LOG_info("Shader Program Linking Success %s shader ID is %i\n", filename,shader->shader_p); + + // Populate uniforms and pragma uniforms + shader->u_FrameDirection = glGetUniformLocation( shader->shader_p, "FrameDirection"); + shader->u_FrameCount = glGetUniformLocation( shader->shader_p, "FrameCount"); + shader->u_OutputSize = glGetUniformLocation( shader->shader_p, "OutputSize"); + shader->u_TextureSize = glGetUniformLocation( shader->shader_p, "TextureSize"); + shader->u_InputSize = glGetUniformLocation( shader->shader_p, "InputSize"); + shader->u_OrigTextureSize = glGetUniformLocation( shader->shader_p, "OrigTextureSize"); + shader->u_OrigInputSize = glGetUniformLocation( shader->shader_p, "OrigInputSize"); + shader->u_Texture = glGetUniformLocation(shader->shader_p, "Texture"); + shader->u_OrigTexture = glGetUniformLocation(shader->shader_p, "OrigTexture"); + shader->u_texelSize = glGetUniformLocation(shader->shader_p, "texelSize"); + for (int i = 0; i < shader->num_pragmas; ++i) { + shader->pragmas[i].uniformLocation = glGetUniformLocation(shader->shader_p, shader->pragmas[i].name); + shader->pragmas[i].value = shader->pragmas[i].def; + + LOG_info("Param: %s = %f (min: %f, max: %f, step: %f)\n", + shader->pragmas[i].name, + shader->pragmas[i].def, + shader->pragmas[i].min, + shader->pragmas[i].max, + shader->pragmas[i].step); + } + + if (shader->filename) + free(shader->filename); shader->filename = strdup(filename); } diff --git a/workspace/all/common/shader_sets.c b/workspace/all/common/shader_sets.c new file mode 100644 index 000000000..95764e696 --- /dev/null +++ b/workspace/all/common/shader_sets.c @@ -0,0 +1,272 @@ +#include "shader_sets.h" +#include "defines.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool isSafeComponent(const char *value) +{ + if (!value || !value[0] || !strcmp(value, ".") || !strcmp(value, "..")) + return false; + + return !strchr(value, '/') && !strchr(value, '\\'); +} + +static bool isConfigFile(const char *name) +{ + size_t len; + + if (!name || name[0] == '.') + return false; + + len = strlen(name); + return len > 4 && !strcmp(name + len - 4, ".cfg"); +} + +static bool isRegularFile(const char *path) +{ + struct stat st; + return stat(path, &st) == 0 && S_ISREG(st.st_mode); +} + +static int compareNames(const void *left, const void *right) +{ + const char *a = *(const char * const *)left; + const char *b = *(const char * const *)right; + int result = strcasecmp(a, b); + return result ? result : strcmp(a, b); +} + +static void readActiveName(char *name, size_t name_size) +{ + FILE *file; + + if (!name || name_size == 0) + return; + + name[0] = '\0'; + file = fopen(SHADER_SET_STATE_PATH, "r"); + if (!file) + return; + + if (fgets(name, name_size, file)) + name[strcspn(name, "\r\n")] = '\0'; + + fclose(file); +} + +static bool writeActiveName(const char *name) +{ + char temp_path[MAX_PATH]; + FILE *file; + int fd; + + if (!name || !name[0]) { + if (unlink(SHADER_SET_STATE_PATH) == 0 || errno == ENOENT) { + sync(); + return true; + } + return false; + } + + if (snprintf(temp_path, sizeof(temp_path), "%s.tmp.%ld", + SHADER_SET_STATE_PATH, (long)getpid()) >= (int)sizeof(temp_path)) + return false; + + file = fopen(temp_path, "w"); + if (!file) + return false; + + if (fprintf(file, "%s\n", name) < 0 || fflush(file) != 0) { + fclose(file); + unlink(temp_path); + return false; + } + + fd = fileno(file); + if (fd >= 0 && fsync(fd) != 0) { + fclose(file); + unlink(temp_path); + return false; + } + + if (fclose(file) != 0) { + unlink(temp_path); + return false; + } + + if (rename(temp_path, SHADER_SET_STATE_PATH) != 0) { + unlink(temp_path); + return false; + } + + sync(); + return true; +} + +bool ShaderSets_list(ShaderSetList *list) +{ + DIR *dir; + struct dirent *entry; + char **names; + int count = 1; + + if (!list) + return false; + + list->names = NULL; + list->count = 0; + + names = calloc(1, sizeof(char *)); + if (!names) + return false; + + names[0] = strdup(""); + if (!names[0]) { + free(names); + return false; + } + + dir = opendir(SHADER_SETS_PATH); + if (dir) { + while ((entry = readdir(dir)) != NULL) { + char path[MAX_PATH]; + char *stem; + char **expanded; + size_t len; + + if (!isConfigFile(entry->d_name)) + continue; + + if (snprintf(path, sizeof(path), "%s/%s", + SHADER_SETS_PATH, entry->d_name) >= (int)sizeof(path)) + continue; + + if (!isRegularFile(path)) + continue; + + len = strlen(entry->d_name) - 4; + stem = strndup(entry->d_name, len); + if (!stem) + goto fail; + + if (!strcasecmp(stem, SHADER_SET_DISABLED_LABEL)) { + free(stem); + continue; + } + + expanded = realloc(names, sizeof(char *) * (count + 1)); + if (!expanded) { + free(stem); + goto fail; + } + + names = expanded; + names[count++] = stem; + } + closedir(dir); + } + + if (count > 2) + qsort(names + 1, count - 1, sizeof(char *), compareNames); + + list->names = names; + list->count = count; + return true; + +fail: + if (dir) + closedir(dir); + for (int i = 0; i < count; i++) + free(names[i]); + free(names); + return false; +} + +void ShaderSets_freeList(ShaderSetList *list) +{ + if (!list) + return; + + for (int i = 0; i < list->count; i++) + free(list->names[i]); + free(list->names); + list->names = NULL; + list->count = 0; +} + +const char *ShaderSets_displayName(const char *name) +{ + return name && name[0] ? name : SHADER_SET_DISABLED_LABEL; +} + +int ShaderSets_activeIndex(const ShaderSetList *list) +{ + char active[MAX_PATH]; + + if (!list || !list->names || list->count <= 0) + return 0; + + readActiveName(active, sizeof(active)); + if (!isSafeComponent(active)) + return 0; + + for (int i = 1; i < list->count; i++) { + if (!strcmp(list->names[i], active)) + return i; + } + + writeActiveName(""); + return 0; +} + +bool ShaderSets_setActive(const char *name) +{ + char path[MAX_PATH]; + + if (!name || !name[0]) + return writeActiveName(""); + + if (!ShaderSets_rootPath(name, path, sizeof(path)) || !isRegularFile(path)) + return false; + + return writeActiveName(name); +} + +bool ShaderSets_advance(const ShaderSetList *list, char *name, size_t name_size) +{ + int next; + + if (!list || !list->names || list->count <= 0 || !name || name_size == 0) + return false; + + next = (ShaderSets_activeIndex(list) + 1) % list->count; + if (snprintf(name, name_size, "%s", list->names[next]) >= (int)name_size) + return false; + + return ShaderSets_setActive(name); +} + +bool ShaderSets_rootPath(const char *name, char *path, size_t path_size) +{ + if (!isSafeComponent(name) || !path || path_size == 0) + return false; + + return snprintf(path, path_size, "%s/%s.cfg", SHADER_SETS_PATH, name) < (int)path_size; +} + +bool ShaderSets_overridePath(const char *name, const char *tag, char *path, size_t path_size) +{ + if (!isSafeComponent(name) || !isSafeComponent(tag) || !path || path_size == 0) + return false; + + return snprintf(path, path_size, "%s/%s/%s.cfg", + SHADER_SETS_PATH, tag, name) < (int)path_size; +} diff --git a/workspace/all/common/shader_sets.h b/workspace/all/common/shader_sets.h new file mode 100644 index 000000000..fb053702e --- /dev/null +++ b/workspace/all/common/shader_sets.h @@ -0,0 +1,31 @@ +#ifndef SHADER_SETS_H +#define SHADER_SETS_H + +#include +#include + +#ifndef SHADER_SETS_PATH +#define SHADER_SETS_PATH SDCARD_PATH "/Shaders/sets" +#endif +#ifndef SHADER_SET_STATE_PATH +#define SHADER_SET_STATE_PATH SHARED_USERDATA_PATH "/shader-set.txt" +#endif +#define SHADER_SET_DISABLED_LABEL "Disabled" + +typedef struct ShaderSetList { + char **names; + int count; +} ShaderSetList; + +bool ShaderSets_list(ShaderSetList *list); +void ShaderSets_freeList(ShaderSetList *list); + +const char *ShaderSets_displayName(const char *name); +int ShaderSets_activeIndex(const ShaderSetList *list); +bool ShaderSets_setActive(const char *name); +bool ShaderSets_advance(const ShaderSetList *list, char *name, size_t name_size); + +bool ShaderSets_rootPath(const char *name, char *path, size_t path_size); +bool ShaderSets_overridePath(const char *name, const char *tag, char *path, size_t path_size); + +#endif diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.c index 438a0a65b..9f8f4a18e 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.c @@ -7,6 +7,10 @@ #include "ma_internal.h" #include "ma_options.h" #include "ma_config.h" +#include "ma_runframe.h" +#include "shader_sets.h" + +static void freeShaderSettings(int i); static ButtonMapping button_label_mapping[] = { // used to lookup the retro_id and local btn_id from button name {"NONE", -1, BTN_ID_NONE}, @@ -404,35 +408,32 @@ void Config_init(void) { config.initialized = 1; } void Config_quit(void) { - if (!config.initialized) return; - for (int i=0; core_button_mapping[i].name; i++) { - free(core_button_mapping[i].name); + if (config.initialized) { + for (int i=0; core_button_mapping[i].name; i++) { + free(core_button_mapping[i].name); + } } + for (int i = 0; i < 3; i++) + freeShaderSettings(i); + Config_free(); } -static void Config_readOptionsString(char* cfg) { + +static void Config_readFrontendOptionsString(char* cfg, int sync) { if (!cfg) return; - LOG_info("Config_readOptions\n"); - char key[256]; char value[256]; for (int i=0; config.frontend.options[i].key; i++) { Option* option = &config.frontend.options[i]; if (!Config_getValue(cfg, option->key, value, &option->lock)) continue; OptionList_setOptionValue(&config.frontend, option->key, value); - Config_syncFrontend(option->key, option->value); - } - - if (has_custom_controllers && Config_getValue(cfg,"minarch_gamepad_type",value,NULL)) { - gamepad_type = strtol(value, NULL, 0); - int device = strtol(gamepad_values[gamepad_type], NULL, 0); - core.set_controller_port_device(0, device); - } - for (int i=0; config.core.options[i].key; i++) { - Option* option = &config.core.options[i]; - // LOG_info("%s\n",option->key); - if (!Config_getValue(cfg, option->key, value, &option->lock)) continue; - OptionList_setOptionValue(&config.core, option->key, value); + if (sync) Config_syncFrontend(option->key, option->value); } +} + +static void Config_readShaderOptionsString(char* cfg) { + if (!cfg) return; + + char value[256]; for (int i=0; config.shaders.options[i].key; i++) { Option* option = &config.shaders.options[i]; if (!Config_getValue(cfg, option->key, value, &option->lock)) continue; @@ -448,6 +449,32 @@ static void Config_readOptionsString(char* cfg) { } } } + +static void Config_readFrontendShaderOptionsString(char* cfg, int sync) { + Config_readFrontendOptionsString(cfg, sync); + Config_readShaderOptionsString(cfg); +} + +static void Config_readOptionsString(char* cfg) { + if (!cfg) return; + + LOG_info("Config_readOptions\n"); + char value[256]; + Config_readFrontendOptionsString(cfg, 1); + + if (has_custom_controllers && Config_getValue(cfg,"minarch_gamepad_type",value,NULL)) { + gamepad_type = strtol(value, NULL, 0); + int device = strtol(gamepad_values[gamepad_type], NULL, 0); + core.set_controller_port_device(0, device); + } + for (int i=0; config.core.options[i].key; i++) { + Option* option = &config.core.options[i]; + // LOG_info("%s\n",option->key); + if (!Config_getValue(cfg, option->key, value, &option->lock)) continue; + OptionList_setOptionValue(&config.core, option->key, value); + } + Config_readShaderOptionsString(cfg); +} static void Config_readControlsString(char* cfg) { if (!cfg) return; @@ -509,6 +536,36 @@ static void Config_readControlsString(char* cfg) { mapping->mod = mod; } } + +static void Config_loadShaderSet(void) { + ShaderSetList list; + int active; + char path[MAX_PATH]; + + if (config.shader_set_cfg) { + free(config.shader_set_cfg); + config.shader_set_cfg = NULL; + } + if (config.shader_set_override_cfg) { + free(config.shader_set_override_cfg); + config.shader_set_override_cfg = NULL; + } + + if (!ShaderSets_list(&list)) + return; + + active = ShaderSets_activeIndex(&list); + if (active > 0 && ShaderSets_rootPath(list.names[active], path, sizeof(path))) + config.shader_set_cfg = allocFile(path); + + if (active > 0 && + ShaderSets_overridePath(list.names[active], core.tag, path, sizeof(path)) && + exists(path)) + config.shader_set_override_cfg = allocFile(path); + + ShaderSets_freeList(&list); +} + void Config_load(void) { LOG_info("Config_load\n"); @@ -568,21 +625,36 @@ void Config_load(void) { if (!override) Config_getPath(path, CONFIG_WRITE_ALL); config.user_cfg = allocFile(path); - if (!config.user_cfg) return; - - LOG_info("using user config: %s\n", path); - - config.loaded = override ? CONFIG_GAME : CONFIG_CONSOLE; + if (config.user_cfg) { + LOG_info("using user config: %s\n", path); + config.loaded = override ? CONFIG_GAME : CONFIG_CONSOLE; + } + + Config_loadShaderSet(); } void Config_free(void) { if (config.system_cfg) free(config.system_cfg); if (config.default_cfg) free(config.default_cfg); if (config.user_cfg) free(config.user_cfg); + if (config.shader_set_cfg) free(config.shader_set_cfg); + if (config.shader_set_override_cfg) free(config.shader_set_override_cfg); + if (config.shaders_preset) free(config.shaders_preset); + config.system_cfg = NULL; + config.default_cfg = NULL; + config.user_cfg = NULL; + config.shader_set_cfg = NULL; + config.shader_set_override_cfg = NULL; + config.shaders_preset = NULL; } void Config_readOptions(void) { Config_readOptionsString(config.system_cfg); Config_readOptionsString(config.default_cfg); - Config_readOptionsString(config.user_cfg); + if (config.loaded == CONFIG_CONSOLE) + Config_readOptionsString(config.user_cfg); + Config_readFrontendShaderOptionsString(config.shader_set_cfg, 1); + Config_readFrontendShaderOptionsString(config.shader_set_override_cfg, 1); + if (config.loaded == CONFIG_GAME) + Config_readOptionsString(config.user_cfg); } void Config_readControls(void) { Config_readControlsString(config.default_cfg); @@ -650,6 +722,15 @@ void Config_write(int override) { fclose(file); sync(); + + char *updated_user_cfg = allocFile(path); + if (updated_user_cfg) { + if (config.user_cfg) free(config.user_cfg); + config.user_cfg = updated_user_cfg; + } + else { + LOG_error("failed to refresh user config: %s\n", path); + } } void Config_restore(void) { char path[MAX_PATH]; @@ -698,10 +779,10 @@ void Config_restore(void) { mapping->mod = 0; } + Config_free(); Config_load(); Config_readOptions(); Config_readControls(); - Config_free(); renderer.dst_p = 0; } @@ -710,15 +791,35 @@ void readShadersPreset(int i) { char shaderspath[MAX_PATH] = {0}; sprintf(shaderspath, SHADERS_FOLDER "/%s", config.shaders.options[SH_SHADERS_PRESET].values[i]); LOG_info("read shaders preset %s\n",shaderspath); + if (config.shaders_preset) { + free(config.shaders_preset); + config.shaders_preset = NULL; + } if (exists(shaderspath)) { config.shaders_preset = allocFile(shaderspath); Config_readOptionsString(config.shaders_preset); } - else config.shaders_preset = NULL; +} + +static void freeShaderSettings(int i) { + if (!config.shaderpragmas[i].options) + return; + + for (int j = 0; j < config.shaderpragmas[i].count; j++) { + Option *option = &config.shaderpragmas[i].options[j]; + for (int k = 0; option->values && option->values[k]; k++) + free(option->values[k]); + free(option->values); + free(option->labels); + } + free(config.shaderpragmas[i].options); + config.shaderpragmas[i].options = NULL; + config.shaderpragmas[i].count = 0; } void loadShaderSettings(int i) { int menucount = 0; + freeShaderSettings(i); config.shaderpragmas[i].options = calloc(32 + 1, sizeof(Option)); ShaderParam *params = PLAT_getShaderPragmas(i); if(params == NULL) return; @@ -888,6 +989,128 @@ void initShaders() { } } +static void resetFrontendShaders(void) { + for (int i = 0; config.frontend.options[i].key; i++) { + config.frontend.options[i].value = config.frontend.options[i].default_value; + config.frontend.options[i].lock = 0; + } + for (int i = 0; config.shaders.options[i].key; i++) { + config.shaders.options[i].value = config.shaders.options[i].default_value; + config.shaders.options[i].lock = 0; + } + for (int i = 0; i < 3; i++) + freeShaderSettings(i); +} + +static void readEffectiveFrontendOptions(int sync) { + Config_readFrontendOptionsString(config.system_cfg, sync); + Config_readFrontendOptionsString(config.default_cfg, sync); + if (config.loaded == CONFIG_CONSOLE) + Config_readFrontendOptionsString(config.user_cfg, sync); + Config_readFrontendOptionsString(config.shader_set_cfg, sync); + Config_readFrontendOptionsString(config.shader_set_override_cfg, sync); + if (config.loaded == CONFIG_GAME) + Config_readFrontendOptionsString(config.user_cfg, sync); +} + +static void readEffectiveShaderOptions(void) { + Config_readShaderOptionsString(config.system_cfg); + Config_readShaderOptionsString(config.default_cfg); + if (config.loaded == CONFIG_CONSOLE) + Config_readShaderOptionsString(config.user_cfg); + Config_readShaderOptionsString(config.shader_set_cfg); + Config_readShaderOptionsString(config.shader_set_override_cfg); + if (config.loaded == CONFIG_GAME) + Config_readShaderOptionsString(config.user_cfg); +} + +static void readEffectiveFrontendShaders(int sync) { + readEffectiveFrontendOptions(sync); + readEffectiveShaderOptions(); +} + +static int shaderSetTouchesFrontendOption(int index) { + char value[256]; + const char *key = config.frontend.options[index].key; + + return (config.shader_set_cfg && + Config_getValue(config.shader_set_cfg, key, value, NULL)) || + (config.shader_set_override_cfg && + Config_getValue(config.shader_set_override_cfg, key, value, NULL)); +} + +static void resetShaderPragmas(int pass) { + ShaderParam *params = PLAT_getShaderPragmas(pass); + if (!params) + return; + + for (int i = 0; i < 32; i++) + params[i].value = params[i].def; +} + +static void reloadShaders(const int *old_values) { + for (int i = 0; config.shaders.options[i].key; i++) { + if (i == SH_EXTRASETTINGS || i == SH_SHADERS_PRESET) + continue; + + if (i == SH_SHADER1 || i == SH_SHADER2 || i == SH_SHADER3) { + int pass = i == SH_SHADER1 ? 0 : i == SH_SHADER2 ? 1 : 2; + if (config.shaders.options[i].value != old_values[i]) + Config_syncShaders(config.shaders.options[i].key, config.shaders.options[i].value); + else { + resetShaderPragmas(pass); + loadShaderSettings(pass); + } + } + else if (config.shaders.options[i].value != old_values[i]) { + Config_syncShaders(config.shaders.options[i].key, config.shaders.options[i].value); + } + } +} + +bool Config_reloadFrontendShaders(void) { + int old_values[FE_OPT_COUNT]; + int old_locks[FE_OPT_COUNT]; + int old_set_options[FE_OPT_COUNT]; + int old_shader_values[SH_NONE]; + int apply_overclock = 0; + int apply_sync_ref = 0; + + for (int i = 0; i < FE_OPT_COUNT; i++) { + old_values[i] = config.frontend.options[i].value; + old_locks[i] = config.frontend.options[i].lock; + old_set_options[i] = shaderSetTouchesFrontendOption(i); + } + for (int i = 0; i < SH_NONE; i++) + old_shader_values[i] = config.shaders.options[i].value; + + Config_loadShaderSet(); + resetFrontendShaders(); + readEffectiveFrontendShaders(0); + + for (int i = 0; i < FE_OPT_COUNT; i++) { + int new_set_option = shaderSetTouchesFrontendOption(i); + if (!old_set_options[i] && !new_set_option) { + config.frontend.options[i].value = old_values[i]; + config.frontend.options[i].lock = old_locks[i]; + } + else if (config.frontend.options[i].value != old_values[i]) { + Config_syncFrontend(config.frontend.options[i].key, config.frontend.options[i].value); + if (i == FE_OPT_OVERCLOCK) apply_overclock = 1; + if (i == FE_OPT_SYNC_REFERENCE) apply_sync_ref = 1; + } + } + + if (apply_overclock) setOverclock(overclock); + if (apply_sync_ref) chooseSyncRef(); + + reloadShaders(old_shader_values); + readEffectiveShaderOptions(); + applyShaderSettings(); + apply_live_video_reset(); + return true; +} + /* ----------------------------------------------------------------------- Config data: label arrays, button mappings, and struct Config initializer. Moved from minarch.c; these are the static data backing the config module. @@ -1717,6 +1940,7 @@ struct Config config = { [SHORTCUT_HOLD_REWIND] = {"Hold Rewind", -1, BTN_ID_NONE, 0}, [SHORTCUT_GAMESWITCHER] = {"Game Switcher", -1, BTN_ID_NONE, 0}, [SHORTCUT_SCREENSHOT] = {"Screenshot", -1, BTN_ID_NONE, 0}, + [SHORTCUT_NEXT_SHADER_SET] = {"Next Shader Set", -1, BTN_ID_NONE, 0}, // Trimui only [SHORTCUT_TOGGLE_TURBO_A] = {"Toggle Turbo A", -1, BTN_ID_NONE, 0}, [SHORTCUT_TOGGLE_TURBO_B] = {"Toggle Turbo B", -1, BTN_ID_NONE, 0}, @@ -1730,5 +1954,3 @@ struct Config config = { {NULL} }, }; - - diff --git a/workspace/all/minarch/ma_config.h b/workspace/all/minarch/ma_config.h index 85293afbd..0da8d9f2f 100644 --- a/workspace/all/minarch/ma_config.h +++ b/workspace/all/minarch/ma_config.h @@ -13,6 +13,7 @@ void Config_readOptions(void); void Config_readControls(void); void Config_write(int override); void Config_restore(void); +bool Config_reloadFrontendShaders(void); void Config_syncShaders(char* key, int value); void applyShaderSettings(void); void initShaders(void); diff --git a/workspace/all/minarch/ma_input.c b/workspace/all/minarch/ma_input.c index 3233b214d..5aef0ad7d 100644 --- a/workspace/all/minarch/ma_input.c +++ b/workspace/all/minarch/ma_input.c @@ -1,6 +1,9 @@ #include "ma_internal.h" #include "ma_input.h" +#include "notification.h" +#include "shader_sets.h" +#include #include int setFastForward(int enable) { @@ -12,6 +15,37 @@ int setFastForward(int enable) { return val; } +static void nextShaderSet(void) { + ShaderSetList list; + char name[MAX_PATH]; + char message[NOTIFICATION_MAX_MESSAGE]; + + if (!ShaderSets_list(&list)) { + Notification_push(NOTIFICATION_SETTING, "Unable to read shader sets", NULL); + return; + } + + if (list.count == 1) { + if (ShaderSets_setActive("")) + Notification_push(NOTIFICATION_SETTING, "Shader set: Disabled", NULL); + else + Notification_push(NOTIFICATION_SETTING, "Unable to change shader set", NULL); + ShaderSets_freeList(&list); + return; + } + + if (!ShaderSets_advance(&list, name, sizeof(name))) { + ShaderSets_freeList(&list); + Notification_push(NOTIFICATION_SETTING, "Unable to change shader set", NULL); + return; + } + + Config_reloadFrontendShaders(); + snprintf(message, sizeof(message), "Shader set: %s", ShaderSets_displayName(name)); + Notification_push(NOTIFICATION_SETTING, message, NULL); + ShaderSets_freeList(&list); +} + static uint32_t buttons = 0; // RETRO_DEVICE_ID_JOYPAD_* buttons static int ignore_menu = 0; void input_poll_callback(void) { @@ -167,6 +201,9 @@ void input_poll_callback(void) { screen_effect = (screen_effect + 1) % config.frontend.options[FE_OPT_EFFECT].count; Config_syncFrontend(config.frontend.options[FE_OPT_EFFECT].key, screen_effect); break; + case SHORTCUT_NEXT_SHADER_SET: + nextShaderSet(); + break; default: break; } diff --git a/workspace/all/minarch/ma_internal.h b/workspace/all/minarch/ma_internal.h index 49d4a8935..556e8b186 100644 --- a/workspace/all/minarch/ma_internal.h +++ b/workspace/all/minarch/ma_internal.h @@ -182,6 +182,8 @@ struct Config { char* system_cfg; char* default_cfg; char* user_cfg; + char* shader_set_cfg; + char* shader_set_override_cfg; char* shaders_preset; char* device_tag; OptionList frontend; @@ -245,6 +247,7 @@ enum { SHORTCUT_HOLD_REWIND, SHORTCUT_GAMESWITCHER, SHORTCUT_SCREENSHOT, + SHORTCUT_NEXT_SHADER_SET, // Trimui only SHORTCUT_TOGGLE_TURBO_A, SHORTCUT_TOGGLE_TURBO_B, diff --git a/workspace/all/minarch/makefile b/workspace/all/minarch/makefile index ae10f2529..45446fd4f 100644 --- a/workspace/all/minarch/makefile +++ b/workspace/all/minarch/makefile @@ -24,7 +24,7 @@ PRODUCT= build/$(PLATFORM)/$(TARGET).elf INCDIR = -I. -I./libretro-common/include/ -I../common/ -I../../$(PLATFORM)/platform/ SOURCE = $(TARGET).c ma_cheats.c ma_rewind.c ma_audio.c ma_input.c \ ma_options.c ma_frontend_opts.c ma_saves.c ma_video.c ma_core.c ma_game.c ma_environment.c ma_config.c ma_menu.c ma_runframe.c \ - ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c \ + ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../common/shader_sets.c \ ../common/notification.c ../../$(PLATFORM)/platform/platform.c # RA support diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index b3e156eda..3b754e875 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -245,8 +245,6 @@ int main(int argc , char* argv[]) { int rewind_initialized = Rewind_init(core.serialize_size ? core.serialize_size() : 0); rewind_init_ready = 1; // Mark setup as attempted, even if rewind init failed, so option changes can retry it later. if (rewind_initialized && core.serialize_size) Rewind_on_state_change(); - // release config when all is loaded - Config_free(); LOG_info("total startup time %ims\n\n",SDL_GetTicks()); diff --git a/workspace/all/shadersets/Makefile b/workspace/all/shadersets/Makefile new file mode 100644 index 000000000..df46e06c0 --- /dev/null +++ b/workspace/all/shadersets/Makefile @@ -0,0 +1,41 @@ +########################################################### + +ifeq (,$(PLATFORM)) +PLATFORM=$(UNION_PLATFORM) +endif + +ifeq (,$(PLATFORM)) +$(error please specify PLATFORM, eg. PLATFORM=trimui make) +endif + +ifeq (,$(CROSS_COMPILE)) +$(error missing CROSS_COMPILE for this toolchain) +endif + +########################################################### + +include ../../$(PLATFORM)/platform/makefile.env +SDL?=SDL + +########################################################### + +TARGET = shadersets +INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ +SOURCE = $(TARGET).c ../common/shader_sets.c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c + +CC = $(CROSS_COMPILE)gcc +CFLAGS += $(OPT) +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +LDFLAGS += -lmsettings + +PRODUCT = build/$(PLATFORM)/$(TARGET).elf + +all: $(PREFIX_LOCAL)/include/msettings.h + mkdir -p build/$(PLATFORM) + $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + +clean: + rm -f $(PRODUCT) + +$(PREFIX_LOCAL)/include/msettings.h: + cd ../../$(PLATFORM)/libmsettings && make diff --git a/workspace/all/shadersets/shadersets.c b/workspace/all/shadersets/shadersets.c new file mode 100644 index 000000000..bfc1df24f --- /dev/null +++ b/workspace/all/shadersets/shadersets.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +#include + +#include "defines.h" +#include "api.h" +#include "shader_sets.h" + +#define SET_ROW_PADDING 8 + +static volatile sig_atomic_t quit; + +static void sigHandler(int sig) +{ + if (sig == SIGINT || sig == SIGTERM) + quit = 1; +} + +static void renderTitle(SDL_Surface *screen, int reserved_width) +{ + int max_width = screen->w - SCALE1(PADDING * 2) - reserved_width; + char title[256]; + int text_width = GFX_truncateText(font.large, "Shader Sets", title, + max_width, SCALE1(BUTTON_PADDING * 2)); + SDL_Surface *text; + + max_width = MIN(max_width, text_width); + text = TTF_RenderUTF8_Blended(font.large, title, COLOR_WHITE); + GFX_blitPill(ASSET_BLACK_PILL, screen, + &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); + SDL_BlitSurface(text, + &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, + screen, + &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); + SDL_FreeSurface(text); +} + +static void renderSelection(SDL_Surface *screen, const char *name) +{ + int width = screen->w - SCALE1(PADDING * 2); + int y = (screen->h - SCALE1(BUTTON_SIZE)) / 2; + const char *label = "Shader set"; + const char *value = ShaderSets_displayName(name); + char display_value[256]; + SDL_Surface *label_text; + SDL_Surface *value_text; + + GFX_truncateText(font.tiny, value, display_value, + width / 2, SCALE1(SET_ROW_PADDING)); + GFX_blitPillLight(ASSET_BUTTON, screen, + &(SDL_Rect){SCALE1(PADDING), y, width, SCALE1(BUTTON_SIZE)}); + + label_text = TTF_RenderUTF8_Blended(font.small, label, COLOR_BLACK); + value_text = TTF_RenderUTF8_Blended(font.tiny, display_value, COLOR_BLACK); + + SDL_BlitSurface(label_text, NULL, screen, + &(SDL_Rect){ + SCALE1(PADDING + SET_ROW_PADDING), + y + (SCALE1(BUTTON_SIZE) - label_text->h) / 2 + }); + SDL_BlitSurface(value_text, NULL, screen, + &(SDL_Rect){ + screen->w - SCALE1(PADDING + SET_ROW_PADDING) - value_text->w, + y + (SCALE1(BUTTON_SIZE) - value_text->h) / 2 + }); + + SDL_FreeSurface(label_text); + SDL_FreeSurface(value_text); +} + +int main(int argc, char *argv[]) +{ + ShaderSetList list; + SDL_Surface *screen; + int selected; + int dirty = 1; + int show_setting = 0; + int was_online; + int had_bt; + bool save_error = false; + + (void)argc; + (void)argv; + + InitSettings(); + PWR_setCPUSpeed(CPU_SPEED_AUTO); + screen = GFX_init(MODE_MAIN); + PAD_init(); + PWR_init(); + VIB_init(); + + signal(SIGINT, sigHandler); + signal(SIGTERM, sigHandler); + + if (!ShaderSets_list(&list)) { + LOG_error("Unable to enumerate shader sets\n"); + QuitSettings(); + VIB_quit(); + PWR_quit(); + PAD_quit(); + GFX_quit(); + return EXIT_FAILURE; + } + + selected = ShaderSets_activeIndex(&list); + was_online = PWR_isOnline(); + had_bt = PLAT_btIsConnected(); + + while (!quit) { + GFX_startFrame(); + PAD_poll(); + + if (PAD_justRepeated(BTN_LEFT)) { + selected = (selected - 1 + list.count) % list.count; + save_error = false; + dirty = 1; + } + else if (PAD_justRepeated(BTN_RIGHT)) { + selected = (selected + 1) % list.count; + save_error = false; + dirty = 1; + } + else if (PAD_justPressed(BTN_A)) { + if (ShaderSets_setActive(list.names[selected])) + quit = 1; + else { + LOG_error("Unable to save shader set selection\n"); + if (CFG_getHaptics()) + VIB_triplePulse(5, 150, 200); + save_error = true; + dirty = 1; + } + } + else if (PAD_justPressed(BTN_B)) { + quit = 1; + } + + PWR_update(&dirty, &show_setting, NULL, NULL); + + int is_online = PWR_isOnline(); + if (was_online != is_online) + dirty = 1; + was_online = is_online; + + int has_bt = PLAT_btIsConnected(); + if (had_bt != has_bt) + dirty = 1; + had_bt = has_bt; + + if (dirty) { + GFX_clear(screen); + int reserved_width = GFX_blitHardwareGroup(screen, show_setting); + + renderTitle(screen, reserved_width); + renderSelection(screen, list.names[selected]); + + if (save_error) { + GFX_blitWrappedText(font.tiny, "Could not save the selected shader set.", + screen->w - SCALE1(PADDING * 2), 2, COLOR_LIGHT_TEXT, + screen, SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN)); + } + else if (list.count == 1) { + GFX_blitWrappedText(font.tiny, + "No set configs found in /Shaders/sets.", + screen->w - SCALE1(PADDING * 2), 2, COLOR_LIGHT_TEXT, + screen, SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN)); + } + + if (show_setting) + GFX_blitHardwareHints(screen, show_setting); + else + GFX_blitButtonGroup((char *[]){"L/R", "CHOOSE", NULL}, 0, screen, 0); + GFX_blitButtonGroup((char *[]){"B", "CANCEL", "A", "APPLY", NULL}, 1, screen, 1); + + GFX_flip(screen); + dirty = 0; + } + else { + GFX_sync(); + } + } + + ShaderSets_freeList(&list); + QuitSettings(); + VIB_quit(); + PWR_quit(); + PAD_quit(); + GFX_quit(); + return EXIT_SUCCESS; +} diff --git a/workspace/makefile b/workspace/makefile index f71af4d62..261795e78 100644 --- a/workspace/makefile +++ b/workspace/makefile @@ -22,6 +22,7 @@ ifeq ($(PLATFORM), desktop) #cd ./all/libbatmondb/ && make #cd ./all/battery/ && make cd ./all/clock/ && make + cd ./all/shadersets/ && make #cd ./all/batmon/ && make #cd ./all/libgametimedb/ && make #cd ./all/gametimectl/ && make @@ -53,6 +54,7 @@ endif cd ./all/settings/ && make cd ./all/ledcontrol/ && make cd ./all/bootlogo/ && make + cd ./all/shadersets/ && make cd ./all/audiomon && make all cd ./all/show2/ && make endif @@ -91,6 +93,7 @@ endif cd ./all/minarch/ && make clean cd ./all/battery/ && make clean cd ./all/clock/ && make clean + cd ./all/shadersets/ && make clean cd ./all/libbatmondb/ && make clean cd ./all/libgametimedb/ && make clean cd ./all/gametimectl/ && make clean From 3df2e4ceebfc01f463cce48d827021976ee9ffe2 Mon Sep 17 00:00:00 2001 From: sinedied Date: Mon, 27 Jul 2026 16:10:29 +0200 Subject: [PATCH 2/9] refactor: remove shader sets tool and move shader set option to shader config --- makefile | 1 - skeleton/BASE/README.txt | 4 +- .../Tools/desktop/Shader Sets.pak/launch.sh | 4 - .../Tools/tg5040/Shader Sets.pak/launch.sh | 4 - .../Tools/tg5050/Shader Sets.pak/launch.sh | 4 - workspace/all/minarch/ma_frontend_opts.c | 109 ++++++++-- workspace/all/minarch/ma_input.c | 6 +- workspace/all/shadersets/Makefile | 41 ---- workspace/all/shadersets/shadersets.c | 193 ------------------ workspace/makefile | 3 - 10 files changed, 105 insertions(+), 264 deletions(-) delete mode 100755 skeleton/EXTRAS/Tools/desktop/Shader Sets.pak/launch.sh delete mode 100755 skeleton/EXTRAS/Tools/tg5040/Shader Sets.pak/launch.sh delete mode 100755 skeleton/EXTRAS/Tools/tg5050/Shader Sets.pak/launch.sh delete mode 100644 workspace/all/shadersets/Makefile delete mode 100644 workspace/all/shadersets/shadersets.c diff --git a/makefile b/makefile index 4279c445e..fed3da9f6 100644 --- a/makefile +++ b/makefile @@ -101,7 +101,6 @@ endif cp ./workspace/all/minarch/build/$(PLATFORM)/minarch.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/nextval/build/$(PLATFORM)/nextval.elf ./build/SYSTEM/$(PLATFORM)/bin/ cp ./workspace/all/clock/build/$(PLATFORM)/clock.elf ./build/EXTRAS/Tools/$(PLATFORM)/Clock.pak/ - cp ./workspace/all/shadersets/build/$(PLATFORM)/shadersets.elf ./build/EXTRAS/Tools/$(PLATFORM)/Shader\ Sets.pak/ cp ./workspace/all/minput/build/$(PLATFORM)/minput.elf ./build/EXTRAS/Tools/$(PLATFORM)/Input.pak/ cp ./workspace/all/settings/build/$(PLATFORM)/settings.elf ./build/EXTRAS/Tools/$(PLATFORM)/Settings.pak/ ifneq (,$(filter $(PLATFORM),tg5040 tg5050)) diff --git a/skeleton/BASE/README.txt b/skeleton/BASE/README.txt index 2faa1d2f8..722680aed 100644 --- a/skeleton/BASE/README.txt +++ b/skeleton/BASE/README.txt @@ -100,7 +100,7 @@ Cheat file name needs to match ROM name, and go underneath the "Cheats" director ---------------------------------------- Shader sets -The Shader Sets tool applies one frontend and shader configuration across all Minarch emulators. Set configs live in `/Shaders/sets`. Each `.cfg` file directly inside that folder appears as a selectable set. Select Disabled to use the normal emulator settings without a global set. +The first option in the in-game Shaders menu applies one frontend and shader configuration across all Minarch emulators. Set configs live in `/Shaders/sets`. Each `.cfg` file directly inside that folder appears as a selectable set. Select Disabled to use the normal emulator settings without a global set. Changes are saved globally and applied immediately. A set can be adjusted for a specific emulator tag by adding a config with the same filename inside a tag subfolder. For example, `/Shaders/sets/Retro.cfg` is the fallback for every emulator and `/Shaders/sets/GBA/Retro.cfg` overrides its values for GBA games. @@ -108,6 +108,8 @@ Set configs support `minarch_` frontend, shader, and shader-parameter options on The in-game Shortcuts menu includes Next Shader Set. Bind it to cycle through Disabled and the available sets, apply the new set immediately, and show the selected name in a notification. +Saving console or per-game settings while a shader set is active saves the currently effective shader values into that emulator config. Select Disabled before saving if you do not want the set values included. + ---------------------------------------- Disc-based games diff --git a/skeleton/EXTRAS/Tools/desktop/Shader Sets.pak/launch.sh b/skeleton/EXTRAS/Tools/desktop/Shader Sets.pak/launch.sh deleted file mode 100755 index 94269b8e0..000000000 --- a/skeleton/EXTRAS/Tools/desktop/Shader Sets.pak/launch.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -cd "$(dirname "$0")" -./shadersets.elf diff --git a/skeleton/EXTRAS/Tools/tg5040/Shader Sets.pak/launch.sh b/skeleton/EXTRAS/Tools/tg5040/Shader Sets.pak/launch.sh deleted file mode 100755 index 94269b8e0..000000000 --- a/skeleton/EXTRAS/Tools/tg5040/Shader Sets.pak/launch.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -cd "$(dirname "$0")" -./shadersets.elf diff --git a/skeleton/EXTRAS/Tools/tg5050/Shader Sets.pak/launch.sh b/skeleton/EXTRAS/Tools/tg5050/Shader Sets.pak/launch.sh deleted file mode 100755 index 94269b8e0..000000000 --- a/skeleton/EXTRAS/Tools/tg5050/Shader Sets.pak/launch.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -cd "$(dirname "$0")" -./shadersets.elf diff --git a/workspace/all/minarch/ma_frontend_opts.c b/workspace/all/minarch/ma_frontend_opts.c index 3c786b96c..c1816b372 100644 --- a/workspace/all/minarch/ma_frontend_opts.c +++ b/workspace/all/minarch/ma_frontend_opts.c @@ -3,6 +3,7 @@ #include "ma_cheats.h" #include "ra_integration.h" #include "notification.h" +#include "shader_sets.h" #include #include @@ -651,19 +652,54 @@ static int OptionPragmas_openMenu(MenuList* list, int i) { return MENU_CALLBACK_NOP; } + +#define SHADER_SET_MENU_ID -1 + +static ShaderSetList ShaderSetOptions = {0}; +static char **ShaderSetLabels = NULL; + +static void OptionShaders_freeFileList(char **files) { + if (!files) + return; + for (int i = 0; files[i]; i++) + free(files[i]); + free(files); +} + +static void OptionShaders_refreshItems(MenuList *list) { + for (int i = 0; i < config.shaders.count; i++) + list->items[i + 1].value = config.shaders.options[i].value; +} + static int OptionShaders_optionChanged(MenuList* list, int i) { MenuItem* item = &list->items[i]; + if (item->id == SHADER_SET_MENU_ID) { + int previous = ShaderSets_activeIndex(&ShaderSetOptions); + if (item->value == previous) + return MENU_CALLBACK_NOP; + + if (item->value < 0 || item->value >= ShaderSetOptions.count || + !ShaderSets_setActive(ShaderSetOptions.names[item->value])) { + item->value = previous; + Menu_message("Unable to save shader set", (char*[]){"B", "BACK", NULL}); + return MENU_CALLBACK_NOP; + } + + Config_reloadFrontendShaders(); + item->value = ShaderSets_activeIndex(&ShaderSetOptions); + OptionShaders_refreshItems(list); + return MENU_CALLBACK_NOP; + } + + int shader_index = item->id; // Process menu entry change, update underlying config cruft and call handler Config_syncShaders(item->key, item->value); // Apply shader pragmas if needed applyShaderSettings(); // Update menu entries to reflect any changes made by the handler - for (int y = 0; y < config.shaders.count; y++) { - MenuItem* item = &list->items[y]; - item->value = config.shaders.options[y].value; - } + OptionShaders_refreshItems(list); - if(i==SH_SHADERS_PRESET) { + if(shader_index==SH_SHADERS_PRESET) { // On shader preset change: // Push all new shader settings to shader engine, // compile shaders if needed, populate pragmas list @@ -696,9 +732,54 @@ int OptionShaders_openMenu(MenuList* list, int i) { return MENU_CALLBACK_NOP; } - ShaderOptions_menu.items = calloc(config.shaders.count + 1, sizeof(MenuItem)); + if (!ShaderSets_list(&ShaderSetOptions)) { + OptionShaders_freeFileList(filelist); + Menu_message("Unable to read shader sets", (char*[]){"B", "BACK", NULL}); + return MENU_CALLBACK_NOP; + } + + ShaderSetLabels = calloc(ShaderSetOptions.count + 1, sizeof(char *)); + if (!ShaderSetLabels) { + OptionShaders_freeFileList(filelist); + ShaderSets_freeList(&ShaderSetOptions); + Menu_message("Unable to read shader sets", (char*[]){"B", "BACK", NULL}); + return MENU_CALLBACK_NOP; + } + for (int i = 0; i < ShaderSetOptions.count; i++) { + ShaderSetLabels[i] = strdup(ShaderSets_displayName(ShaderSetOptions.names[i])); + if (!ShaderSetLabels[i]) { + for (int j = 0; j < i; j++) + free(ShaderSetLabels[j]); + free(ShaderSetLabels); + ShaderSetLabels = NULL; + OptionShaders_freeFileList(filelist); + ShaderSets_freeList(&ShaderSetOptions); + Menu_message("Unable to read shader sets", (char*[]){"B", "BACK", NULL}); + return MENU_CALLBACK_NOP; + } + } + + ShaderOptions_menu.items = calloc(config.shaders.count + 2, sizeof(MenuItem)); + if (!ShaderOptions_menu.items) { + for (int i = 0; i < ShaderSetOptions.count; i++) + free(ShaderSetLabels[i]); + free(ShaderSetLabels); + ShaderSetLabels = NULL; + OptionShaders_freeFileList(filelist); + ShaderSets_freeList(&ShaderSetOptions); + Menu_message("Unable to open shader settings", (char*[]){"B", "BACK", NULL}); + return MENU_CALLBACK_NOP; + } + + MenuItem *set_item = &ShaderOptions_menu.items[0]; + set_item->id = SHADER_SET_MENU_ID; + set_item->name = "Shader Set"; + set_item->desc = "Globally applies a frontend and shader configuration immediately."; + set_item->value = ShaderSets_activeIndex(&ShaderSetOptions); + set_item->values = ShaderSetLabels; + for (int i = 0; i < config.shaders.count; i++) { - MenuItem* item = &ShaderOptions_menu.items[i]; + MenuItem* item = &ShaderOptions_menu.items[i + 1]; Option* configitem = &config.shaders.options[i]; item->id = i; item->name = configitem->name; @@ -718,11 +799,15 @@ int OptionShaders_openMenu(MenuList* list, int i) { } - if (ShaderOptions_menu.items[0].name) { - Menu_options(&ShaderOptions_menu); - } else { - Menu_message("No shaders available\n/Shaders folder or shader files not found", (char*[]){"B", "BACK", NULL}); - } + Menu_options(&ShaderOptions_menu); + + free(ShaderOptions_menu.items); + ShaderOptions_menu.items = NULL; + for (int i = 0; i < ShaderSetOptions.count; i++) + free(ShaderSetLabels[i]); + free(ShaderSetLabels); + ShaderSetLabels = NULL; + ShaderSets_freeList(&ShaderSetOptions); return MENU_CALLBACK_NOP; } diff --git a/workspace/all/minarch/ma_input.c b/workspace/all/minarch/ma_input.c index 5aef0ad7d..f49383939 100644 --- a/workspace/all/minarch/ma_input.c +++ b/workspace/all/minarch/ma_input.c @@ -26,8 +26,12 @@ static void nextShaderSet(void) { } if (list.count == 1) { - if (ShaderSets_setActive("")) + int had_active_set = config.shader_set_cfg || config.shader_set_override_cfg; + if (ShaderSets_setActive("")) { + if (had_active_set) + Config_reloadFrontendShaders(); Notification_push(NOTIFICATION_SETTING, "Shader set: Disabled", NULL); + } else Notification_push(NOTIFICATION_SETTING, "Unable to change shader set", NULL); ShaderSets_freeList(&list); diff --git a/workspace/all/shadersets/Makefile b/workspace/all/shadersets/Makefile deleted file mode 100644 index df46e06c0..000000000 --- a/workspace/all/shadersets/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -########################################################### - -ifeq (,$(PLATFORM)) -PLATFORM=$(UNION_PLATFORM) -endif - -ifeq (,$(PLATFORM)) -$(error please specify PLATFORM, eg. PLATFORM=trimui make) -endif - -ifeq (,$(CROSS_COMPILE)) -$(error missing CROSS_COMPILE for this toolchain) -endif - -########################################################### - -include ../../$(PLATFORM)/platform/makefile.env -SDL?=SDL - -########################################################### - -TARGET = shadersets -INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/shader_sets.c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c - -CC = $(CROSS_COMPILE)gcc -CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 -LDFLAGS += -lmsettings - -PRODUCT = build/$(PLATFORM)/$(TARGET).elf - -all: $(PREFIX_LOCAL)/include/msettings.h - mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) - -clean: - rm -f $(PRODUCT) - -$(PREFIX_LOCAL)/include/msettings.h: - cd ../../$(PLATFORM)/libmsettings && make diff --git a/workspace/all/shadersets/shadersets.c b/workspace/all/shadersets/shadersets.c deleted file mode 100644 index bfc1df24f..000000000 --- a/workspace/all/shadersets/shadersets.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "defines.h" -#include "api.h" -#include "shader_sets.h" - -#define SET_ROW_PADDING 8 - -static volatile sig_atomic_t quit; - -static void sigHandler(int sig) -{ - if (sig == SIGINT || sig == SIGTERM) - quit = 1; -} - -static void renderTitle(SDL_Surface *screen, int reserved_width) -{ - int max_width = screen->w - SCALE1(PADDING * 2) - reserved_width; - char title[256]; - int text_width = GFX_truncateText(font.large, "Shader Sets", title, - max_width, SCALE1(BUTTON_PADDING * 2)); - SDL_Surface *text; - - max_width = MIN(max_width, text_width); - text = TTF_RenderUTF8_Blended(font.large, title, COLOR_WHITE); - GFX_blitPill(ASSET_BLACK_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, - &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, - screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); - SDL_FreeSurface(text); -} - -static void renderSelection(SDL_Surface *screen, const char *name) -{ - int width = screen->w - SCALE1(PADDING * 2); - int y = (screen->h - SCALE1(BUTTON_SIZE)) / 2; - const char *label = "Shader set"; - const char *value = ShaderSets_displayName(name); - char display_value[256]; - SDL_Surface *label_text; - SDL_Surface *value_text; - - GFX_truncateText(font.tiny, value, display_value, - width / 2, SCALE1(SET_ROW_PADDING)); - GFX_blitPillLight(ASSET_BUTTON, screen, - &(SDL_Rect){SCALE1(PADDING), y, width, SCALE1(BUTTON_SIZE)}); - - label_text = TTF_RenderUTF8_Blended(font.small, label, COLOR_BLACK); - value_text = TTF_RenderUTF8_Blended(font.tiny, display_value, COLOR_BLACK); - - SDL_BlitSurface(label_text, NULL, screen, - &(SDL_Rect){ - SCALE1(PADDING + SET_ROW_PADDING), - y + (SCALE1(BUTTON_SIZE) - label_text->h) / 2 - }); - SDL_BlitSurface(value_text, NULL, screen, - &(SDL_Rect){ - screen->w - SCALE1(PADDING + SET_ROW_PADDING) - value_text->w, - y + (SCALE1(BUTTON_SIZE) - value_text->h) / 2 - }); - - SDL_FreeSurface(label_text); - SDL_FreeSurface(value_text); -} - -int main(int argc, char *argv[]) -{ - ShaderSetList list; - SDL_Surface *screen; - int selected; - int dirty = 1; - int show_setting = 0; - int was_online; - int had_bt; - bool save_error = false; - - (void)argc; - (void)argv; - - InitSettings(); - PWR_setCPUSpeed(CPU_SPEED_AUTO); - screen = GFX_init(MODE_MAIN); - PAD_init(); - PWR_init(); - VIB_init(); - - signal(SIGINT, sigHandler); - signal(SIGTERM, sigHandler); - - if (!ShaderSets_list(&list)) { - LOG_error("Unable to enumerate shader sets\n"); - QuitSettings(); - VIB_quit(); - PWR_quit(); - PAD_quit(); - GFX_quit(); - return EXIT_FAILURE; - } - - selected = ShaderSets_activeIndex(&list); - was_online = PWR_isOnline(); - had_bt = PLAT_btIsConnected(); - - while (!quit) { - GFX_startFrame(); - PAD_poll(); - - if (PAD_justRepeated(BTN_LEFT)) { - selected = (selected - 1 + list.count) % list.count; - save_error = false; - dirty = 1; - } - else if (PAD_justRepeated(BTN_RIGHT)) { - selected = (selected + 1) % list.count; - save_error = false; - dirty = 1; - } - else if (PAD_justPressed(BTN_A)) { - if (ShaderSets_setActive(list.names[selected])) - quit = 1; - else { - LOG_error("Unable to save shader set selection\n"); - if (CFG_getHaptics()) - VIB_triplePulse(5, 150, 200); - save_error = true; - dirty = 1; - } - } - else if (PAD_justPressed(BTN_B)) { - quit = 1; - } - - PWR_update(&dirty, &show_setting, NULL, NULL); - - int is_online = PWR_isOnline(); - if (was_online != is_online) - dirty = 1; - was_online = is_online; - - int has_bt = PLAT_btIsConnected(); - if (had_bt != has_bt) - dirty = 1; - had_bt = has_bt; - - if (dirty) { - GFX_clear(screen); - int reserved_width = GFX_blitHardwareGroup(screen, show_setting); - - renderTitle(screen, reserved_width); - renderSelection(screen, list.names[selected]); - - if (save_error) { - GFX_blitWrappedText(font.tiny, "Could not save the selected shader set.", - screen->w - SCALE1(PADDING * 2), 2, COLOR_LIGHT_TEXT, - screen, SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN)); - } - else if (list.count == 1) { - GFX_blitWrappedText(font.tiny, - "No set configs found in /Shaders/sets.", - screen->w - SCALE1(PADDING * 2), 2, COLOR_LIGHT_TEXT, - screen, SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN)); - } - - if (show_setting) - GFX_blitHardwareHints(screen, show_setting); - else - GFX_blitButtonGroup((char *[]){"L/R", "CHOOSE", NULL}, 0, screen, 0); - GFX_blitButtonGroup((char *[]){"B", "CANCEL", "A", "APPLY", NULL}, 1, screen, 1); - - GFX_flip(screen); - dirty = 0; - } - else { - GFX_sync(); - } - } - - ShaderSets_freeList(&list); - QuitSettings(); - VIB_quit(); - PWR_quit(); - PAD_quit(); - GFX_quit(); - return EXIT_SUCCESS; -} diff --git a/workspace/makefile b/workspace/makefile index 261795e78..f71af4d62 100644 --- a/workspace/makefile +++ b/workspace/makefile @@ -22,7 +22,6 @@ ifeq ($(PLATFORM), desktop) #cd ./all/libbatmondb/ && make #cd ./all/battery/ && make cd ./all/clock/ && make - cd ./all/shadersets/ && make #cd ./all/batmon/ && make #cd ./all/libgametimedb/ && make #cd ./all/gametimectl/ && make @@ -54,7 +53,6 @@ endif cd ./all/settings/ && make cd ./all/ledcontrol/ && make cd ./all/bootlogo/ && make - cd ./all/shadersets/ && make cd ./all/audiomon && make all cd ./all/show2/ && make endif @@ -93,7 +91,6 @@ endif cd ./all/minarch/ && make clean cd ./all/battery/ && make clean cd ./all/clock/ && make clean - cd ./all/shadersets/ && make clean cd ./all/libbatmondb/ && make clean cd ./all/libgametimedb/ && make clean cd ./all/gametimectl/ && make clean From 39d8f07c787a22110e5edd932d2b323a259b70da Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 28 Jul 2026 09:23:41 +0200 Subject: [PATCH 3/9] feat: save platform override when shader set is enabled --- skeleton/BASE/README.txt | 4 +- workspace/all/common/shader_sets.c | 40 ++ workspace/all/common/shader_sets.h | 1 + workspace/all/minarch/ma_config.c | 535 +++++++++++++++++++++-- workspace/all/minarch/ma_config.h | 7 +- workspace/all/minarch/ma_frontend_opts.c | 14 +- 6 files changed, 561 insertions(+), 40 deletions(-) diff --git a/skeleton/BASE/README.txt b/skeleton/BASE/README.txt index 722680aed..d0d68023d 100644 --- a/skeleton/BASE/README.txt +++ b/skeleton/BASE/README.txt @@ -108,7 +108,9 @@ Set configs support `minarch_` frontend, shader, and shader-parameter options on The in-game Shortcuts menu includes Next Shader Set. Bind it to cycle through Disabled and the available sets, apply the new set immediately, and show the selected name in a notification. -Saving console or per-game settings while a shader set is active saves the currently effective shader values into that emulator config. Select Disabled before saving if you do not want the set values included. +When a shader set is active, Save for console keeps normal emulator, control, and shortcut settings in the console config and writes frontend or shader changes to `/Shaders/sets//.cfg`. Only values differing from the effective root-set baseline are written, making the generated override suitable for review and sharing. + +Save for game continues to save the currently effective shader values into the game config. Game settings take priority over shader sets, so that game may retain those values when switching sets. ---------------------------------------- diff --git a/workspace/all/common/shader_sets.c b/workspace/all/common/shader_sets.c index 95764e696..a01fbd760 100644 --- a/workspace/all/common/shader_sets.c +++ b/workspace/all/common/shader_sets.c @@ -227,6 +227,46 @@ int ShaderSets_activeIndex(const ShaderSetList *list) return 0; } +bool ShaderSets_getActive(char *name, size_t name_size) +{ + ShaderSetList list; + FILE *file; + + if (!name || name_size == 0) + return false; + + name[0] = '\0'; + file = fopen(SHADER_SET_STATE_PATH, "r"); + if (!file) + return errno == ENOENT; + if (fgets(name, name_size, file)) + name[strcspn(name, "\r\n")] = '\0'; + if (ferror(file) || fclose(file) != 0) { + name[0] = '\0'; + return false; + } + if (!name[0]) + return true; + if (!isSafeComponent(name)) { + name[0] = '\0'; + return false; + } + + if (!ShaderSets_list(&list)) + return false; + + for (int i = 1; i < list.count; i++) { + if (!strcmp(list.names[i], name)) { + ShaderSets_freeList(&list); + return true; + } + } + + ShaderSets_freeList(&list); + name[0] = '\0'; + return false; +} + bool ShaderSets_setActive(const char *name) { char path[MAX_PATH]; diff --git a/workspace/all/common/shader_sets.h b/workspace/all/common/shader_sets.h index fb053702e..6e21f44ec 100644 --- a/workspace/all/common/shader_sets.h +++ b/workspace/all/common/shader_sets.h @@ -22,6 +22,7 @@ void ShaderSets_freeList(ShaderSetList *list); const char *ShaderSets_displayName(const char *name); int ShaderSets_activeIndex(const ShaderSetList *list); +bool ShaderSets_getActive(char *name, size_t name_size); bool ShaderSets_setActive(const char *name); bool ShaderSets_advance(const ShaderSetList *list, char *name, size_t name_size); diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.c index 9f8f4a18e..1bf7110ce 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "ma_internal.h" #include "ma_options.h" @@ -660,38 +663,24 @@ void Config_readControls(void) { Config_readControlsString(config.default_cfg); Config_readControlsString(config.user_cfg); } -void Config_write(int override) { - char path[MAX_PATH]; - // sprintf(path, "%s/%s.cfg", core.config_dir, game.alt_name); - Config_getPath(path, CONFIG_WRITE_GAME); - - if (!override) { - if (config.loaded==CONFIG_GAME) unlink(path); - Config_getPath(path, CONFIG_WRITE_ALL); - } - config.loaded = override ? CONFIG_GAME : CONFIG_CONSOLE; - - FILE *file = fopen(path, "wb"); - if (!file) return; - + +static int Config_writeFrontendShaders(FILE *file) { for (int i=0; config.frontend.options[i].key; i++) { Option* option = &config.frontend.options[i]; int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - fprintf(file, "%s = %s\n", option->key, option->values[option->value]); + if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + return 0; } } - for (int i=0; config.core.options[i].key; i++) { - Option* option = &config.core.options[i]; - fprintf(file, "%s = %s\n", option->key, option->values[option->value]); - } for (int i=0; config.shaders.options[i].key; i++) { Option* option = &config.shaders.options[i]; int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - fprintf(file, "%s = %s\n", option->key, option->values[option->value]); + if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + return 0; } } for (int y=0; y < config.shaders.options[SH_NROFSHADERS].value; y++) { @@ -700,37 +689,515 @@ void Config_write(int override) { int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - fprintf(file, "%s = %s\n", option->key, option->values[option->value]); + if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + return 0; } } } - - if (has_custom_controllers) fprintf(file, "%s = %i\n", "minarch_gamepad_type", gamepad_type); - + return 1; +} + +static int Config_writeNonvisual(FILE *file) { + for (int i=0; config.core.options[i].key; i++) { + Option* option = &config.core.options[i]; + if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + return 0; + } + + if (has_custom_controllers && + fprintf(file, "%s = %i\n", "minarch_gamepad_type", gamepad_type) < 0) + return 0; + for (int i=0; config.controls[i].name; i++) { ButtonMapping* mapping = &config.controls[i]; int j = mapping->local + 1; if (mapping->mod) j += LOCAL_BUTTON_COUNT; - fprintf(file, "bind %s = %s\n", mapping->name, button_labels[j]); + if (fprintf(file, "bind %s = %s\n", mapping->name, button_labels[j]) < 0) + return 0; } for (int i=0; config.shortcuts[i].name; i++) { ButtonMapping* mapping = &config.shortcuts[i]; int j = mapping->local + 1; if (mapping->mod) j += LOCAL_BUTTON_COUNT; - fprintf(file, "bind %s = %s\n", mapping->name, button_labels[j]); + if (fprintf(file, "bind %s = %s\n", mapping->name, button_labels[j]) < 0) + return 0; } - - fclose(file); - sync(); + return 1; +} - char *updated_user_cfg = allocFile(path); - if (updated_user_cfg) { - if (config.user_cfg) free(config.user_cfg); - config.user_cfg = updated_user_cfg; +static int Config_finishFile(FILE *file) { + if (fflush(file) != 0) { + fclose(file); + return 0; } - else { + int fd = fileno(file); + if (fd >= 0 && fsync(fd) != 0) { + fclose(file); + return 0; + } + return fclose(file) == 0; +} + +static int Config_refreshUserConfig(const char *path) { + char *updated_user_cfg = allocFile((char *)path); + if (!updated_user_cfg) { LOG_error("failed to refresh user config: %s\n", path); + return 0; + } + if (config.user_cfg) free(config.user_cfg); + config.user_cfg = updated_user_cfg; + return 1; +} + +static int Config_writeStandard(const char *path) { + FILE *file = fopen(path, "wb"); + if (!file) + return 0; + + if (!Config_writeFrontendShaders(file) || !Config_writeNonvisual(file)) { + fclose(file); + return 0; + } + if (!Config_finishFile(file)) + return 0; + + return Config_refreshUserConfig(path); +} + +static int Config_isNonvisualLine(const char *line, size_t len) { + char key[256]; + const char *start = line; + const char *delimiter; + + while (len && (*start == ' ' || *start == '\t')) { + start++; + len--; + } + if (len && *start == '-') { + start++; + len--; + } + if (len >= 5 && !strncmp(start, "bind ", 5)) + return 1; + + delimiter = NULL; + for (size_t i = 0; i + 2 < len; i++) { + if (start[i] == ' ' && start[i + 1] == '=' && start[i + 2] == ' ') { + delimiter = start + i; + break; + } + } + if (!delimiter) + return 0; + + size_t key_len = delimiter - start; + if (key_len >= sizeof(key)) + return 0; + memcpy(key, start, key_len); + key[key_len] = '\0'; + + if (!strcmp(key, "minarch_gamepad_type")) + return 1; + for (int i = 0; config.core.options[i].key; i++) { + if (!strcmp(key, config.core.options[i].key)) + return 1; } + return 0; +} + +static int Config_writePreservedVisual(FILE *file, const char *cfg) { + if (!cfg) + return 1; + + const char *line = cfg; + while (*line) { + const char *end = line; + while (*end && *end != '\n' && *end != '\r') + end++; + size_t len = end - line; + + if (!Config_isNonvisualLine(line, len)) { + if (len && fwrite(line, 1, len, file) != len) + return 0; + if (fputc('\n', file) == EOF) + return 0; + } + + while (*end == '\n' || *end == '\r') + end++; + line = end; + } + return 1; +} + +static int Config_resolveOptionIndex(Option *option, const char *console_cfg, + const char *root_cfg, int *lock) { + const char *layers[] = { + config.system_cfg, + config.default_cfg, + console_cfg, + root_cfg, + }; + int value_index = option->default_value; + char value[256]; + + if (lock) *lock = 0; + for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); i++) { + if (layers[i] && Config_getValue((char *)layers[i], option->key, value, lock)) + value_index = Option_getValueIndex(option, value); + } + return value_index; +} + +static int Config_resolvePragmaIndex(int pass, Option *option, int default_index, + const char *console_cfg, const char *root_cfg, int *lock) { + const char *layers[] = { + config.system_cfg, + config.default_cfg, + console_cfg, + root_cfg, + }; + int value = default_index; + int shader_count = config.shaders.options[SH_NROFSHADERS].default_value; + char serialized[256]; + + if (lock) *lock = 0; + for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); i++) { + if (!layers[i]) + continue; + if (Config_getValue((char *)layers[i], + config.shaders.options[SH_NROFSHADERS].key, serialized, NULL)) + shader_count = Option_getValueIndex( + &config.shaders.options[SH_NROFSHADERS], serialized); + if (pass < shader_count && + Config_getValue((char *)layers[i], option->key, serialized, lock)) + value = Option_getValueIndex(option, serialized); + } + return value; +} + +static int Config_getPragmaDefaultIndex(int pass, Option *option) { + ShaderParam *params = PLAT_getShaderPragmas(pass); + if (!params || !option->values) + return 0; + + for (int i = 0; i < 32; i++) { + if (!strcmp(params[i].name, option->key)) { + for (int j = 0; option->values[j]; j++) { + float candidate = strtof(option->values[j], NULL); + if (fabsf(params[i].def - candidate) < 0.001f) + return j; + } + break; + } + } + return 0; +} + +static int Config_writeOverrideDelta(FILE *file, const char *console_cfg, const char *root_cfg) { + int written = 0; + + for (int i = 0; config.frontend.options[i].key; i++) { + Option *option = &config.frontend.options[i]; + if (!option->values || option->value < 0 || !option->values[option->value]) + continue; + int baseline_lock = 0; + int baseline = Config_resolveOptionIndex(option, console_cfg, root_cfg, &baseline_lock); + if (option->value != baseline || option->lock != baseline_lock) { + if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) + return -1; + written++; + } + } + + for (int i = 0; config.shaders.options[i].key; i++) { + Option *option = &config.shaders.options[i]; + if (!option->values || option->value < 0 || !option->values[option->value]) + continue; + int baseline_lock = 0; + int baseline = Config_resolveOptionIndex(option, console_cfg, root_cfg, &baseline_lock); + if (option->value != baseline || option->lock != baseline_lock) { + if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) + return -1; + written++; + } + } + + for (int pass = 0; pass < config.shaders.options[SH_NROFSHADERS].value; pass++) { + for (int i = 0; i < config.shaderpragmas[pass].count; i++) { + Option *option = &config.shaderpragmas[pass].options[i]; + if (!option->values || option->value < 0 || !option->values[option->value]) + continue; + int baseline_lock = 0; + int baseline = Config_resolvePragmaIndex(pass, option, + Config_getPragmaDefaultIndex(pass, option), console_cfg, root_cfg, + &baseline_lock); + if (option->value != baseline || option->lock != baseline_lock) { + if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) + return -1; + written++; + } + } + } + return written; +} + +static int Config_makeTempPath(const char *path, char *temp_path, size_t temp_size) { + return snprintf(temp_path, temp_size, "%s.tmp.%ld", path, (long)getpid()) < (int)temp_size; +} + +static int Config_makeBackupPath(const char *path, char *backup_path, size_t backup_size) { + return snprintf(backup_path, backup_size, "%s.bak.%ld", path, (long)getpid()) < (int)backup_size; +} + +static int Config_copyFile(const char *source, const char *destination) { + FILE *input = fopen(source, "rb"); + if (!input) + return 0; + FILE *output = fopen(destination, "wb"); + if (!output) { + fclose(input); + return 0; + } + + char buffer[4096]; + size_t count; + int result = 1; + while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) { + if (fwrite(buffer, 1, count, output) != count) { + result = 0; + break; + } + } + if (ferror(input)) + result = 0; + fclose(input); + if (!result) { + fclose(output); + unlink(destination); + return 0; + } + if (!Config_finishFile(output)) { + unlink(destination); + return 0; + } + return 1; +} + +static void Config_restoreBackup(const char *backup_path, const char *path, int had_original) { + if (had_original) { + if (rename(backup_path, path) != 0) + LOG_error("failed to restore config: %s\n", path); + } + else { + unlink(path); + } +} + +static int Config_commitConsoleAndOverride(const char *console_temp, const char *console_path, + const char *override_temp, const char *override_path, int has_override, + const char *game_path, int remove_game) { + char console_backup[MAX_PATH]; + char override_backup[MAX_PATH]; + char game_backup[MAX_PATH]; + int console_backed_up = 0; + int override_backed_up = 0; + int game_backed_up = 0; + int console_installed = 0; + int override_modified = 0; + + if (!Config_makeBackupPath(console_path, console_backup, sizeof(console_backup)) || + !Config_makeBackupPath(override_path, override_backup, sizeof(override_backup)) || + (remove_game && !Config_makeBackupPath(game_path, game_backup, sizeof(game_backup)))) + return 0; + + unlink(console_backup); + unlink(override_backup); + if (remove_game) + unlink(game_backup); + + if (exists((char *)console_path)) { + if (!Config_copyFile(console_path, console_backup)) + goto rollback; + console_backed_up = 1; + } + if (exists((char *)override_path)) { + if (!Config_copyFile(override_path, override_backup)) + goto rollback; + override_backed_up = 1; + } + if (remove_game && exists((char *)game_path)) { + if (!Config_copyFile(game_path, game_backup)) + goto rollback; + game_backed_up = 1; + } + + if (rename(console_temp, console_path) != 0) + goto rollback; + console_installed = 1; + + if (has_override) { + if (rename(override_temp, override_path) != 0) + goto rollback; + override_modified = 1; + } + else if (unlink(override_path) != 0 && errno != ENOENT) { + goto rollback; + } + else { + override_modified = 1; + } + + if (remove_game && unlink(game_path) != 0 && errno != ENOENT) + goto rollback; + + unlink(console_backup); + unlink(override_backup); + if (remove_game) + unlink(game_backup); + return 1; + +rollback: + if (console_installed) + Config_restoreBackup(console_backup, console_path, console_backed_up); + else + unlink(console_backup); + if (override_modified) + Config_restoreBackup(override_backup, override_path, override_backed_up); + else + unlink(override_backup); + if (game_backed_up && !exists((char *)game_path)) + Config_restoreBackup(game_backup, game_path, 1); + else + unlink(game_backup); + return 0; +} + +static int Config_writeActiveSetConsole(const char *set_name, const char *game_path, + const char *console_path) { + char override_dir[MAX_PATH]; + char override_path[MAX_PATH]; + char root_path[MAX_PATH]; + char console_temp[MAX_PATH] = {0}; + char override_temp[MAX_PATH] = {0}; + char *console_cfg = allocFile((char *)console_path); + char *root_cfg = NULL; + char *new_user_cfg = NULL; + char *new_override_cfg = NULL; + FILE *console_file = NULL; + FILE *override_file = NULL; + int override_lines; + int result = 0; + + if (!ShaderSets_rootPath(set_name, root_path, sizeof(root_path)) || + !ShaderSets_overridePath(set_name, core.tag, override_path, sizeof(override_path)) || + snprintf(override_dir, sizeof(override_dir), "%s/%s", SHADER_SETS_PATH, core.tag) >= (int)sizeof(override_dir) || + !Config_makeTempPath(console_path, console_temp, sizeof(console_temp)) || + !Config_makeTempPath(override_path, override_temp, sizeof(override_temp))) + goto cleanup; + + root_cfg = allocFile(root_path); + if (!root_cfg) + goto cleanup; + if (exists((char *)console_path) && !console_cfg) + goto cleanup; + + if (mkdir(override_dir, 0755) != 0 && errno != EEXIST) + goto cleanup; + + console_file = fopen(console_temp, "wb"); + if (!console_file) + goto cleanup; + if (!Config_writePreservedVisual(console_file, console_cfg) || + !Config_writeNonvisual(console_file)) + goto cleanup; + if (!Config_finishFile(console_file)) { + console_file = NULL; + goto cleanup; + } + console_file = NULL; + + override_file = fopen(override_temp, "wb"); + if (!override_file) + goto cleanup; + override_lines = Config_writeOverrideDelta(override_file, console_cfg, root_cfg); + if (override_lines < 0) + goto cleanup; + if (!Config_finishFile(override_file)) { + override_file = NULL; + goto cleanup; + } + override_file = NULL; + + new_user_cfg = allocFile(console_temp); + if (!new_user_cfg) + goto cleanup; + if (override_lines > 0) { + new_override_cfg = allocFile(override_temp); + if (!new_override_cfg) + goto cleanup; + } + + if (!Config_commitConsoleAndOverride(console_temp, console_path, + override_temp, override_path, override_lines > 0, + game_path, config.loaded == CONFIG_GAME)) + goto cleanup; + + config.loaded = CONFIG_CONSOLE; + if (config.user_cfg) free(config.user_cfg); + config.user_cfg = new_user_cfg; + new_user_cfg = NULL; + if (config.shader_set_override_cfg) free(config.shader_set_override_cfg); + config.shader_set_override_cfg = new_override_cfg; + new_override_cfg = NULL; + sync(); + result = 1; + +cleanup: + if (console_file) fclose(console_file); + if (override_file) fclose(override_file); + if (console_temp[0]) unlink(console_temp); + if (override_temp[0]) unlink(override_temp); + free(console_cfg); + free(root_cfg); + free(new_user_cfg); + free(new_override_cfg); + return result; +} + +int Config_write(int override) { + char game_path[MAX_PATH]; + char path[MAX_PATH]; + char active_set[MAX_PATH]; + + Config_getPath(game_path, CONFIG_WRITE_GAME); + + if (!override) { + if (!ShaderSets_getActive(active_set, sizeof(active_set))) + return CONFIG_WRITE_FAILED; + if (active_set[0]) { + Config_getPath(path, CONFIG_WRITE_ALL); + return Config_writeActiveSetConsole(active_set, game_path, path) + ? CONFIG_WRITE_SHADER_SET + : CONFIG_WRITE_FAILED; + } + } + + strcpy(path, game_path); + if (!override) { + if (config.loaded==CONFIG_GAME) unlink(path); + Config_getPath(path, CONFIG_WRITE_ALL); + } + + if (!Config_writeStandard(path)) + return CONFIG_WRITE_FAILED; + + config.loaded = override ? CONFIG_GAME : CONFIG_CONSOLE; + sync(); + return CONFIG_WRITE_STANDARD; } void Config_restore(void) { char path[MAX_PATH]; diff --git a/workspace/all/minarch/ma_config.h b/workspace/all/minarch/ma_config.h index 0da8d9f2f..f427db2f3 100644 --- a/workspace/all/minarch/ma_config.h +++ b/workspace/all/minarch/ma_config.h @@ -11,7 +11,12 @@ void Config_load(void); void Config_free(void); void Config_readOptions(void); void Config_readControls(void); -void Config_write(int override); +enum { + CONFIG_WRITE_FAILED, + CONFIG_WRITE_STANDARD, + CONFIG_WRITE_SHADER_SET, +}; +int Config_write(int override); void Config_restore(void); bool Config_reloadFrontendShaders(void); void Config_syncShaders(char* key, int value); diff --git a/workspace/all/minarch/ma_frontend_opts.c b/workspace/all/minarch/ma_frontend_opts.c index c1816b372..cfe04f76c 100644 --- a/workspace/all/minarch/ma_frontend_opts.c +++ b/workspace/all/minarch/ma_frontend_opts.c @@ -432,13 +432,19 @@ static int OptionSaveChanges_onConfirm(MenuList* list, int i) { char* message; switch (i) { case 0: { - Config_write(CONFIG_WRITE_ALL); - message = "Saved for console."; + int result = Config_write(CONFIG_WRITE_ALL); + if (result == CONFIG_WRITE_SHADER_SET) + message = "Saved console and shader set."; + else if (result == CONFIG_WRITE_STANDARD) + message = "Saved for console."; + else + message = "Unable to save for console."; break; } case 1: { - Config_write(CONFIG_WRITE_GAME); - message = "Saved for game."; + message = Config_write(CONFIG_WRITE_GAME) + ? "Saved for game." + : "Unable to save for game."; break; } default: { From 2c2c0d799bbe0f4f406d5674e0e52f5d8728d761 Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 28 Jul 2026 09:53:39 +0200 Subject: [PATCH 4/9] refactor: simplify shader set config management and overrides --- skeleton/BASE/README.txt | 6 +- workspace/all/minarch/ma_config.c | 304 +++--------------------------- 2 files changed, 29 insertions(+), 281 deletions(-) diff --git a/skeleton/BASE/README.txt b/skeleton/BASE/README.txt index d0d68023d..11a4f693f 100644 --- a/skeleton/BASE/README.txt +++ b/skeleton/BASE/README.txt @@ -104,13 +104,13 @@ The first option in the in-game Shaders menu applies one frontend and shader con A set can be adjusted for a specific emulator tag by adding a config with the same filename inside a tag subfolder. For example, `/Shaders/sets/Retro.cfg` is the fallback for every emulator and `/Shaders/sets/GBA/Retro.cfg` overrides its values for GBA games. -Set configs support `minarch_` frontend, shader, and shader-parameter options only. Console settings are applied before the selected set, while per-game settings are applied afterward and take priority. Existing shader presets in `/Shaders` remain available separately in the in-game Shaders menu. +Set configs support `minarch_` frontend, shader, and shader-parameter options only. The selected set is applied after console or per-game settings and takes priority for those visual options. Existing shader presets in `/Shaders` remain available separately in the in-game Shaders menu. The in-game Shortcuts menu includes Next Shader Set. Bind it to cycle through Disabled and the available sets, apply the new set immediately, and show the selected name in a notification. -When a shader set is active, Save for console keeps normal emulator, control, and shortcut settings in the console config and writes frontend or shader changes to `/Shaders/sets//.cfg`. Only values differing from the effective root-set baseline are written, making the generated override suitable for review and sharing. +When a shader set is active, Save for console keeps normal emulator, control, and shortcut settings in the console config and writes a complete frontend and shader snapshot to `/Shaders/sets//.cfg`. This tag override replaces the root set values for that emulator until it is regenerated or removed. -Save for game continues to save the currently effective shader values into the game config. Game settings take priority over shader sets, so that game may retain those values when switching sets. +Save for game continues to save the currently effective shader values into the game config. The active set stays visually in control; selecting Disabled reveals the saved game values again. ---------------------------------------- diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.c index 1bf7110ce..3d6ac77f9 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.c @@ -652,25 +652,25 @@ void Config_free(void) { void Config_readOptions(void) { Config_readOptionsString(config.system_cfg); Config_readOptionsString(config.default_cfg); - if (config.loaded == CONFIG_CONSOLE) - Config_readOptionsString(config.user_cfg); + Config_readOptionsString(config.user_cfg); + // Active sets intentionally override both console and game visual settings. Config_readFrontendShaderOptionsString(config.shader_set_cfg, 1); Config_readFrontendShaderOptionsString(config.shader_set_override_cfg, 1); - if (config.loaded == CONFIG_GAME) - Config_readOptionsString(config.user_cfg); } void Config_readControls(void) { Config_readControlsString(config.default_cfg); Config_readControlsString(config.user_cfg); } -static int Config_writeFrontendShaders(FILE *file) { +static int Config_writeFrontendShaders(FILE *file, int preserve_locks) { for (int i=0; config.frontend.options[i].key; i++) { Option* option = &config.frontend.options[i]; int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + if (fprintf(file, "%s%s = %s\n", + preserve_locks && option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) return 0; } } @@ -679,7 +679,9 @@ static int Config_writeFrontendShaders(FILE *file) { int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + if (fprintf(file, "%s%s = %s\n", + preserve_locks && option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) return 0; } } @@ -689,7 +691,9 @@ static int Config_writeFrontendShaders(FILE *file) { int count = 0; while ( option->values && option->values[count]) count++; if (option->value >= 0 && option->value < count) { - if (fprintf(file, "%s = %s\n", option->key, option->values[option->value]) < 0) + if (fprintf(file, "%s%s = %s\n", + preserve_locks && option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) return 0; } } @@ -754,7 +758,7 @@ static int Config_writeStandard(const char *path) { if (!file) return 0; - if (!Config_writeFrontendShaders(file) || !Config_writeNonvisual(file)) { + if (!Config_writeFrontendShaders(file, 0) || !Config_writeNonvisual(file)) { fclose(file); return 0; } @@ -830,278 +834,29 @@ static int Config_writePreservedVisual(FILE *file, const char *cfg) { return 1; } -static int Config_resolveOptionIndex(Option *option, const char *console_cfg, - const char *root_cfg, int *lock) { - const char *layers[] = { - config.system_cfg, - config.default_cfg, - console_cfg, - root_cfg, - }; - int value_index = option->default_value; - char value[256]; - - if (lock) *lock = 0; - for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); i++) { - if (layers[i] && Config_getValue((char *)layers[i], option->key, value, lock)) - value_index = Option_getValueIndex(option, value); - } - return value_index; -} - -static int Config_resolvePragmaIndex(int pass, Option *option, int default_index, - const char *console_cfg, const char *root_cfg, int *lock) { - const char *layers[] = { - config.system_cfg, - config.default_cfg, - console_cfg, - root_cfg, - }; - int value = default_index; - int shader_count = config.shaders.options[SH_NROFSHADERS].default_value; - char serialized[256]; - - if (lock) *lock = 0; - for (size_t i = 0; i < sizeof(layers) / sizeof(layers[0]); i++) { - if (!layers[i]) - continue; - if (Config_getValue((char *)layers[i], - config.shaders.options[SH_NROFSHADERS].key, serialized, NULL)) - shader_count = Option_getValueIndex( - &config.shaders.options[SH_NROFSHADERS], serialized); - if (pass < shader_count && - Config_getValue((char *)layers[i], option->key, serialized, lock)) - value = Option_getValueIndex(option, serialized); - } - return value; -} - -static int Config_getPragmaDefaultIndex(int pass, Option *option) { - ShaderParam *params = PLAT_getShaderPragmas(pass); - if (!params || !option->values) - return 0; - - for (int i = 0; i < 32; i++) { - if (!strcmp(params[i].name, option->key)) { - for (int j = 0; option->values[j]; j++) { - float candidate = strtof(option->values[j], NULL); - if (fabsf(params[i].def - candidate) < 0.001f) - return j; - } - break; - } - } - return 0; -} - -static int Config_writeOverrideDelta(FILE *file, const char *console_cfg, const char *root_cfg) { - int written = 0; - - for (int i = 0; config.frontend.options[i].key; i++) { - Option *option = &config.frontend.options[i]; - if (!option->values || option->value < 0 || !option->values[option->value]) - continue; - int baseline_lock = 0; - int baseline = Config_resolveOptionIndex(option, console_cfg, root_cfg, &baseline_lock); - if (option->value != baseline || option->lock != baseline_lock) { - if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", - option->key, option->values[option->value]) < 0) - return -1; - written++; - } - } - - for (int i = 0; config.shaders.options[i].key; i++) { - Option *option = &config.shaders.options[i]; - if (!option->values || option->value < 0 || !option->values[option->value]) - continue; - int baseline_lock = 0; - int baseline = Config_resolveOptionIndex(option, console_cfg, root_cfg, &baseline_lock); - if (option->value != baseline || option->lock != baseline_lock) { - if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", - option->key, option->values[option->value]) < 0) - return -1; - written++; - } - } - - for (int pass = 0; pass < config.shaders.options[SH_NROFSHADERS].value; pass++) { - for (int i = 0; i < config.shaderpragmas[pass].count; i++) { - Option *option = &config.shaderpragmas[pass].options[i]; - if (!option->values || option->value < 0 || !option->values[option->value]) - continue; - int baseline_lock = 0; - int baseline = Config_resolvePragmaIndex(pass, option, - Config_getPragmaDefaultIndex(pass, option), console_cfg, root_cfg, - &baseline_lock); - if (option->value != baseline || option->lock != baseline_lock) { - if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", - option->key, option->values[option->value]) < 0) - return -1; - written++; - } - } - } - return written; -} - static int Config_makeTempPath(const char *path, char *temp_path, size_t temp_size) { return snprintf(temp_path, temp_size, "%s.tmp.%ld", path, (long)getpid()) < (int)temp_size; } -static int Config_makeBackupPath(const char *path, char *backup_path, size_t backup_size) { - return snprintf(backup_path, backup_size, "%s.bak.%ld", path, (long)getpid()) < (int)backup_size; -} - -static int Config_copyFile(const char *source, const char *destination) { - FILE *input = fopen(source, "rb"); - if (!input) - return 0; - FILE *output = fopen(destination, "wb"); - if (!output) { - fclose(input); - return 0; - } - - char buffer[4096]; - size_t count; - int result = 1; - while ((count = fread(buffer, 1, sizeof(buffer), input)) > 0) { - if (fwrite(buffer, 1, count, output) != count) { - result = 0; - break; - } - } - if (ferror(input)) - result = 0; - fclose(input); - if (!result) { - fclose(output); - unlink(destination); - return 0; - } - if (!Config_finishFile(output)) { - unlink(destination); - return 0; - } - return 1; -} - -static void Config_restoreBackup(const char *backup_path, const char *path, int had_original) { - if (had_original) { - if (rename(backup_path, path) != 0) - LOG_error("failed to restore config: %s\n", path); - } - else { - unlink(path); - } -} - -static int Config_commitConsoleAndOverride(const char *console_temp, const char *console_path, - const char *override_temp, const char *override_path, int has_override, - const char *game_path, int remove_game) { - char console_backup[MAX_PATH]; - char override_backup[MAX_PATH]; - char game_backup[MAX_PATH]; - int console_backed_up = 0; - int override_backed_up = 0; - int game_backed_up = 0; - int console_installed = 0; - int override_modified = 0; - - if (!Config_makeBackupPath(console_path, console_backup, sizeof(console_backup)) || - !Config_makeBackupPath(override_path, override_backup, sizeof(override_backup)) || - (remove_game && !Config_makeBackupPath(game_path, game_backup, sizeof(game_backup)))) - return 0; - - unlink(console_backup); - unlink(override_backup); - if (remove_game) - unlink(game_backup); - - if (exists((char *)console_path)) { - if (!Config_copyFile(console_path, console_backup)) - goto rollback; - console_backed_up = 1; - } - if (exists((char *)override_path)) { - if (!Config_copyFile(override_path, override_backup)) - goto rollback; - override_backed_up = 1; - } - if (remove_game && exists((char *)game_path)) { - if (!Config_copyFile(game_path, game_backup)) - goto rollback; - game_backed_up = 1; - } - - if (rename(console_temp, console_path) != 0) - goto rollback; - console_installed = 1; - - if (has_override) { - if (rename(override_temp, override_path) != 0) - goto rollback; - override_modified = 1; - } - else if (unlink(override_path) != 0 && errno != ENOENT) { - goto rollback; - } - else { - override_modified = 1; - } - - if (remove_game && unlink(game_path) != 0 && errno != ENOENT) - goto rollback; - - unlink(console_backup); - unlink(override_backup); - if (remove_game) - unlink(game_backup); - return 1; - -rollback: - if (console_installed) - Config_restoreBackup(console_backup, console_path, console_backed_up); - else - unlink(console_backup); - if (override_modified) - Config_restoreBackup(override_backup, override_path, override_backed_up); - else - unlink(override_backup); - if (game_backed_up && !exists((char *)game_path)) - Config_restoreBackup(game_backup, game_path, 1); - else - unlink(game_backup); - return 0; -} - static int Config_writeActiveSetConsole(const char *set_name, const char *game_path, const char *console_path) { char override_dir[MAX_PATH]; char override_path[MAX_PATH]; - char root_path[MAX_PATH]; char console_temp[MAX_PATH] = {0}; char override_temp[MAX_PATH] = {0}; char *console_cfg = allocFile((char *)console_path); - char *root_cfg = NULL; char *new_user_cfg = NULL; char *new_override_cfg = NULL; FILE *console_file = NULL; FILE *override_file = NULL; - int override_lines; int result = 0; - if (!ShaderSets_rootPath(set_name, root_path, sizeof(root_path)) || - !ShaderSets_overridePath(set_name, core.tag, override_path, sizeof(override_path)) || + if (!ShaderSets_overridePath(set_name, core.tag, override_path, sizeof(override_path)) || snprintf(override_dir, sizeof(override_dir), "%s/%s", SHADER_SETS_PATH, core.tag) >= (int)sizeof(override_dir) || !Config_makeTempPath(console_path, console_temp, sizeof(console_temp)) || !Config_makeTempPath(override_path, override_temp, sizeof(override_temp))) goto cleanup; - root_cfg = allocFile(root_path); - if (!root_cfg) - goto cleanup; if (exists((char *)console_path) && !console_cfg) goto cleanup; @@ -1123,8 +878,7 @@ static int Config_writeActiveSetConsole(const char *set_name, const char *game_p override_file = fopen(override_temp, "wb"); if (!override_file) goto cleanup; - override_lines = Config_writeOverrideDelta(override_file, console_cfg, root_cfg); - if (override_lines < 0) + if (!Config_writeFrontendShaders(override_file, 1)) goto cleanup; if (!Config_finishFile(override_file)) { override_file = NULL; @@ -1135,15 +889,16 @@ static int Config_writeActiveSetConsole(const char *set_name, const char *game_p new_user_cfg = allocFile(console_temp); if (!new_user_cfg) goto cleanup; - if (override_lines > 0) { - new_override_cfg = allocFile(override_temp); - if (!new_override_cfg) - goto cleanup; - } + new_override_cfg = allocFile(override_temp); + if (!new_override_cfg) + goto cleanup; - if (!Config_commitConsoleAndOverride(console_temp, console_path, - override_temp, override_path, override_lines > 0, - game_path, config.loaded == CONFIG_GAME)) + if (rename(console_temp, console_path) != 0) + goto cleanup; + if (rename(override_temp, override_path) != 0) + goto cleanup; + if (config.loaded == CONFIG_GAME && + unlink(game_path) != 0 && errno != ENOENT) goto cleanup; config.loaded = CONFIG_CONSOLE; @@ -1162,7 +917,6 @@ static int Config_writeActiveSetConsole(const char *set_name, const char *game_p if (console_temp[0]) unlink(console_temp); if (override_temp[0]) unlink(override_temp); free(console_cfg); - free(root_cfg); free(new_user_cfg); free(new_override_cfg); return result; @@ -1472,23 +1226,17 @@ static void resetFrontendShaders(void) { static void readEffectiveFrontendOptions(int sync) { Config_readFrontendOptionsString(config.system_cfg, sync); Config_readFrontendOptionsString(config.default_cfg, sync); - if (config.loaded == CONFIG_CONSOLE) - Config_readFrontendOptionsString(config.user_cfg, sync); + Config_readFrontendOptionsString(config.user_cfg, sync); Config_readFrontendOptionsString(config.shader_set_cfg, sync); Config_readFrontendOptionsString(config.shader_set_override_cfg, sync); - if (config.loaded == CONFIG_GAME) - Config_readFrontendOptionsString(config.user_cfg, sync); } static void readEffectiveShaderOptions(void) { Config_readShaderOptionsString(config.system_cfg); Config_readShaderOptionsString(config.default_cfg); - if (config.loaded == CONFIG_CONSOLE) - Config_readShaderOptionsString(config.user_cfg); + Config_readShaderOptionsString(config.user_cfg); Config_readShaderOptionsString(config.shader_set_cfg); Config_readShaderOptionsString(config.shader_set_override_cfg); - if (config.loaded == CONFIG_GAME) - Config_readShaderOptionsString(config.user_cfg); } static void readEffectiveFrontendShaders(int sync) { From 465b1558466a519cacd6302aef944a0b2c71f6fe Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 28 Jul 2026 12:12:21 +0200 Subject: [PATCH 5/9] feat: add dmg shader --- .../BASE/Shaders/glsl/dmg_dot_matrix.glsl | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl diff --git a/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl b/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl new file mode 100644 index 000000000..03454339a --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl @@ -0,0 +1,49 @@ +// DMG Dot Matrix Shader +// Initial version built by Status_Librarian_313 +// shared on Reddit: https://www.reddit.com/r/trimui/s/CfQup5U7ek +// Modified by sinedied (http://github.com/sinedied/) + +#pragma parameter dmg_edge_alpha "Grid opacity" 0.3 0.0 1.0 0.01 +#pragma parameter dmg_brightness_correction "Brightness correction" 1.2 0.5 2.0 0.01 +#pragma parameter dmg_grid_lightness "Grid lightness" 1.0 0.0 1.0 0.01 +#pragma parameter dmg_gamma "Gamma" 1.4 0.5 2.0 0.1 + +#ifdef VERTEX +attribute vec4 VertexCoord, TexCoord; +varying vec4 TEX0; +uniform mat4 MVPMatrix; + +void main() { + TEX0 = TexCoord; + gl_Position = MVPMatrix * VertexCoord; +} +#else +precision highp float; + +uniform sampler2D Texture; +uniform vec2 OutputSize; +uniform vec2 TextureSize; +uniform float dmg_edge_alpha; +uniform float dmg_brightness_correction; +uniform float dmg_grid_lightness; +uniform float dmg_gamma; + +varying vec4 TEX0; + +void main() { + vec2 screenCoord = TEX0.xy * OutputSize; + vec2 texelSize = OutputSize / TextureSize; + + float lineWidth = 1.0; + float edgeX = step(texelSize.x - lineWidth, mod(screenCoord.x, texelSize.x)); + float edgeY = step(texelSize.y - lineWidth, mod(screenCoord.y, texelSize.y)); + float gridMask = max(edgeX, edgeY); + + vec3 color = texture2D(Texture, TEX0.xy).rgb * dmg_brightness_correction; + vec3 gridColor = vec3(dmg_grid_lightness); + + vec3 finalColor = mix(color, gridColor, gridMask * dmg_edge_alpha); + finalColor = pow(clamp(finalColor, 0.0, 1.0), vec3(dmg_gamma)); + gl_FragColor = vec4(finalColor, 1.0); +} +#endif From 0fcf8c232cd2540e554e4e7a9633c417eb05f848 Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 28 Jul 2026 12:12:52 +0200 Subject: [PATCH 6/9] feat: add libretro image adjustment shader --- .../BASE/Shaders/glsl/image-adjustment.glsl | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 skeleton/BASE/Shaders/glsl/image-adjustment.glsl diff --git a/skeleton/BASE/Shaders/glsl/image-adjustment.glsl b/skeleton/BASE/Shaders/glsl/image-adjustment.glsl new file mode 100644 index 000000000..c1bff0b2f --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/image-adjustment.glsl @@ -0,0 +1,247 @@ +// Image Adjustment +// Author: hunterk +// License: Public domain + +#pragma parameter ia_target_gamma "Target Gamma" 2.2 0.1 5.0 0.1 +#pragma parameter ia_monitor_gamma "Monitor Gamma" 2.2 0.1 5.0 0.1 +#pragma parameter ia_overscan_percent_x "Horizontal Overscan %" 0.0 -25.0 25.0 1.0 +#pragma parameter ia_overscan_percent_y "Vertical Overscan %" 0.0 -25.0 25.0 1.0 +#pragma parameter ia_saturation "Saturation" 1.0 0.0 5.0 0.1 +#pragma parameter ia_contrast "Contrast" 1.0 0.0 10.0 0.05 +#pragma parameter ia_luminance "Luminance" 1.0 0.0 2.0 0.1 +#pragma parameter ia_black_level "Black Level" 0.00 -0.30 0.30 0.01 +#pragma parameter ia_bright_boost "Brightness Boost" 0.0 -1.0 1.0 0.05 +#pragma parameter ia_R "Red Channel" 1.0 0.0 2.0 0.05 +#pragma parameter ia_G "Green Channel" 1.0 0.0 2.0 0.05 +#pragma parameter ia_B "Blue Channel" 1.0 0.0 2.0 0.05 +#pragma parameter ia_ZOOM "Zoom Factor" 1.0 0.0 4.0 0.01 +#pragma parameter ia_XPOS "X Modifier" 0.0 -2.0 2.0 0.005 +#pragma parameter ia_YPOS "Y Modifier" 0.0 -2.0 2.0 0.005 +#pragma parameter ia_TOPMASK "Overscan Mask Top" 0.0 0.0 1.0 0.0025 +#pragma parameter ia_BOTMASK "Overscan Mask Bottom" 0.0 0.0 1.0 0.0025 +#pragma parameter ia_LMASK "Overscan Mask Left" 0.0 0.0 1.0 0.0025 +#pragma parameter ia_RMASK "Overscan Mask Right" 0.0 0.0 1.0 0.0025 +#pragma parameter ia_GRAIN_STR "Film Grain" 0.0 0.0 72.0 6.0 +#pragma parameter ia_SHARPEN "Sharpen" 0.0 0.0 1.0 0.05 +#pragma parameter ia_FLIP_HORZ "Flip Horiz Axis" 0.0 0.0 1.0 1.0 +#pragma parameter ia_FLIP_VERT "Flip Vert Axis" 0.0 0.0 1.0 1.0 + +#if defined(VERTEX) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING out +#define COMPAT_ATTRIBUTE in +#define COMPAT_TEXTURE texture +#else +#define COMPAT_VARYING varying +#define COMPAT_ATTRIBUTE attribute +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +COMPAT_ATTRIBUTE vec4 VertexCoord; +COMPAT_ATTRIBUTE vec4 COLOR; +COMPAT_ATTRIBUTE vec4 TexCoord; +COMPAT_VARYING vec4 COL0; +COMPAT_VARYING vec4 TEX0; +// out variables go here as COMPAT_VARYING whatever + +vec4 _oPosition1; +uniform mat4 MVPMatrix; +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; + +// compatibility #defines +#define vTexCoord TEX0.xy +#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize +#define OutSize vec4(OutputSize, 1.0 / OutputSize) + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float ia_overscan_percent_x; +uniform COMPAT_PRECISION float ia_overscan_percent_y; +uniform COMPAT_PRECISION float ia_ZOOM; +uniform COMPAT_PRECISION float ia_XPOS; +uniform COMPAT_PRECISION float ia_YPOS; +uniform COMPAT_PRECISION float ia_FLIP_HORZ; +uniform COMPAT_PRECISION float ia_FLIP_VERT; +#else +#define ia_overscan_percent_x 0.0 // crop width of image by X%; default is 0.0 +#define ia_overscan_percent_y 0.0 // crop height of image by X%; default is 0.0 +#define ia_ZOOM 1.0 // zoom factor; default is 1.0 +#define ia_XPOS 0.0 // horizontal position modifier; default is 0.0 +#define ia_YPOS 0.0 // vertical position modifier; default is 0.0 +#define ia_FLIP_HORZ 0.0 // horizontal flip toggle; default is 0.0 +#define ia_FLIP_VERT 0.0 // vertical flip toggle; default is 0.0 +#endif + +void main() +{ + vec4 flip_pos = VertexCoord; + if (ia_FLIP_HORZ > 0.5) flip_pos.x = 1.0 - flip_pos.x; + if (ia_FLIP_VERT > 0.5) flip_pos.y = 1.0 - flip_pos.y; + gl_Position = MVPMatrix * flip_pos; + vec2 shift = (vec2(0.5) * InputSize) / TextureSize; + vec2 overscan_coord = ((TexCoord.xy - shift) / ia_ZOOM) * (1.0 - vec2(ia_overscan_percent_x / 100.0, ia_overscan_percent_y / 100.0)) + shift; + TEX0.xy = overscan_coord + vec2(ia_XPOS, ia_YPOS); +} + +#elif defined(FRAGMENT) + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +#if __VERSION__ >= 130 +#define COMPAT_VARYING in +#define COMPAT_TEXTURE texture +out COMPAT_PRECISION vec4 FragColor; +#else +#define COMPAT_VARYING varying +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float ia_target_gamma; +uniform COMPAT_PRECISION float ia_monitor_gamma; +uniform COMPAT_PRECISION float ia_saturation; +uniform COMPAT_PRECISION float ia_contrast; +uniform COMPAT_PRECISION float ia_luminance; +uniform COMPAT_PRECISION float ia_black_level; +uniform COMPAT_PRECISION float ia_bright_boost; +uniform COMPAT_PRECISION float ia_R; +uniform COMPAT_PRECISION float ia_G; +uniform COMPAT_PRECISION float ia_B; +uniform COMPAT_PRECISION float ia_TOPMASK; +uniform COMPAT_PRECISION float ia_BOTMASK; +uniform COMPAT_PRECISION float ia_LMASK; +uniform COMPAT_PRECISION float ia_RMASK; +uniform COMPAT_PRECISION float ia_GRAIN_STR; +uniform COMPAT_PRECISION float ia_SHARPEN; +#else +#define ia_target_gamma 2.2 // the gamma you want the image to have; CRT TVs typically have a gamma of 2.4 +#define ia_monitor_gamma 2.2 // gamma setting of your current display; LCD monitors typically have a gamma of 2.2 +#define ia_saturation 1.0 // color saturation; default 1.0 +#define ia_contrast 1.0 // image contrast; default 1.0 +#define ia_luminance 1.0 // image luminance; default 1.0 +#define ia_black_level 0.0 // black level; default 0.0 +#define ia_bright_boost 0.0 // adds to the total brightness. Negative values decrease it; Use values between 1.0 (totally white) and -1.0 (totally black); default is 0.0 +#define ia_R 1.0 // red level; default 1.0 +#define ia_G 1.0 // green level; default 1.0 +#define ia_B 1.0 // red level; default 1.0 +#define ia_TOPMASK 0.0 // mask top of image by X%; default is 0.0 +#define ia_BOTMASK 0.0 // mask bottom of image by X%; default is 0.0 +#define ia_LMASK 0.0 // mask left of image by X%; default is 0.0 +#define ia_RMASK 0.0 // mask right of image by X%; default is 0.0 +#define ia_GRAIN_STR 0.0 // grain filter strength; default is 0.0 +#define ia_SHARPEN 0.0 // sharpen filter strength; default is 0.0 +#endif + +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; +uniform sampler2D Texture; +COMPAT_VARYING vec4 TEX0; +// in variables go here as COMPAT_VARYING whatever + +// compatibility #defines +#define Source Texture +#define vTexCoord TEX0.xy + +#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize +#define OutSize vec4(OutputSize, 1.0 / OutputSize) + +// texture(a, b) with COMPAT_TEXTURE(a, b) <-can't macro unfortunately + +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +//https://www.shadertoy.com/view/4sXSWs strength= 16.0 +vec3 filmGrain(vec2 uv, float strength ) +{ + float x = (uv.x + 4.0 ) * (uv.y + 4.0 ) * ((mod(vec2(FrameCount, FrameCount).x, 800.0) + 10.0) * 10.0); + return vec3(mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01)-0.005) * strength; +} + +// based on "Improved texture interpolation" by Iñigo Quílez +// Original description: http://www.iquilezles.org/www/articles/texture/texture.htm +vec3 sharp(sampler2D tex, vec2 texCoord) +{ + vec2 p = texCoord.xy; + p = p * SourceSize.xy + vec2(0.5, 0.5); + vec2 i = floor(p); + vec2 f = p - i; + f = f * f * f * (f * (f * 6.0 - vec2(15.0, 15.0)) + vec2(10.0, 10.0)); + p = i + f; + p = (p - vec2(0.5, 0.5)) * SourceSize.zw; + return COMPAT_TEXTURE(tex, p).rgb; +} + + +void main() +{ + vec3 film_grain = filmGrain(vTexCoord, ia_GRAIN_STR); + vec3 res = COMPAT_TEXTURE(Source, vTexCoord).rgb; // sample the texture + res = mix(res, sharp(Source, vTexCoord), ia_SHARPEN) + film_grain; // add film grain and sharpness + vec3 gamma = vec3(ia_monitor_gamma / ia_target_gamma); // set up ratio of display's gamma vs desired gamma + +//saturation and luminance + vec3 satColor = clamp(hsv2rgb(rgb2hsv(res) * vec3(1.0, ia_saturation, ia_luminance)), 0.0, 1.0); + +//contrast and brightness + vec3 conColor = clamp((satColor - 0.5) * ia_contrast + 0.5 + ia_bright_boost, 0.0, 1.0); + + conColor -= vec3(ia_black_level); // apply black level + conColor *= (vec3(1.0) / vec3(1.0-ia_black_level)); + conColor = pow(conColor, 1.0 / vec3(gamma)); // Apply gamma correction + conColor *= vec3(ia_R, ia_G, ia_B); + +//overscan mask + + vec2 FragCoord = (vTexCoord * TextureSize.xy / InputSize.xy); //needed for overscan mask to work properly + + if (FragCoord.y > ia_TOPMASK && FragCoord.y < (1.0 - ia_BOTMASK)) + conColor = conColor; + else + conColor = vec3(0.0); + + if (FragCoord.x > ia_LMASK && FragCoord.x < (1.0 - ia_RMASK)) + conColor = conColor; + else + conColor = vec3(0.0); + + FragColor = vec4(conColor, 1.0); +} +#endif From 5029fb1fd830b3ac90872374a6b6b8c6b43da807 Mon Sep 17 00:00:00 2001 From: sinedied Date: Sun, 2 Aug 2026 11:48:33 +0200 Subject: [PATCH 7/9] feat(minarch): support core options in shader sets --- skeleton/BASE/README.txt | 4 +- workspace/all/minarch/ma_config.c | 154 +++++++++++++++++++++++++--- workspace/all/minarch/ma_internal.h | 1 + workspace/all/minarch/minarch.c | 6 +- 4 files changed, 145 insertions(+), 20 deletions(-) diff --git a/skeleton/BASE/README.txt b/skeleton/BASE/README.txt index 11a4f693f..ad509c5a0 100644 --- a/skeleton/BASE/README.txt +++ b/skeleton/BASE/README.txt @@ -104,11 +104,11 @@ The first option in the in-game Shaders menu applies one frontend and shader con A set can be adjusted for a specific emulator tag by adding a config with the same filename inside a tag subfolder. For example, `/Shaders/sets/Retro.cfg` is the fallback for every emulator and `/Shaders/sets/GBA/Retro.cfg` overrides its values for GBA games. -Set configs support `minarch_` frontend, shader, and shader-parameter options only. The selected set is applied after console or per-game settings and takes priority for those visual options. Existing shader presets in `/Shaders` remain available separately in the in-game Shaders menu. +Set configs support frontend, shader, shader-parameter, and emulator/core options. They can also include `minarch_gamepad_type` when authored manually. Controls and shortcuts are not read from sets. The selected set is applied after console or per-game settings and takes priority. For example, `/Shaders/sets/GB/Retro.cfg` can select a Gambatte palette for the Retro set. The in-game Shortcuts menu includes Next Shader Set. Bind it to cycle through Disabled and the available sets, apply the new set immediately, and show the selected name in a notification. -When a shader set is active, Save for console keeps normal emulator, control, and shortcut settings in the console config and writes a complete frontend and shader snapshot to `/Shaders/sets//.cfg`. This tag override replaces the root set values for that emulator until it is regenerated or removed. +When a shader set is active, Save for console keeps normal emulator, gamepad, control, and shortcut settings in the console config and writes a complete frontend, shader, and core-option snapshot to `/Shaders/sets//.cfg`. Gamepad type is not written to generated set overrides. This tag override freezes those set-specific values for that emulator until it is regenerated or removed. Save for game continues to save the currently effective shader values into the game config. The active set stays visually in control; selecting Disabled reveals the saved game values again. diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.c index 3d6ac77f9..e7942b02b 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -453,31 +454,63 @@ static void Config_readShaderOptionsString(char* cfg) { } } -static void Config_readFrontendShaderOptionsString(char* cfg, int sync) { - Config_readFrontendOptionsString(cfg, sync); - Config_readShaderOptionsString(cfg); +static int Config_getGamepadType(char *cfg, int *type) { + char value[256]; + char *end; + long parsed; + int count = 0; + + if (!cfg || !Config_getValue(cfg, "minarch_gamepad_type", value, NULL)) + return 0; + + errno = 0; + parsed = strtol(value, &end, 0); + if (end == value) + return -1; + while (isspace((unsigned char)*end)) end++; + while (gamepad_values[count]) count++; + if (errno == ERANGE || *end || parsed < 0 || parsed >= count) + return -1; + + *type = parsed; + return 1; } -static void Config_readOptionsString(char* cfg) { +static void Config_readCoreOptionsString(char* cfg) { if (!cfg) return; - LOG_info("Config_readOptions\n"); - char value[256]; - Config_readFrontendOptionsString(cfg, 1); - - if (has_custom_controllers && Config_getValue(cfg,"minarch_gamepad_type",value,NULL)) { - gamepad_type = strtol(value, NULL, 0); - int device = strtol(gamepad_values[gamepad_type], NULL, 0); - core.set_controller_port_device(0, device); + if (has_custom_controllers) { + int type; + int result = Config_getGamepadType(cfg, &type); + if (result > 0) { + gamepad_type = type; + int device = strtol(gamepad_values[gamepad_type], NULL, 0); + core.set_controller_port_device(0, device); + } + else if (result < 0) + LOG_warn("invalid minarch_gamepad_type\n"); } + char value[256]; for (int i=0; config.core.options[i].key; i++) { Option* option = &config.core.options[i]; // LOG_info("%s\n",option->key); if (!Config_getValue(cfg, option->key, value, &option->lock)) continue; OptionList_setOptionValue(&config.core, option->key, value); } +} + +static void Config_readSetOptionsString(char* cfg, int sync) { + Config_readFrontendOptionsString(cfg, sync); + Config_readCoreOptionsString(cfg); Config_readShaderOptionsString(cfg); } + +static void Config_readOptionsString(char* cfg) { + if (!cfg) return; + + LOG_info("Config_readOptions\n"); + Config_readSetOptionsString(cfg, 1); +} static void Config_readControlsString(char* cfg) { if (!cfg) return; @@ -654,8 +687,8 @@ void Config_readOptions(void) { Config_readOptionsString(config.default_cfg); Config_readOptionsString(config.user_cfg); // Active sets intentionally override both console and game visual settings. - Config_readFrontendShaderOptionsString(config.shader_set_cfg, 1); - Config_readFrontendShaderOptionsString(config.shader_set_override_cfg, 1); + Config_readSetOptionsString(config.shader_set_cfg, 1); + Config_readSetOptionsString(config.shader_set_override_cfg, 1); } void Config_readControls(void) { Config_readControlsString(config.default_cfg); @@ -701,6 +734,16 @@ static int Config_writeFrontendShaders(FILE *file, int preserve_locks) { return 1; } +static int Config_writeSetCoreOptions(FILE *file) { + for (int i=0; config.core.options[i].key; i++) { + Option* option = &config.core.options[i]; + if (fprintf(file, "%s%s = %s\n", option->lock ? "-" : "", + option->key, option->values[option->value]) < 0) + return 0; + } + return 1; +} + static int Config_writeNonvisual(FILE *file) { for (int i=0; config.core.options[i].key; i++) { Option* option = &config.core.options[i]; @@ -878,7 +921,8 @@ static int Config_writeActiveSetConsole(const char *set_name, const char *game_p override_file = fopen(override_temp, "wb"); if (!override_file) goto cleanup; - if (!Config_writeFrontendShaders(override_file, 1)) + if (!Config_writeFrontendShaders(override_file, 1) || + !Config_writeSetCoreOptions(override_file)) goto cleanup; if (!Config_finishFile(override_file)) { override_file = NULL; @@ -1254,6 +1298,63 @@ static int shaderSetTouchesFrontendOption(int index) { Config_getValue(config.shader_set_override_cfg, key, value, NULL)); } +static int shaderSetTouchesKey(const char *key) { + char value[256]; + return (config.shader_set_cfg && + Config_getValue(config.shader_set_cfg, key, value, NULL)) || + (config.shader_set_override_cfg && + Config_getValue(config.shader_set_override_cfg, key, value, NULL)); +} + +static int shaderSetTouchesGamepad(void) { + int type; + return Config_getGamepadType(config.shader_set_cfg, &type) > 0 || + Config_getGamepadType(config.shader_set_override_cfg, &type) > 0; +} + +static void reloadCoreOptions(const int *old_values, const int *old_locks, + const int *old_set_options, int old_gamepad, int old_set_gamepad) { + int new_set_gamepad = shaderSetTouchesGamepad(); + int palette_changed = 0; + + for (int i = 0; i < config.core.count; i++) { + config.core.options[i].value = config.core.options[i].default_value; + config.core.options[i].lock = 0; + } + if (has_custom_controllers) { + gamepad_type = 0; + core.set_controller_port_device(0, RETRO_DEVICE_JOYPAD); + } + + Config_readCoreOptionsString(config.system_cfg); + Config_readCoreOptionsString(config.default_cfg); + Config_readCoreOptionsString(config.user_cfg); + Config_readCoreOptionsString(config.shader_set_cfg); + Config_readCoreOptionsString(config.shader_set_override_cfg); + + // Keep unsaved core changes that neither the old nor new set manages. + for (int i = 0; i < config.core.count; i++) { + int new_set_option = shaderSetTouchesKey(config.core.options[i].key); + if ((old_set_options[i] || new_set_option) && + containsString(config.core.options[i].key, "palette")) + palette_changed = 1; + if (!old_set_options[i] && + !new_set_option) { + config.core.options[i].value = old_values[i]; + config.core.options[i].lock = old_locks[i]; + } + } + if (has_custom_controllers && !old_set_gamepad && !new_set_gamepad) { + gamepad_type = old_gamepad; + int device = strtol(gamepad_values[gamepad_type], NULL, 0); + core.set_controller_port_device(0, device); + } + config.core.changed = 1; + has_pending_opt_change = 1; + if (palette_changed && exactMatch((char*)core.tag, "GB")) + Special_updatedDMGPalette(2); +} + static void resetShaderPragmas(int pass) { ShaderParam *params = PLAT_getShaderPragmas(pass); if (!params) @@ -1288,6 +1389,13 @@ bool Config_reloadFrontendShaders(void) { int old_locks[FE_OPT_COUNT]; int old_set_options[FE_OPT_COUNT]; int old_shader_values[SH_NONE]; + int core_count = config.core.count; + int old_core_values[core_count ? core_count : 1]; + int old_core_locks[core_count ? core_count : 1]; + int old_core_set_options[core_count ? core_count : 1]; + int old_gamepad = gamepad_type; + int old_set_gamepad = shaderSetTouchesGamepad(); + int reload_core = old_set_gamepad; int apply_overclock = 0; int apply_sync_ref = 0; @@ -1298,8 +1406,24 @@ bool Config_reloadFrontendShaders(void) { } for (int i = 0; i < SH_NONE; i++) old_shader_values[i] = config.shaders.options[i].value; + for (int i = 0; i < core_count; i++) { + old_core_values[i] = config.core.options[i].value; + old_core_locks[i] = config.core.options[i].lock; + old_core_set_options[i] = shaderSetTouchesKey(config.core.options[i].key); + if (old_core_set_options[i]) reload_core = 1; + } Config_loadShaderSet(); + if (shaderSetTouchesGamepad()) + reload_core = 1; + for (int i = 0; i < config.core.count && !reload_core; i++) { + if (shaderSetTouchesKey(config.core.options[i].key)) + reload_core = 1; + } + if (reload_core) + reloadCoreOptions(old_core_values, old_core_locks, old_core_set_options, + old_gamepad, old_set_gamepad); + resetFrontendShaders(); readEffectiveFrontendShaders(0); diff --git a/workspace/all/minarch/ma_internal.h b/workspace/all/minarch/ma_internal.h index 556e8b186..b27721a4c 100644 --- a/workspace/all/minarch/ma_internal.h +++ b/workspace/all/minarch/ma_internal.h @@ -127,6 +127,7 @@ extern int rewind_cfg_audio; extern int rewind_cfg_compress; extern int rewind_cfg_lz4_acceleration; extern int rewind_init_ready; +extern int has_pending_opt_change; #include "ma_rewind.h" diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 3b754e875..6ef291329 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -67,6 +67,7 @@ int DEVICE_WIDTH = 0; int DEVICE_HEIGHT = 0; int DEVICE_PITCH = 0; int shader_reset_suppressed = 0; +int has_pending_opt_change = 0; GFX_Renderer renderer; @@ -236,8 +237,6 @@ int main(int argc , char* argv[]) { chooseSyncRef(); - int has_pending_opt_change = 0; - // then initialize custom shaders from settings initShaders(); Config_readOptions(); @@ -300,11 +299,12 @@ int main(int argc , char* argv[]) { Notification_renderToLayer(5); // Always call - handles cleanup when inactive - if (has_pending_opt_change) { + if (has_pending_opt_change && !config.core.changed) { has_pending_opt_change = 0; if (Core_updateAVInfo()) { LOG_info("AV info changed, reset sound system"); SND_resetAudio(core.sample_rate, core.fps); + renderer.dst_p = 0; } chooseSyncRef(); } From 029f7ffdb38e48791071ad2976dc6682c47ab10c Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 4 Aug 2026 22:47:55 +0200 Subject: [PATCH 8/9] feat: add perfect retro shaders and default sets --- skeleton/BASE/Shaders/glsl/crt-perfect.glsl | 251 +++++++++++++++++ skeleton/BASE/Shaders/glsl/dmg-perfect.glsl | 231 ++++++++++++++++ .../BASE/Shaders/glsl/dmg_dot_matrix.glsl | 49 ---- .../BASE/Shaders/glsl/image-adjustment.glsl | 247 ----------------- skeleton/BASE/Shaders/glsl/lcd-perfect.glsl | 258 ++++++++++++++++++ skeleton/BASE/Shaders/glsl/pixel-perfect.glsl | 175 ++++++++++++ skeleton/BASE/Shaders/sets/GB/Retro.cfg | 24 ++ skeleton/BASE/Shaders/sets/GB/Sharp.cfg | 23 ++ skeleton/BASE/Shaders/sets/GBA/Retro.cfg | 24 +- skeleton/BASE/Shaders/sets/GBC/Retro.cfg | 21 ++ skeleton/BASE/Shaders/sets/GBC/Sharp.cfg | 20 ++ skeleton/BASE/Shaders/sets/GG/Retro.cfg | 18 ++ skeleton/BASE/Shaders/sets/MD/Retro.cfg | 19 ++ skeleton/BASE/Shaders/sets/MD/Sharp.cfg | 17 ++ skeleton/BASE/Shaders/sets/P8/Retro.cfg | 19 ++ skeleton/BASE/Shaders/sets/Retro.cfg | 18 +- skeleton/BASE/Shaders/sets/Sharp.cfg | 16 +- 17 files changed, 1119 insertions(+), 311 deletions(-) create mode 100644 skeleton/BASE/Shaders/glsl/crt-perfect.glsl create mode 100644 skeleton/BASE/Shaders/glsl/dmg-perfect.glsl delete mode 100644 skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl delete mode 100644 skeleton/BASE/Shaders/glsl/image-adjustment.glsl create mode 100644 skeleton/BASE/Shaders/glsl/lcd-perfect.glsl create mode 100644 skeleton/BASE/Shaders/glsl/pixel-perfect.glsl create mode 100755 skeleton/BASE/Shaders/sets/GB/Retro.cfg create mode 100755 skeleton/BASE/Shaders/sets/GB/Sharp.cfg mode change 100644 => 100755 skeleton/BASE/Shaders/sets/GBA/Retro.cfg create mode 100755 skeleton/BASE/Shaders/sets/GBC/Retro.cfg create mode 100755 skeleton/BASE/Shaders/sets/GBC/Sharp.cfg create mode 100755 skeleton/BASE/Shaders/sets/GG/Retro.cfg create mode 100755 skeleton/BASE/Shaders/sets/MD/Retro.cfg create mode 100755 skeleton/BASE/Shaders/sets/MD/Sharp.cfg create mode 100755 skeleton/BASE/Shaders/sets/P8/Retro.cfg mode change 100644 => 100755 skeleton/BASE/Shaders/sets/Retro.cfg mode change 100644 => 100755 skeleton/BASE/Shaders/sets/Sharp.cfg diff --git a/skeleton/BASE/Shaders/glsl/crt-perfect.glsl b/skeleton/BASE/Shaders/glsl/crt-perfect.glsl new file mode 100644 index 000000000..ebda5a26b --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/crt-perfect.glsl @@ -0,0 +1,251 @@ +// crt-perfect v14 - scanlines, an RGB mask and curvature, pixel-perfect scale. +// ----------------------------------------------------------------------------- +// Author: sinedied +// Licence: MIT - Copyright (c) 2026 sinedied +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: the above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND. +// ----------------------------------------------------------------------------- +// PARAMETERS +// +// cp_scanlines 0.00 - 1.00 Scanline visibility. 0 disables them. +// cp_rgb_mask 0.00 - 1.00 RGB mask visibility. 0 disables it. +// cp_mask_type 0 / 1 / 2 Off, aperture grille, slot grille. +// cp_mask_size 0.25 - 2.00 Mask triads per source pixel. +// cp_min_pitch 2.00 - 6.00 Smallest pattern pitch, in output pixels. +// cp_curvature 0.00 - 0.15 Screen curvature. 0 disables it. +// cp_brightness 0.25 - 4.00 Output gain. 1.00 disables it. +// cp_gamma 0.50 - 2.00 Output gamma. 1.00 disables it. +// ----------------------------------------------------------------------------- +// A CRT look: soft scanlines and an RGB shadow mask over a clean pixel scale, +// with optional screen curvature. Reads like a small tube TV, sharp rather than +// blurry, and neither pattern beats against the pixel grid at any scale. +// +// Notes: +// - Needs a LINEAR filter, set in the preset. Under NEAREST the scale becomes +// ordinary nearest-neighbour and the picture gets ragged edges. +// - Render at the output resolution, 1:1 with the display. +// - At min. pitch 2.00 the mask becomes 2 colours: use 2.50 or more to keep +// the triads visible. +// - Brightness above 1.00 clips, may create pattern artifacts against the +// pixel grid unless the output is an integer scale. + +#pragma parameter cp_scanlines "Scanline visibility" 0.60 0.00 1.00 0.05 +#pragma parameter cp_rgb_mask "RGB mask visibility" 0.20 0.00 1.00 0.05 +#pragma parameter cp_mask_type "Mask 0=off 1=grille 2=slot" 1.00 0.00 2.00 1.00 +#pragma parameter cp_mask_size "Mask triads per pixel" 1.00 0.25 2.00 0.25 +#pragma parameter cp_min_pitch "Min. pitch in px" 3.00 2.00 6.00 0.25 +#pragma parameter cp_curvature "Screen curvature" 0.00 0.00 0.15 0.01 +#pragma parameter cp_brightness "Brightness" 1.25 0.25 4.00 0.05 +#pragma parameter cp_gamma "Gamma" 1.00 0.50 2.00 0.05 + +#if defined(VERTEX) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING out +#define COMPAT_ATTRIBUTE in +#define COMPAT_TEXTURE texture +#else +#define COMPAT_VARYING varying +#define COMPAT_ATTRIBUTE attribute +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +COMPAT_ATTRIBUTE vec4 VertexCoord; +COMPAT_ATTRIBUTE vec4 COLOR; +COMPAT_ATTRIBUTE vec4 TexCoord; +COMPAT_VARYING vec4 COL0; +COMPAT_VARYING vec4 TEX0; + +uniform mat4 MVPMatrix; +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; + +void main() +{ + gl_Position = MVPMatrix * VertexCoord; + COL0 = COLOR; + TEX0.xy = TexCoord.xy; +} + +#elif defined(FRAGMENT) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING in +#define COMPAT_TEXTURE texture +out vec4 FragColor; +#else +#define COMPAT_VARYING varying +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION highp +#else +#define COMPAT_PRECISION +#endif + +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; +uniform sampler2D Texture; +COMPAT_VARYING vec4 TEX0; + +#define Source Texture +#define vTexCoord TEX0.xy +#define outsize vec4(OutputSize, 1.0 / OutputSize) + +#define PI 3.141592654 +#define TAU 6.283185307 + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float cp_scanlines; +uniform COMPAT_PRECISION float cp_rgb_mask; +uniform COMPAT_PRECISION float cp_mask_type; +uniform COMPAT_PRECISION float cp_mask_size; +uniform COMPAT_PRECISION float cp_brightness; +uniform COMPAT_PRECISION float cp_min_pitch; +uniform COMPAT_PRECISION float cp_gamma; +uniform COMPAT_PRECISION float cp_curvature; +#else +#define cp_scanlines 0.60 +#define cp_rgb_mask 0.20 +#define cp_mask_type 1.00 +#define cp_mask_size 1.00 +#define cp_brightness 1.25 +#define cp_min_pitch 3.00 +#define cp_gamma 1.00 +#define cp_curvature 0.00 +#endif + +// Average of a unit sinusoid of frequency f, in cycles per output pixel, over +// one pixel-wide box. Reaches zero at one cycle per pixel. +float boxSinc(float f) +{ + float x = PI * max(f, 1e-4); + return sin(x) / x; +} + +// Nothing above Nyquist can be drawn, so fade the pattern out entirely there - +// amplitude and darkening together, leaving no uniform dimming behind. +float nyquistFade(float f) +{ + return 1.0 - smoothstep(0.34, 0.5, f); +} + +void main() +{ + vec2 uv = vTexCoord; + float tube = 1.0; + float noWarp = 1.0; + + if (cp_curvature > 0.0) { + // Dividing by the edge-midpoint value keeps the image edges on the + // screen edges, so only the corners curve away and nothing is cropped. + float norm = 1.0 / (1.0 + cp_curvature); + vec2 c = uv * 2.0 - 1.0; + vec2 cc = c * c; + float r2 = cc.x + cc.y; + + uv = c * (1.0 + cp_curvature * r2) * norm * 0.5 + 0.5; + noWarp = 0.0; + + // The corners now sample past the image, where the sampler would + // stretch the border texel; mask them instead. + vec2 e = outsize.zw; + vec2 aa = clamp(uv / e, 0.0, 1.0) * clamp((1.0 - uv) / e, 0.0, 1.0); + tube = aa.x * aa.y; + } + + // h is deliberately independent of the warp: it keeps everything below it + // uniform across the draw, which the driver can then compute once. + vec2 p = uv * TextureSize; + vec2 h = max(0.4995 * InputSize / OutputSize, 1e-6); + + // B is the nearest texel boundary and w the share of the footprint on its + // low side, handed to the texture unit: one LINEAR tap returns the blend. + vec2 B = floor(p + 0.5); + vec2 w = clamp((B - p + h) / (2.0 * h), 0.0, 1.0); + vec3 color = COMPAT_TEXTURE(Source, (B + 0.5 - w) / TextureSize).rgb; + + // The base is clamped because pow(0, g) is undefined and returns NaN on + // real drivers. 1e-8, not 1e-5, which would lift pure black to 1/255. + if (abs(cp_gamma - 1.0) > 0.001) { + color = pow(max(color, 1e-8), vec3(cp_gamma)); + } + + float scanSrcPitch = OutputSize.y / max(InputSize.y, 1.0); + float scanPitch = max(scanSrcPitch, cp_min_pitch); + float scanLocked = (1.0 - smoothstep(cp_min_pitch * 1.001, cp_min_pitch * 1.02, scanSrcPitch)) * noWarp; + float scanFreq = 1.0 / scanPitch; + + // The flat pitch, used even under curvature: it holds the pattern to one + // cycle per source line, and keeps this uniform across the draw. + float scanLocal = scanFreq; + + float scanAmp = cp_scanlines * mix(nyquistFade(scanLocal), 1.0, scanLocked); + float scanAC = 0.5 * scanAmp * mix(boxSinc(scanLocal), 1.0, scanLocked); + + float scan = 1.0; + if (scanAmp > 0.0) { + float y = uv.y * OutputSize.y - 0.5 * scanLocked; + scan = (1.0 - 0.5 * scanAmp) - scanAC * cos(TAU * fract(y * scanFreq)); + } + + float maskSrcPitch = OutputSize.x / max(InputSize.x * cp_mask_size, 1.0); + float maskPitch = max(maskSrcPitch, cp_min_pitch); + float maskLocked = (1.0 - smoothstep(cp_min_pitch * 1.001, cp_min_pitch * 1.02, maskSrcPitch)) * noWarp; + float maskFreq = 1.0 / maskPitch; + float maskLocal = maskFreq; + + float maskAmp = cp_rgb_mask * mix(nyquistFade(maskLocal), 1.0, maskLocked); + + vec3 mask = vec3(1.0); + if (maskAmp > 0.0 && cp_mask_type >= 0.5) { + float x = uv.x * OutputSize.x - 0.5 * maskLocked; + float phase = x * maskFreq - (1.0 / 6.0); + + if (cp_mask_type >= 1.5) { + float row = floor((uv.y * OutputSize.y - 0.5 * scanLocked) * scanFreq + 1e-3); + phase += 0.5 * mod(row, 2.0); + } + + float dc = 1.0 - 0.5 * maskAmp; + float ac = 0.5 * maskAmp * mix(boxSinc(maskLocal), 1.0, maskLocked); + mask.rg = dc + ac * cos(TAU * (fract(phase) - vec2(0.0, 1.0 / 3.0))); + mask.b = max(3.0 * dc - mask.r - mask.g, 0.0); + } + + // Brightness rides the pattern, so the clamp below lands on the product. + // Clamping the picture instead flattens highlights before the mask shapes + // them. + vec3 gain = sqrt(max(mask * (scan * cp_brightness), 0.0)); + + FragColor = vec4(clamp(color * gain * tube, 0.0, 1.0), 1.0); +} + +#endif diff --git a/skeleton/BASE/Shaders/glsl/dmg-perfect.glsl b/skeleton/BASE/Shaders/glsl/dmg-perfect.glsl new file mode 100644 index 000000000..d92e50814 --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/dmg-perfect.glsl @@ -0,0 +1,231 @@ +// dmg-perfect v11 - a Game Boy dot matrix over a pixel-perfect scale. +// ----------------------------------------------------------------------------- +// Licence: MIT - Copyright (c) 2026 sinedied +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: the above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND. +// ----------------------------------------------------------------------------- +// PARAMETERS +// +// dp_grid 0.00 - 1.00 Grid visibility. 0 disables it. +// dp_gap 0.25 - 2.00 Grid line thickness, in pixels. +// dp_shadow 0.00 - 1.00 Shadow cast by driven dots. 0 disables it. +// dp_brightness 0.25 - 4.00 Output gain. 1.00 disables it. +// dp_gamma 0.50 - 2.00 Output gamma. 1.00 disables it. +// dp_temperature -1.00 - 1.00 Warm above 0, cool below. 0.00 is off. +// dp_tint -1.00 - 1.00 Green above 0, magenta below. 0.00 is off. +// ----------------------------------------------------------------------------- +// An original Game Boy look: the dot matrix grid with its pale gaps, over a +// clean pixel scale. Dots can cast a shadow so they sit above the panel. The +// grid is invisible on white and strongest on dark content, as a real DMG is. +// +// Notes: +// - Needs a LINEAR filter, set in the preset. Under NEAREST the scale becomes +// ordinary nearest-neighbour and the picture gets ragged edges. +// - Render at the output resolution, 1:1 with the display. +// - Grid line thickness is in output pixels, so the panel reads the same at +// every resolution. 1.00 is a one-pixel line. +// - Brightness above 1.00 clips, may create pattern artifacts against the +// pixel grid unless the output is an integer scale. + +#pragma parameter dp_grid "Grid visibility" 0.30 0.00 1.00 0.01 +#pragma parameter dp_gap "Grid line thickness" 1.00 0.25 2.00 0.05 +#pragma parameter dp_shadow "Dot shadow" 0.00 0.00 1.00 0.01 +#pragma parameter dp_brightness "Brightness" 1.00 0.25 4.00 0.05 +#pragma parameter dp_gamma "Gamma" 1.20 0.50 2.00 0.05 +#pragma parameter dp_temperature "Cool / warm balance" 0.00 -1.00 1.00 0.01 +#pragma parameter dp_tint "Magenta / green balance" 0.00 -1.00 1.00 0.01 + +#if defined(VERTEX) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING out +#define COMPAT_ATTRIBUTE in +#define COMPAT_TEXTURE texture +#else +#define COMPAT_VARYING varying +#define COMPAT_ATTRIBUTE attribute +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +COMPAT_ATTRIBUTE vec4 VertexCoord; +COMPAT_ATTRIBUTE vec4 COLOR; +COMPAT_ATTRIBUTE vec4 TexCoord; +COMPAT_VARYING vec4 COL0; +COMPAT_VARYING vec4 TEX0; + +uniform mat4 MVPMatrix; +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; + +void main() +{ + gl_Position = MVPMatrix * VertexCoord; + COL0 = COLOR; + TEX0.xy = TexCoord.xy; +} + +#elif defined(FRAGMENT) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING in +#define COMPAT_TEXTURE texture +out vec4 FragColor; +#else +#define COMPAT_VARYING varying +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION highp +#else +#define COMPAT_PRECISION +#endif + +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; +uniform sampler2D Texture; +COMPAT_VARYING vec4 TEX0; + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float dp_grid; +uniform COMPAT_PRECISION float dp_gap; +uniform COMPAT_PRECISION float dp_shadow; +uniform COMPAT_PRECISION float dp_brightness; +uniform COMPAT_PRECISION float dp_gamma; +uniform COMPAT_PRECISION float dp_temperature; +uniform COMPAT_PRECISION float dp_tint; +#else +#define dp_grid 0.30 +#define dp_gap 1.0 +#define dp_shadow 0.0 +#define dp_brightness 1.0 +#define dp_gamma 1.2 +#define dp_temperature 0.0 +#define dp_tint 0.0 +#endif + +// The substrate a DMG's gaps show: undriven crystal at its lightest state. +#define DMG_SUBSTRATE 1.0 + +#define LUMA vec3(0.299, 0.587, 0.114) + +// Floor under the paper level the shadow measures opacity against. Must sit +// below the darkest palette anyone ships. +#define PAPER_FLOOR 0.35 + +// In source pixels, so the shadow holds its proportions at every scale. +#define SHADOW_OFFSET vec2(0.45, 0.85) + +// Extra half-width on the box that samples the displaced aperture. Softens the +// aperture's gaps only; the shadow's outer edge comes from the opacity field. +#define APERTURE_SOFT 0.5 + +// Antiderivative of the dot profile. Differencing it gives the exact box mean. +// Peak-normalised, so it is a coverage mask topping out at 1. +vec2 dotInt(vec2 x, vec2 w) +{ + vec2 n = floor(x); + return n * w + clamp(x - n, vec2(0.0), w); +} + +void main() +{ + // Source texels. The max() guards an unset uniform, which is 0 and would + // make h a zero divisor below. + vec2 p = TEX0.xy * TextureSize; + vec2 h = max(0.4995 * InputSize / OutputSize, 1e-6); + vec2 B = floor(p + 0.5); + + // N is the whole scale that fits, so a dp_gap line is dp_gap/N of a cell + // and stays a pixel wide at any scale. The nudge guards floor() on a + // division landing a ULP short. + vec2 sc = OutputSize / max(InputSize, 1.0); + float N = max(floor(min(sc.x, sc.y) + 1e-3), 1.0); + vec2 lit = clamp(vec2(1.0 - dp_gap / N), 1e-3, 1.0); + + // Coverage of the lit dot over this output pixel, exactly, per axis. + vec2 cov = max(dotInt(p + h, lit) - dotInt(p - h, lit), vec2(1e-6)) + / (2.0 * h); + + // Below two output pixels per cell the pattern folds to a coarser pitch, + // so it has to reach zero at two, not at one. + cov = mix(vec2(1.0), cov, smoothstep(vec2(2.0), vec2(2.9), sc)); + float dot2d = cov.x * cov.y; + + // B is the nearest texel boundary and w the share of the footprint on its + // low side, handed to the texture unit: one LINEAR tap returns the blend. + vec2 w = clamp((B - p + h) / (2.0 * h), 0.0, 1.0); + vec3 area = COMPAT_TEXTURE(Texture, (B + 0.5 - w) / TextureSize).rgb; + + // Not applied to the SUBSTRATE, so brightening lifts the dots toward a + // fixed paper rather than washing the whole panel out. + vec3 grade = (1.0 + dp_temperature * vec3(1.0, 0.0, -1.0) + + dp_tint * vec3(-0.5, 1.0, -0.5)) * dp_brightness; + + // Gaps show the substrate, dots show the picture; k is the share of this + // pixel reading as gap. + float k = dp_grid * (1.0 - dot2d); + vec3 col = mix(area * grade, vec3(DMG_SUBSTRATE), k); + + // Multiplies everything rather than darkening the gap colour, which is + // what puts the shadow underneath the dots. + if (dp_shadow > 0.0) { + // In source pixels, so the offset is a fixed fraction of a cell. + vec2 q = p - SHADOW_OFFSET; + + // The dot's own shape, displaced and widened: a box blur of it. + vec2 hs = h + APERTURE_SOFT; + vec2 covS = max(dotInt(q + hs, lit) - dotInt(q - hs, lit), vec2(0.0)) + / (2.0 * hs); + covS = mix(vec2(1.0), covS, smoothstep(vec2(2.0), vec2(2.9), sc)); + + // dot() is linear, so the luma of a blend is the blend of lumas and + // one tap suffices. + float casterLum = dot(COMPAT_TEXTURE(Texture, q / TextureSize).rgb, LUMA); + + // The undriven level to measure opacity against - not white, since no + // Game Boy palette is near it. + float paper = max(dot(area, LUMA), PAPER_FLOOR); + + // Both sides raw, so an output gain cancels out of the ratio. + float opacity = clamp(1.0 - casterLum / paper, 0.0, 1.0); + + col *= 1.0 - dp_shadow * opacity * covS.x * covS.y; + } + + // The base is clamped because pow(0, g) is undefined and returns NaN on + // real drivers. 1e-8, not 1e-5, which would lift pure black to 1/255. + if (abs(dp_gamma - 1.0) > 0.001) { + col = pow(max(col, 1e-8), vec3(dp_gamma)); + } + + FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); +} + +#endif diff --git a/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl b/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl deleted file mode 100644 index 03454339a..000000000 --- a/skeleton/BASE/Shaders/glsl/dmg_dot_matrix.glsl +++ /dev/null @@ -1,49 +0,0 @@ -// DMG Dot Matrix Shader -// Initial version built by Status_Librarian_313 -// shared on Reddit: https://www.reddit.com/r/trimui/s/CfQup5U7ek -// Modified by sinedied (http://github.com/sinedied/) - -#pragma parameter dmg_edge_alpha "Grid opacity" 0.3 0.0 1.0 0.01 -#pragma parameter dmg_brightness_correction "Brightness correction" 1.2 0.5 2.0 0.01 -#pragma parameter dmg_grid_lightness "Grid lightness" 1.0 0.0 1.0 0.01 -#pragma parameter dmg_gamma "Gamma" 1.4 0.5 2.0 0.1 - -#ifdef VERTEX -attribute vec4 VertexCoord, TexCoord; -varying vec4 TEX0; -uniform mat4 MVPMatrix; - -void main() { - TEX0 = TexCoord; - gl_Position = MVPMatrix * VertexCoord; -} -#else -precision highp float; - -uniform sampler2D Texture; -uniform vec2 OutputSize; -uniform vec2 TextureSize; -uniform float dmg_edge_alpha; -uniform float dmg_brightness_correction; -uniform float dmg_grid_lightness; -uniform float dmg_gamma; - -varying vec4 TEX0; - -void main() { - vec2 screenCoord = TEX0.xy * OutputSize; - vec2 texelSize = OutputSize / TextureSize; - - float lineWidth = 1.0; - float edgeX = step(texelSize.x - lineWidth, mod(screenCoord.x, texelSize.x)); - float edgeY = step(texelSize.y - lineWidth, mod(screenCoord.y, texelSize.y)); - float gridMask = max(edgeX, edgeY); - - vec3 color = texture2D(Texture, TEX0.xy).rgb * dmg_brightness_correction; - vec3 gridColor = vec3(dmg_grid_lightness); - - vec3 finalColor = mix(color, gridColor, gridMask * dmg_edge_alpha); - finalColor = pow(clamp(finalColor, 0.0, 1.0), vec3(dmg_gamma)); - gl_FragColor = vec4(finalColor, 1.0); -} -#endif diff --git a/skeleton/BASE/Shaders/glsl/image-adjustment.glsl b/skeleton/BASE/Shaders/glsl/image-adjustment.glsl deleted file mode 100644 index c1bff0b2f..000000000 --- a/skeleton/BASE/Shaders/glsl/image-adjustment.glsl +++ /dev/null @@ -1,247 +0,0 @@ -// Image Adjustment -// Author: hunterk -// License: Public domain - -#pragma parameter ia_target_gamma "Target Gamma" 2.2 0.1 5.0 0.1 -#pragma parameter ia_monitor_gamma "Monitor Gamma" 2.2 0.1 5.0 0.1 -#pragma parameter ia_overscan_percent_x "Horizontal Overscan %" 0.0 -25.0 25.0 1.0 -#pragma parameter ia_overscan_percent_y "Vertical Overscan %" 0.0 -25.0 25.0 1.0 -#pragma parameter ia_saturation "Saturation" 1.0 0.0 5.0 0.1 -#pragma parameter ia_contrast "Contrast" 1.0 0.0 10.0 0.05 -#pragma parameter ia_luminance "Luminance" 1.0 0.0 2.0 0.1 -#pragma parameter ia_black_level "Black Level" 0.00 -0.30 0.30 0.01 -#pragma parameter ia_bright_boost "Brightness Boost" 0.0 -1.0 1.0 0.05 -#pragma parameter ia_R "Red Channel" 1.0 0.0 2.0 0.05 -#pragma parameter ia_G "Green Channel" 1.0 0.0 2.0 0.05 -#pragma parameter ia_B "Blue Channel" 1.0 0.0 2.0 0.05 -#pragma parameter ia_ZOOM "Zoom Factor" 1.0 0.0 4.0 0.01 -#pragma parameter ia_XPOS "X Modifier" 0.0 -2.0 2.0 0.005 -#pragma parameter ia_YPOS "Y Modifier" 0.0 -2.0 2.0 0.005 -#pragma parameter ia_TOPMASK "Overscan Mask Top" 0.0 0.0 1.0 0.0025 -#pragma parameter ia_BOTMASK "Overscan Mask Bottom" 0.0 0.0 1.0 0.0025 -#pragma parameter ia_LMASK "Overscan Mask Left" 0.0 0.0 1.0 0.0025 -#pragma parameter ia_RMASK "Overscan Mask Right" 0.0 0.0 1.0 0.0025 -#pragma parameter ia_GRAIN_STR "Film Grain" 0.0 0.0 72.0 6.0 -#pragma parameter ia_SHARPEN "Sharpen" 0.0 0.0 1.0 0.05 -#pragma parameter ia_FLIP_HORZ "Flip Horiz Axis" 0.0 0.0 1.0 1.0 -#pragma parameter ia_FLIP_VERT "Flip Vert Axis" 0.0 0.0 1.0 1.0 - -#if defined(VERTEX) - -#if __VERSION__ >= 130 -#define COMPAT_VARYING out -#define COMPAT_ATTRIBUTE in -#define COMPAT_TEXTURE texture -#else -#define COMPAT_VARYING varying -#define COMPAT_ATTRIBUTE attribute -#define COMPAT_TEXTURE texture2D -#endif - -#ifdef GL_ES -#define COMPAT_PRECISION mediump -#else -#define COMPAT_PRECISION -#endif - -COMPAT_ATTRIBUTE vec4 VertexCoord; -COMPAT_ATTRIBUTE vec4 COLOR; -COMPAT_ATTRIBUTE vec4 TexCoord; -COMPAT_VARYING vec4 COL0; -COMPAT_VARYING vec4 TEX0; -// out variables go here as COMPAT_VARYING whatever - -vec4 _oPosition1; -uniform mat4 MVPMatrix; -uniform COMPAT_PRECISION int FrameDirection; -uniform COMPAT_PRECISION int FrameCount; -uniform COMPAT_PRECISION vec2 OutputSize; -uniform COMPAT_PRECISION vec2 TextureSize; -uniform COMPAT_PRECISION vec2 InputSize; - -// compatibility #defines -#define vTexCoord TEX0.xy -#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize -#define OutSize vec4(OutputSize, 1.0 / OutputSize) - -#ifdef PARAMETER_UNIFORM -uniform COMPAT_PRECISION float ia_overscan_percent_x; -uniform COMPAT_PRECISION float ia_overscan_percent_y; -uniform COMPAT_PRECISION float ia_ZOOM; -uniform COMPAT_PRECISION float ia_XPOS; -uniform COMPAT_PRECISION float ia_YPOS; -uniform COMPAT_PRECISION float ia_FLIP_HORZ; -uniform COMPAT_PRECISION float ia_FLIP_VERT; -#else -#define ia_overscan_percent_x 0.0 // crop width of image by X%; default is 0.0 -#define ia_overscan_percent_y 0.0 // crop height of image by X%; default is 0.0 -#define ia_ZOOM 1.0 // zoom factor; default is 1.0 -#define ia_XPOS 0.0 // horizontal position modifier; default is 0.0 -#define ia_YPOS 0.0 // vertical position modifier; default is 0.0 -#define ia_FLIP_HORZ 0.0 // horizontal flip toggle; default is 0.0 -#define ia_FLIP_VERT 0.0 // vertical flip toggle; default is 0.0 -#endif - -void main() -{ - vec4 flip_pos = VertexCoord; - if (ia_FLIP_HORZ > 0.5) flip_pos.x = 1.0 - flip_pos.x; - if (ia_FLIP_VERT > 0.5) flip_pos.y = 1.0 - flip_pos.y; - gl_Position = MVPMatrix * flip_pos; - vec2 shift = (vec2(0.5) * InputSize) / TextureSize; - vec2 overscan_coord = ((TexCoord.xy - shift) / ia_ZOOM) * (1.0 - vec2(ia_overscan_percent_x / 100.0, ia_overscan_percent_y / 100.0)) + shift; - TEX0.xy = overscan_coord + vec2(ia_XPOS, ia_YPOS); -} - -#elif defined(FRAGMENT) - -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#define COMPAT_PRECISION mediump -#else -#define COMPAT_PRECISION -#endif - -#if __VERSION__ >= 130 -#define COMPAT_VARYING in -#define COMPAT_TEXTURE texture -out COMPAT_PRECISION vec4 FragColor; -#else -#define COMPAT_VARYING varying -#define FragColor gl_FragColor -#define COMPAT_TEXTURE texture2D -#endif - -#ifdef PARAMETER_UNIFORM -uniform COMPAT_PRECISION float ia_target_gamma; -uniform COMPAT_PRECISION float ia_monitor_gamma; -uniform COMPAT_PRECISION float ia_saturation; -uniform COMPAT_PRECISION float ia_contrast; -uniform COMPAT_PRECISION float ia_luminance; -uniform COMPAT_PRECISION float ia_black_level; -uniform COMPAT_PRECISION float ia_bright_boost; -uniform COMPAT_PRECISION float ia_R; -uniform COMPAT_PRECISION float ia_G; -uniform COMPAT_PRECISION float ia_B; -uniform COMPAT_PRECISION float ia_TOPMASK; -uniform COMPAT_PRECISION float ia_BOTMASK; -uniform COMPAT_PRECISION float ia_LMASK; -uniform COMPAT_PRECISION float ia_RMASK; -uniform COMPAT_PRECISION float ia_GRAIN_STR; -uniform COMPAT_PRECISION float ia_SHARPEN; -#else -#define ia_target_gamma 2.2 // the gamma you want the image to have; CRT TVs typically have a gamma of 2.4 -#define ia_monitor_gamma 2.2 // gamma setting of your current display; LCD monitors typically have a gamma of 2.2 -#define ia_saturation 1.0 // color saturation; default 1.0 -#define ia_contrast 1.0 // image contrast; default 1.0 -#define ia_luminance 1.0 // image luminance; default 1.0 -#define ia_black_level 0.0 // black level; default 0.0 -#define ia_bright_boost 0.0 // adds to the total brightness. Negative values decrease it; Use values between 1.0 (totally white) and -1.0 (totally black); default is 0.0 -#define ia_R 1.0 // red level; default 1.0 -#define ia_G 1.0 // green level; default 1.0 -#define ia_B 1.0 // red level; default 1.0 -#define ia_TOPMASK 0.0 // mask top of image by X%; default is 0.0 -#define ia_BOTMASK 0.0 // mask bottom of image by X%; default is 0.0 -#define ia_LMASK 0.0 // mask left of image by X%; default is 0.0 -#define ia_RMASK 0.0 // mask right of image by X%; default is 0.0 -#define ia_GRAIN_STR 0.0 // grain filter strength; default is 0.0 -#define ia_SHARPEN 0.0 // sharpen filter strength; default is 0.0 -#endif - -uniform COMPAT_PRECISION int FrameDirection; -uniform COMPAT_PRECISION int FrameCount; -uniform COMPAT_PRECISION vec2 OutputSize; -uniform COMPAT_PRECISION vec2 TextureSize; -uniform COMPAT_PRECISION vec2 InputSize; -uniform sampler2D Texture; -COMPAT_VARYING vec4 TEX0; -// in variables go here as COMPAT_VARYING whatever - -// compatibility #defines -#define Source Texture -#define vTexCoord TEX0.xy - -#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize -#define OutSize vec4(OutputSize, 1.0 / OutputSize) - -// texture(a, b) with COMPAT_TEXTURE(a, b) <-can't macro unfortunately - -vec3 rgb2hsv(vec3 c) -{ - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); - vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); -} - -vec3 hsv2rgb(vec3 c) -{ - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -//https://www.shadertoy.com/view/4sXSWs strength= 16.0 -vec3 filmGrain(vec2 uv, float strength ) -{ - float x = (uv.x + 4.0 ) * (uv.y + 4.0 ) * ((mod(vec2(FrameCount, FrameCount).x, 800.0) + 10.0) * 10.0); - return vec3(mod((mod(x, 13.0) + 1.0) * (mod(x, 123.0) + 1.0), 0.01)-0.005) * strength; -} - -// based on "Improved texture interpolation" by Iñigo Quílez -// Original description: http://www.iquilezles.org/www/articles/texture/texture.htm -vec3 sharp(sampler2D tex, vec2 texCoord) -{ - vec2 p = texCoord.xy; - p = p * SourceSize.xy + vec2(0.5, 0.5); - vec2 i = floor(p); - vec2 f = p - i; - f = f * f * f * (f * (f * 6.0 - vec2(15.0, 15.0)) + vec2(10.0, 10.0)); - p = i + f; - p = (p - vec2(0.5, 0.5)) * SourceSize.zw; - return COMPAT_TEXTURE(tex, p).rgb; -} - - -void main() -{ - vec3 film_grain = filmGrain(vTexCoord, ia_GRAIN_STR); - vec3 res = COMPAT_TEXTURE(Source, vTexCoord).rgb; // sample the texture - res = mix(res, sharp(Source, vTexCoord), ia_SHARPEN) + film_grain; // add film grain and sharpness - vec3 gamma = vec3(ia_monitor_gamma / ia_target_gamma); // set up ratio of display's gamma vs desired gamma - -//saturation and luminance - vec3 satColor = clamp(hsv2rgb(rgb2hsv(res) * vec3(1.0, ia_saturation, ia_luminance)), 0.0, 1.0); - -//contrast and brightness - vec3 conColor = clamp((satColor - 0.5) * ia_contrast + 0.5 + ia_bright_boost, 0.0, 1.0); - - conColor -= vec3(ia_black_level); // apply black level - conColor *= (vec3(1.0) / vec3(1.0-ia_black_level)); - conColor = pow(conColor, 1.0 / vec3(gamma)); // Apply gamma correction - conColor *= vec3(ia_R, ia_G, ia_B); - -//overscan mask - - vec2 FragCoord = (vTexCoord * TextureSize.xy / InputSize.xy); //needed for overscan mask to work properly - - if (FragCoord.y > ia_TOPMASK && FragCoord.y < (1.0 - ia_BOTMASK)) - conColor = conColor; - else - conColor = vec3(0.0); - - if (FragCoord.x > ia_LMASK && FragCoord.x < (1.0 - ia_RMASK)) - conColor = conColor; - else - conColor = vec3(0.0); - - FragColor = vec4(conColor, 1.0); -} -#endif diff --git a/skeleton/BASE/Shaders/glsl/lcd-perfect.glsl b/skeleton/BASE/Shaders/glsl/lcd-perfect.glsl new file mode 100644 index 000000000..1998ffc93 --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/lcd-perfect.glsl @@ -0,0 +1,258 @@ +// lcd-perfect v10 - an LCD matrix and RGB stripes over a pixel-perfect scale. +// ----------------------------------------------------------------------------- +// Licence: MIT - Copyright (c) 2026 sinedied +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: the above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND. +// ----------------------------------------------------------------------------- +// PARAMETERS +// +// lp_grid 0.00 - 1.00 Grid visibility. 0 disables it. +// lp_balance 0.00 - 1.00 Row/column balance. 0 rows, 1 columns. +// lp_min_pitch 2.00 - 6.00 Smallest pattern pitch, in output pixels. +// lp_subpixels 0.00 - 1.00 RGB stripe visibility. 0 disables them. +// lp_layout 0 / 1 Stripe order: RGB or BGR. +// lp_brightness 0.25 - 4.00 Output gain. 1.00 disables it. +// lp_gamma 0.50 - 2.00 Output gamma. 1.00 disables it. +// ----------------------------------------------------------------------------- +// A handheld LCD look: a soft backlit mesh with RGB subpixel stripes, over a +// clean pixel scale. Reads like a Game Boy Color or GBA screen in good light - +// a gentle grid rather than a hard black matrix, and it stays even at every +// scale instead of breaking into a pattern. +// +// Notes: +// - Needs a LINEAR filter, set in the preset. Under NEAREST the scale becomes +// ordinary nearest-neighbour and the picture gets ragged edges. +// - Render at the output resolution, 1:1 with the display. +// - Row/column balance sets which axis dominates. Real panels are row-dominant; +// 0.80 or so matches lcd1x. +// - Brightness above 1.00 clips, may create pattern artifacts against the +// pixel grid unless the output is an integer scale. + +#pragma parameter lp_grid "Grid visibility" 0.30 0.00 1.00 0.01 +#pragma parameter lp_balance "Row/column balance" 0.60 0.00 1.00 0.01 +#pragma parameter lp_min_pitch "Minimum pitch in px" 3.00 2.00 6.00 0.25 +#pragma parameter lp_subpixels "RGB stripe visibility" 0.20 0.00 1.00 0.05 +#pragma parameter lp_layout "Stripe order 0=RGB 1=BGR" 0.00 0.00 1.00 1.00 +#pragma parameter lp_brightness "Brightness" 1.25 0.25 4.00 0.05 +#pragma parameter lp_gamma "Gamma" 1.00 0.50 2.00 0.05 + +#if defined(VERTEX) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING out +#define COMPAT_ATTRIBUTE in +#define COMPAT_TEXTURE texture +#else +#define COMPAT_VARYING varying +#define COMPAT_ATTRIBUTE attribute +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +COMPAT_ATTRIBUTE vec4 VertexCoord; +COMPAT_ATTRIBUTE vec4 COLOR; +COMPAT_ATTRIBUTE vec4 TexCoord; +COMPAT_VARYING vec4 COL0; +COMPAT_VARYING vec4 TEX0; + +uniform mat4 MVPMatrix; +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; + +void main() +{ + gl_Position = MVPMatrix * VertexCoord; + COL0 = COLOR; + TEX0.xy = TexCoord.xy; +} + +#elif defined(FRAGMENT) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING in +#define COMPAT_TEXTURE texture +out vec4 FragColor; +#else +#define COMPAT_VARYING varying +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION highp +#else +#define COMPAT_PRECISION +#endif + +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; +uniform sampler2D Texture; +COMPAT_VARYING vec4 TEX0; + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float lp_grid; +uniform COMPAT_PRECISION float lp_balance; +uniform COMPAT_PRECISION float lp_min_pitch; +uniform COMPAT_PRECISION float lp_subpixels; +uniform COMPAT_PRECISION float lp_layout; +uniform COMPAT_PRECISION float lp_brightness; +uniform COMPAT_PRECISION float lp_gamma; +#else +#define lp_grid 0.30 +#define lp_balance 0.60 +#define lp_min_pitch 3.00 +#define lp_subpixels 0.20 +#define lp_layout 0.0 +#define lp_brightness 1.25 +#define lp_gamma 1.00 +#endif + +#define TAU 6.283185307 +#define PI 3.141592654 + +// cos and sin of TAU/6, the angle from a cell's centre to the red stripe. +// Green sits half a turn from there, so its pair is (-1, 0) and costs nothing. +#define COS_TAU_6 0.5 +#define SIN_TAU_6 0.866025404 + +// Mean of a unit sinusoid over one output pixel. The mesh gets this exactly +// from the integral below; the stripes are sampled, so they need it named. +float boxSinc(float f) +{ + float x = PI * max(f, 1e-4); + return sin(x) / x; +} + +// Past Nyquist the pattern would fold to a coarser pitch at nearly full +// strength, so take it out entirely instead. +vec2 nyquistFade(vec2 f) +{ + return 1.0 - smoothstep(0.34, 0.5, f); +} + +void main() +{ + vec2 p = TEX0.xy * TextureSize; + vec2 d = max(InputSize / OutputSize, 1e-6); + vec2 B = floor(p + 0.5); + + // Cells per period: a WHOLE number of cells, never a fixed pixel size, so + // the pattern stays periodic on the source grid. The bias is load-bearing - + // a/b is a*rcp(b), so a ratio of exactly 1 can land a hair above it. + vec2 N = max(ceil(lp_min_pitch * d - 1e-4), 1.0); + + vec2 f = d / N; + + // A sinusoid does not band-limit itself, so this cannot be dropped. + vec2 fade = nyquistFade(f); + + // Once a period spans several cells only one boundary in N carries a line, + // so the amplitude is spread back out. N == 1 is untouched. + vec2 amp = clamp(lp_grid * 2.0 * vec2(lp_balance, 1.0 - lp_balance), 0.0, 1.0) + * fade * (2.0 / (N + 1.0)); + + // Half an output pixel, in cycles: puts one sample per cycle on the + // trough, which is what lets a two-pixel pitch resolve at all. + vec2 phase = 0.5 * f; + + // The pattern coordinate, in periods. With N == 1 this is exactly p. + vec2 t = p / N; + vec2 hh = 0.4995 * f; + + // The aperture integral over the footprint, the exact box filter. Both ends + // are symmetric about X, so one sin and one cos of X do the work of four + // and the stripes below reuse the pair. + vec2 X = TAU * (t - phase); + vec2 sinX = sin(X); + vec2 cosX = cos(X); + + vec2 Y = TAU * hh; + vec2 sinY = sin(Y); + vec2 cosY = cos(Y); + vec2 k = amp / TAU; + + // Alo must use the UNCLAMPED Iraw, or the two disagree where I clamps. + vec2 Iraw = 2.0 * hh - k * (2.0 * cosX * sinY); + vec2 Alo = t - 0.5 * Iraw - (k * cosY) * sinX; + vec2 I = max(Iraw, 1e-6); + + // Peak-normalised, so the flat top lands at 1 and nothing meets the clamp. + vec2 g = I * (1.0 / (2.0 * hh * (1.0 + amp))); + float gain = g.x * g.y; + + // The mesh's dark line and the scaler's transition pixel both sit on the + // cell boundary, so the blend is weighted by aperture, not by area. + vec2 Bt = B / N; + vec2 AB = Bt - k * sin(TAU * (Bt - phase)); + vec2 w = clamp((AB - Alo) / I, 0.0, 1.0); + + // One LINEAR tap, handed the weights above: this texcoord asks the texture + // unit for exactly mix(T[B], T[B-1], w). + vec3 color = COMPAT_TEXTURE(Texture, (B + 0.5 - w) / TextureSize).rgb; + + // Three sinusoids 120 degrees apart, summing to exactly 3 at every pixel, + // so they are luminance neutral and blue costs no third cosine. + vec3 stripe = vec3(1.0); + if (lp_subpixels > 0.0) { + float sinc = boxSinc(f.x); + float ac = lp_subpixels * sinc * fade.x; + vec2 rg = 1.0 + ac * vec2(COS_TAU_6 * cosX.x + SIN_TAU_6 * sinX.x, + -cosX.x); + stripe = vec3(rg, 3.0 - rg.x - rg.y); + + // A column mesh and the stripes share a pitch, so whichever stripe + // lands on the dark line is dimmed. Divide the cast back out; M must be + // the BOX-FILTERED amplitude or it overshoots where the filter bites. + float M = amp.x * sinc; + vec3 corr = 1.0 - 0.5 * M * ac * vec3(COS_TAU_6, -1.0, COS_TAU_6); + // sqrt() below halves any deviation on the way to the encoded value, + // so the correction is halved here to match. + stripe /= sqrt(max(corr, 1e-3)); + + if (lp_layout >= 0.5) { + stripe = stripe.bgr; + } + } + + // Treating the encoding as a gamma of 2 makes sqrt(linear * m) equal + // encoded * sqrt(m), so one square root replaces the decode and re-encode. + // Brightness rides the pattern, so the clamp lands on the product. + vec3 m = sqrt(max(stripe * (gain * lp_brightness), 0.0)); + + // The base is clamped because pow(0, g) is undefined and returns NaN on + // real drivers. 1e-8, not 1e-5, which would lift pure black to 1/255. + if (abs(lp_gamma - 1.0) > 0.001) { + color = pow(max(color, 1e-8), vec3(lp_gamma)); + } + + vec3 outc = color * m; + + FragColor = vec4(clamp(outc, 0.0, 1.0), 1.0); +} + + +#endif diff --git a/skeleton/BASE/Shaders/glsl/pixel-perfect.glsl b/skeleton/BASE/Shaders/glsl/pixel-perfect.glsl new file mode 100644 index 000000000..379b04e80 --- /dev/null +++ b/skeleton/BASE/Shaders/glsl/pixel-perfect.glsl @@ -0,0 +1,175 @@ +// pixel-perfect v8 - uniform pixel blocks and colour controls. +// ----------------------------------------------------------------------------- +// Licence: MIT - Copyright (c) 2026 sinedied +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: the above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND. +// ----------------------------------------------------------------------------- +// PARAMETERS +// +// pp_brightness 0.50 - 4.00 Output gain. 1.00 disables it. +// pp_contrast 0.00 - 2.00 Contrast. 1.00 disables it. +// pp_saturation 0.00 - 2.00 Colour intensity. 1.00 disables it. +// pp_gamma 0.50 - 2.00 Output gamma. 1.00 disables it. +// pp_temperature -1.00 - 1.00 Warm above 0, cool below. 0.00 is off. +// pp_tint -1.00 - 1.00 Green above 0, magenta below. 0.00 is off. +// ----------------------------------------------------------------------------- +// A clean upscale: every source pixel becomes an even block, with no shimmer +// and no blur. The plain, fast default when you want the picture and nothing +// else, plus simple colour controls for tuning it to a screen. +// +// Notes: +// - Needs a LINEAR filter, set in the preset. Under NEAREST the scale becomes +// ordinary nearest-neighbour and the picture gets ragged edges. +// - Render at the output resolution, 1:1 with the display. +// - Brightness above 1.00 clips, may create pattern artifacts against the +// pixel grid unless the output is an integer scale. + +#pragma parameter pp_brightness "Brightness" 1.00 0.50 4.00 0.05 +#pragma parameter pp_contrast "Contrast" 1.00 0.00 2.00 0.05 +#pragma parameter pp_saturation "Saturation" 1.00 0.00 2.00 0.05 +#pragma parameter pp_gamma "Gamma" 1.00 0.50 2.00 0.05 +#pragma parameter pp_temperature "Cool / warm balance" 0.00 -1.00 1.00 0.01 +#pragma parameter pp_tint "Magenta / green balance" 0.00 -1.00 1.00 0.01 + +#if defined(VERTEX) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING out +#define COMPAT_ATTRIBUTE in +#define COMPAT_TEXTURE texture +#else +#define COMPAT_VARYING varying +#define COMPAT_ATTRIBUTE attribute +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#define COMPAT_PRECISION mediump +#else +#define COMPAT_PRECISION +#endif + +COMPAT_ATTRIBUTE vec4 VertexCoord; +COMPAT_ATTRIBUTE vec4 COLOR; +COMPAT_ATTRIBUTE vec4 TexCoord; +COMPAT_VARYING vec4 COL0; +COMPAT_VARYING vec4 TEX0; + +uniform mat4 MVPMatrix; +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; + +void main() +{ + gl_Position = MVPMatrix * VertexCoord; + COL0 = COLOR; + TEX0.xy = TexCoord.xy; +} + +#elif defined(FRAGMENT) + +#if __VERSION__ >= 130 +#define COMPAT_VARYING in +#define COMPAT_TEXTURE texture +out vec4 FragColor; +#else +#define COMPAT_VARYING varying +#define FragColor gl_FragColor +#define COMPAT_TEXTURE texture2D +#endif + +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#define COMPAT_PRECISION highp +#else +#define COMPAT_PRECISION +#endif + +uniform COMPAT_PRECISION int FrameDirection; +uniform COMPAT_PRECISION int FrameCount; +uniform COMPAT_PRECISION vec2 OutputSize; +uniform COMPAT_PRECISION vec2 TextureSize; +uniform COMPAT_PRECISION vec2 InputSize; +uniform sampler2D Texture; +COMPAT_VARYING vec4 TEX0; + +#ifdef PARAMETER_UNIFORM +uniform COMPAT_PRECISION float pp_brightness; +uniform COMPAT_PRECISION float pp_contrast; +uniform COMPAT_PRECISION float pp_saturation; +uniform COMPAT_PRECISION float pp_gamma; +uniform COMPAT_PRECISION float pp_temperature; +uniform COMPAT_PRECISION float pp_tint; +#else +#define pp_brightness 1.0 +#define pp_contrast 1.0 +#define pp_saturation 1.0 +#define pp_gamma 1.0 +#define pp_temperature 0.0 +#define pp_tint 0.0 +#endif + +// Rec.709 luma, for the saturation mix. Applied to encoded values: the round +// trip to linear is the construction the scaler exists to avoid. +const vec3 LUMA = vec3(0.2126, 0.7152, 0.0722); + +void main() +{ + // Source texels. The max() guards an unset InputSize, which is 0 and would + // make h a zero divisor below. + vec2 p = TEX0.xy * TextureSize; + vec2 h = max(0.4995 * InputSize / OutputSize, 1e-6); + + // B is the nearest texel boundary; w is the share of the footprint on its + // low side. Clamps to 0 or 1 wherever the footprint sits inside one texel, + // which is what keeps the blocks flat. + vec2 B = floor(p + 0.5); + vec2 w = clamp((B - p + h) / (2.0 * h), 0.0, 1.0); + + // One LINEAR tap, handed w: this texcoord asks the texture unit for + // exactly mix(T[B], T[B-1], w). + vec3 col = COMPAT_TEXTURE(Texture, (B + 0.5 - w) / TextureSize).rgb; + + // Balance first, so saturation sees the tinted colour and 0 really is + // monochrome. Brightness, contrast and saturation then fold into one affine + // map - do not un-fold it, or the defaults stop being exactly col * 1.0. + // Tested separately, not summed, or warm could cancel a cool tint. + if (pp_brightness != 1.0 || pp_contrast != 1.0 || pp_saturation != 1.0 + || pp_temperature != 0.0 || pp_tint != 0.0) { + // Warm/cool trades red against blue, tint trades green against both. + // Not luma-normalised, so they shift the level a little too. + col *= 1.0 + pp_temperature * vec3(1.0, 0.0, -1.0) + + pp_tint * vec3(-0.5, 1.0, -0.5); + + float ga = pp_brightness * pp_contrast; + float gb = 0.5 - 0.5 * pp_contrast; + col = col * (ga * pp_saturation) + + (dot(col, LUMA) * (ga * (1.0 - pp_saturation)) + gb); + } + + // The base is clamped because pow(0, g) is undefined and returns NaN on + // real drivers. 1e-8, not 1e-5, which would lift pure black to 1/255. + if (abs(pp_gamma - 1.0) > 0.001) { + col = pow(max(col, 1e-8), vec3(pp_gamma)); + } + + // Last, always: a grade or a gamma can leave 0 to 1, the blend cannot. + FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); +} + +#endif diff --git a/skeleton/BASE/Shaders/sets/GB/Retro.cfg b/skeleton/BASE/Shaders/sets/GB/Retro.cfg new file mode 100755 index 000000000..0b3b6776a --- /dev/null +++ b/skeleton/BASE/Shaders/sets/GB/Retro.cfg @@ -0,0 +1,24 @@ +minarch_screen_effect = None +minarch_screen_scaling = Native +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = dmg-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +dp_grid = 0.30 +dp_gap = 1.00 +dp_shadow = 0.40 +dp_brightness = 1.75 +dp_gamma = 1.50 +dp_temperature = -0.12 +dp_tint = -0.15 + +gambatte_gb_colorization = internal +gambatte_gb_internal_palette = TWB64 - Pack 1 +gambatte_gb_palette_twb64_1 = TWB64 040 - DMG Ver. +gambatte_gbc_color_correction = GBC only +gambatte_gbc_color_correction_mode = accurate diff --git a/skeleton/BASE/Shaders/sets/GB/Sharp.cfg b/skeleton/BASE/Shaders/sets/GB/Sharp.cfg new file mode 100755 index 000000000..8d792f282 --- /dev/null +++ b/skeleton/BASE/Shaders/sets/GB/Sharp.cfg @@ -0,0 +1,23 @@ +minarch_screen_effect = None +minarch_screen_scaling = Native +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = pixel-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +pp_brightness = 1.75 +pp_contrast = 1.00 +pp_saturation = 1.00 +pp_gamma = 1.50 +pp_temperature = -0.12 +pp_tint = -0.15 + +gambatte_gb_colorization = internal +gambatte_gb_internal_palette = TWB64 - Pack 1 +gambatte_gb_palette_twb64_1 = TWB64 040 - DMG Ver. +gambatte_gbc_color_correction = GBC only +gambatte_gbc_color_correction_mode = accurate diff --git a/skeleton/BASE/Shaders/sets/GBA/Retro.cfg b/skeleton/BASE/Shaders/sets/GBA/Retro.cfg old mode 100644 new mode 100755 index a94de845d..ed3cf0e6e --- a/skeleton/BASE/Shaders/sets/GBA/Retro.cfg +++ b/skeleton/BASE/Shaders/sets/GBA/Retro.cfg @@ -1,12 +1,18 @@ -minarch_nrofshaders = 2 -minarch_shader1 = pixellate.glsl -minarch_shader1_filter = NEAREST +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = lcd-perfect.glsl +minarch_shader1_filter = LINEAR minarch_shader1_srctype = source minarch_shader1_scaletype = source minarch_shader1_upscale = screen -minarch_shader2 = lcd3x.glsl -minarch_shader2_filter = NEAREST -minarch_shader2_srctype = source -minarch_shader2_scaletype = source -minarch_shader2_upscale = screen -minarch_scale_filter = NEAREST + +lp_grid = 0.30 +lp_balance = 0.60 +lp_min_pitch = 3.00 +lp_subpixels = 0.00 +lp_layout = 1.00 +lp_brightness = 1.00 +lp_gamma = 1.00 diff --git a/skeleton/BASE/Shaders/sets/GBC/Retro.cfg b/skeleton/BASE/Shaders/sets/GBC/Retro.cfg new file mode 100755 index 000000000..5bba5413e --- /dev/null +++ b/skeleton/BASE/Shaders/sets/GBC/Retro.cfg @@ -0,0 +1,21 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = lcd-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +lp_grid = 0.20 +lp_balance = 0.80 +lp_min_pitch = 3.00 +lp_subpixels = 0.20 +lp_layout = 0.00 +lp_brightness = 1.25 +lp_gamma = 1.00 + +gambatte_gbc_color_correction = GBC only +gambatte_gbc_color_correction_mode = accurate diff --git a/skeleton/BASE/Shaders/sets/GBC/Sharp.cfg b/skeleton/BASE/Shaders/sets/GBC/Sharp.cfg new file mode 100755 index 000000000..c2d79b09c --- /dev/null +++ b/skeleton/BASE/Shaders/sets/GBC/Sharp.cfg @@ -0,0 +1,20 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = pixel-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +pp_brightness = 1.00 +pp_contrast = 1.00 +pp_saturation = 1.00 +pp_gamma = 1.00 +pp_temperature = -0.00 +pp_tint = -0.00 + +gambatte_gbc_color_correction = GBC only +gambatte_gbc_color_correction_mode = accurate diff --git a/skeleton/BASE/Shaders/sets/GG/Retro.cfg b/skeleton/BASE/Shaders/sets/GG/Retro.cfg new file mode 100755 index 000000000..e9a92ef6f --- /dev/null +++ b/skeleton/BASE/Shaders/sets/GG/Retro.cfg @@ -0,0 +1,18 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = lcd-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +lp_grid = 0.20 +lp_balance = 0.80 +lp_min_pitch = 3.00 +lp_subpixels = 0.20 +lp_layout = 0.00 +lp_brightness = 1.40 +lp_gamma = 1.00 diff --git a/skeleton/BASE/Shaders/sets/MD/Retro.cfg b/skeleton/BASE/Shaders/sets/MD/Retro.cfg new file mode 100755 index 000000000..bf4ab2908 --- /dev/null +++ b/skeleton/BASE/Shaders/sets/MD/Retro.cfg @@ -0,0 +1,19 @@ +minarch_screen_effect = None +minarch_screen_scaling = Fullscreen +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = crt-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +cp_scanlines = 0.60 +cp_rgb_mask = 0.05 +cp_mask_type = 1.00 +cp_mask_size = 1.00 +cp_min_pitch = 5.00 +cp_curvature = 0.00 +cp_brightness = 1.50 +cp_gamma = 1.00 diff --git a/skeleton/BASE/Shaders/sets/MD/Sharp.cfg b/skeleton/BASE/Shaders/sets/MD/Sharp.cfg new file mode 100755 index 000000000..c8330e938 --- /dev/null +++ b/skeleton/BASE/Shaders/sets/MD/Sharp.cfg @@ -0,0 +1,17 @@ +minarch_screen_effect = None +minarch_screen_scaling = Fullscreen +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = pixel-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +pp_brightness = 1.00 +pp_contrast = 1.00 +pp_saturation = 1.00 +pp_gamma = 1.00 +pp_temperature = -0.00 +pp_tint = -0.00 diff --git a/skeleton/BASE/Shaders/sets/P8/Retro.cfg b/skeleton/BASE/Shaders/sets/P8/Retro.cfg new file mode 100755 index 000000000..74124e733 --- /dev/null +++ b/skeleton/BASE/Shaders/sets/P8/Retro.cfg @@ -0,0 +1,19 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + +minarch_nrofshaders = 1 +minarch_shader1 = lcd-perfect.glsl +minarch_shader1_filter = LINEAR +minarch_shader1_srctype = source +minarch_shader1_scaletype = source +minarch_shader1_upscale = screen + +lp_grid = 0.30 +lp_balance = 0.60 +lp_min_pitch = 3.00 +lp_subpixels = 0.20 +lp_layout = 0.00 +lp_brightness = 1.50 +lp_gamma = 1.00 + diff --git a/skeleton/BASE/Shaders/sets/Retro.cfg b/skeleton/BASE/Shaders/sets/Retro.cfg old mode 100644 new mode 100755 index 434d37c40..eb38f8fb1 --- a/skeleton/BASE/Shaders/sets/Retro.cfg +++ b/skeleton/BASE/Shaders/sets/Retro.cfg @@ -1,7 +1,19 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + minarch_nrofshaders = 1 -minarch_shader1 = res-independent-scanlines.glsl -minarch_shader1_filter = NEAREST +minarch_shader1 = crt-perfect.glsl +minarch_shader1_filter = LINEAR minarch_shader1_srctype = source minarch_shader1_scaletype = source minarch_shader1_upscale = screen -minarch_scale_filter = NEAREST + +cp_scanlines = 0.60 +cp_rgb_mask = 0.05 +cp_mask_type = 1.00 +cp_mask_size = 1.00 +cp_min_pitch = 5.00 +cp_curvature = 0.00 +cp_brightness = 1.50 +cp_gamma = 1.00 diff --git a/skeleton/BASE/Shaders/sets/Sharp.cfg b/skeleton/BASE/Shaders/sets/Sharp.cfg old mode 100644 new mode 100755 index 189193b42..6d38c4156 --- a/skeleton/BASE/Shaders/sets/Sharp.cfg +++ b/skeleton/BASE/Shaders/sets/Sharp.cfg @@ -1,7 +1,17 @@ +minarch_screen_effect = None +minarch_screen_scaling = Aspect +minarch_scale_filter = LINEAR + minarch_nrofshaders = 1 -minarch_shader1 = pixellate.glsl -minarch_shader1_filter = NEAREST +minarch_shader1 = pixel-perfect.glsl +minarch_shader1_filter = LINEAR minarch_shader1_srctype = source minarch_shader1_scaletype = source minarch_shader1_upscale = screen -minarch_scale_filter = NEAREST + +pp_brightness = 1.00 +pp_contrast = 1.00 +pp_saturation = 1.00 +pp_gamma = 1.00 +pp_temperature = -0.00 +pp_tint = -0.00 From 5c0ba43c8a3ac33ac6aef8de4ccb2b12a33e730a Mon Sep 17 00:00:00 2001 From: sinedied Date: Tue, 4 Aug 2026 23:03:48 +0200 Subject: [PATCH 9/9] chore: reduce gb shadow --- skeleton/BASE/Shaders/sets/GB/Retro.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skeleton/BASE/Shaders/sets/GB/Retro.cfg b/skeleton/BASE/Shaders/sets/GB/Retro.cfg index 0b3b6776a..d38b241e0 100755 --- a/skeleton/BASE/Shaders/sets/GB/Retro.cfg +++ b/skeleton/BASE/Shaders/sets/GB/Retro.cfg @@ -11,7 +11,7 @@ minarch_shader1_upscale = screen dp_grid = 0.30 dp_gap = 1.00 -dp_shadow = 0.40 +dp_shadow = 0.20 dp_brightness = 1.75 dp_gamma = 1.50 dp_temperature = -0.12