From dd90d498c423cdfd527acc4cd5c80cb5bec5e937 Mon Sep 17 00:00:00 2001 From: profi200 Date: Tue, 3 Dec 2024 20:34:53 +0100 Subject: [PATCH] !Breaking change! Changed the config file format to use strings in many places instead of values. Added a new saturation setting for color profiles other than none. Removed lcdGamma and displayGamma temporarily. Updated README.md with all the changes. --- README.md | 85 ++++++++++++++----------- include/arm11/config.h | 16 +++-- source/arm11/config.c | 133 ++++++++++++++++++++++++++++++++------- source/arm11/oaf_video.c | 79 ++++++++++++++++------- 4 files changed, 220 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 9d90489..2d37cbe 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Settings are stored in `/3ds/open_agb_firm/config.ini`. ### General General settings. -`u8 backlight` - Backlight brightness in luminance (cd/m²) +`u8 backlight` - Backlight brightness in luminance (cd/m²). * Default: `64` * Possible values: * Old 3DS: `20`-`117` @@ -57,47 +57,47 @@ General settings. * Values ≤`64` are recommended. * Hardware calibration from your CTRNAND is required to get the correct brightness for both LCDs. -`u8 backlightSteps` - How much to adjust backlight brightness by +`u8 backlightSteps` - How much to adjust backlight brightness by. * Default: `5` -`bool directBoot` - Skip GBA BIOS intro at game startup +`bool directBoot` - Skip GBA BIOS intro at game startup. * Default: `false` -`bool useGbaDb` - Use `gba_db.bin` to get save types +`bool useGbaDb` - Use `gba_db.bin` to get save types. * Default: `true` -`bool useSavesFolder` - Use `/3ds/open_agb_firm/saves` for save files instead of the ROM directory +`bool useSavesFolder` - Use `/3ds/open_agb_firm/saves` for save files instead of the ROM directory. * Default: `true` ### Video Video-related settings. -`u8 scaler` - Video scaler. 0 = none, 1 = bilinear, 2 = hardware. -* Default: `2` +`string scaler` - Video scaler. +* Default: `matrix` +* Options: `none`, `bilinear`, `matrix` -`float gbaGamma` - GBA input gamma -* Default: `2.2` +`string colorProfile` - Color correction profile. +* Default: `none` +* Options: `none`, `gba`, `nds`, `nds_white`, `nso`, `identity` +* If you just want less saturated colors or change other basic settings like contrast or brightness then set this to `identity`. +* Due to most 2/3DS LCDs not being calibrated correctly from factory the look may not match exactly what you see on real hardware. +* Due to a lot of extra RAM access and extra CPU processing per frame, battery runtime is affected with color profiles other than `none`. -`float lcdGamma` - Output LCD gamma -* Default : `1.54` - -`float contrast` - Screen gain +`float contrast` - Screen gain. No effect when `colorProfile=none`. * Default: `1.0` -`float brightness` - Screen lift +`float brightness` - Screen lift. No effect when `colorProfile=none`. * Default: `0.0` -`string colorProfile` - Color correction profile. `none`, `gba`, `nds` or `nds_white`. -* Default: `none` -* For the gba profile it's recommended to adjust lcdGamma to match a GBA. For New 3DS XL with IPS LCD roughly 1.8 is good. -* Due to most 2/3DS LCDs not being calibrated correctly from factory the look may not match exactly what you see on a real GBA. -* Due to a lot of extra RAM access and up to 6.3 ms (worst case for scaler=2) of extra CPU processing time per frame, battery run time is affected with color profiles other than none. +`float saturation` - Screen saturation. No effect when `colorProfile=none`. +* Default: `1.0` ### Audio Audio settings. -`u8 audioOut` - Audio output. 0 = auto, 1 = speakers, 2 = headphones. -* Default: `0` +`string audioOut` - Audio output. +* Default: `auto` +* Options: `auto`, `speakers`, `headphones` `s8 volume` - Audio volume. Values above 48 mean control via volume slider. Range -128 (muted) to -20 (100%). Avoid the range -19 to 48. * Default: `127` @@ -139,7 +139,7 @@ Note that button mappings can cause input lag of up to 1 frame depending on when `L` - Button map for the L button. * Default: `none` -Example: +Example which maps the D-Pad and Circle-Pad to the GBA D-Pad: ``` [input] RIGHT=RIGHT,CP_RIGHT @@ -151,29 +151,38 @@ DOWN=DOWN,CP_DOWN ### Game Game-specific settings. Only intended to be used in the per-game settings (romName.ini in `/3ds/open_agb_firm/saves`). -`u8 saveSlot` - Savegame slot (0-9) +`u8 saveSlot` - Savegame slot (0-9). * Default: `0` -`u8 saveType` - Override to use a specific save type, see values for `defaultSave` (0-15, 255) -* Default: `255` (disabled) +`string saveType` - Override to use a specific save type. +* Default: `auto` +* Options starting with `rom_256m` are intended for 32 MiB games. Options ending with `rtc` enable the hardware real-time clock. Options: + * `eeprom_8k` + * `rom_256m_eeprom_8k` + * `eeprom_64k` + * `rom_256m_eeprom_64k` + * `flash_512k_atmel_rtc` + * `flash_512k_atmel` + * `flash_512k_sst_rtc` + * `flash_512k_sst` + * `flash_512k_panasonic_rtc` + * `flash_512k_panasonic` + * `flash_1m_macronix_rtc` + * `flash_1m_macronix` + * `flash_1m_sanyo_rtc` + * `flash_1m_sanyo` + * `sram_256k` + * `none` + * `auto` ### Advanced Options for advanced users. No pun intended. -`bool saveOverride` - Open save type override menu after selecting a game +`bool saveOverride` - Open save type override menu after selecting a game. * Default: `false` -`u16 defaultSave` - Change save type default when save type is not in `gba_db.bin` and cannot be autodetected -* Default: `14` (SRAM 256k) -* Possible values: - * `0`, `1`: EEPROM 8k - * `2`, `3`: EEPROM 64k - * `4`, `6`, `8`: Flash 512k RTC - * `5`, `7`, `9`: Flash 512k - * `10`, `12`: Flash 1m RTC - * `11`, `13`: Flash 1m - * `14`: SRAM 256k - * `15`: None +`string defaultSave` - Save type default when save type is not in `gba_db.bin` and cannot be autodetected. Same options as for `saveType` above except `auto` is not supported. +* Default: `sram_256k` ## Patches open_agb_firm supports automatically applying IPS and UPS patches. To use a patch, rename the patch file to match the ROM file name (without the extension). @@ -274,4 +283,4 @@ You may use this under the terms of the GNU General Public License GPL v3 or the * **[hunterk and Pokefan531 for their amazing libretro shaders](https://forums.libretro.com/t/real-gba-and-ds-phat-colors/1540/220)** * ...everyone who contributed to **3dbrew.org** -Copyright (C) 2024 derrek, profi200, d0k3 +Copyright (C) 2024 derrek, profi200, d0k3 \ No newline at end of file diff --git a/include/arm11/config.h b/include/arm11/config.h index 8179c75..742274c 100644 --- a/include/arm11/config.h +++ b/include/arm11/config.h @@ -42,12 +42,11 @@ typedef struct bool useSavesFolder; // [video] - u8 scaler; // 0 = 1:1, 1 = bilinear (GPU) x1.5, 2 = matrix (hardware) x1.5. - float gbaGamma; - float lcdGamma; - float contrast; - float brightness; - u8 colorProfile; // 0 = none, 1 = GBA, 2 = DS phat, 3 = DS phat white. + u8 scaler; // 0 = 1:1/none, 1 = bilinear (GPU) x1.5, 2 = matrix (hardware) x1.5. + u8 colorProfile; // 0 = none, 1 = GBA, 2 = DS phat, 3 = DS phat white, 4 = Nintendo Switch Online, 5 = identity. + float contrast; // Range 0.0-1.0. + float brightness; // Range 0.0-1.0. + float saturation; // Range 0.0-1.0. // [audio] u8 audioOut; // 0 = auto, 1 = speakers, 2 = headphones. @@ -63,11 +62,10 @@ typedef struct // [advanced] bool saveOverride; - u16 defaultSave; + u16 defaultSave; // TODO: Should be u8. Investigate if u8 has any downsides. } OafConfig; -//static_assert(sizeof(OafConfig) == 76, "nope"); -extern OafConfig g_oafConfig; +extern OafConfig g_oafConfig; // Global config in config.c. diff --git a/source/arm11/config.c b/source/arm11/config.c index 685a628..23d72da 100644 --- a/source/arm11/config.c +++ b/source/arm11/config.c @@ -32,19 +32,21 @@ "directBoot=false\n" \ "useGbaDb=true\n" \ "useSavesFolder=true\n\n" \ + \ "[video]\n" \ - "scaler=2\n" \ - "gbaGamma=2.2\n" \ - "lcdGamma=1.54\n" \ + "scaler=matrix\n" \ + "colorProfile=none\n" \ "contrast=1.0\n" \ "brightness=0.0\n" \ - "colorProfile=none\n\n" \ + "saturation=1.0\n\n" \ + \ "[audio]\n" \ - "audioOut=0\n" \ + "audioOut=auto\n" \ "volume=127\n\n" \ + \ "[advanced]\n" \ "saveOverride=false\n" \ - "defaultSave=14" + "defaultSave=sram_256k" @@ -60,11 +62,10 @@ OafConfig g_oafConfig = // [video] 2, // scaler - 2.2f, // gbaGamma - 1.54f, // lcdGamma + 0, // colorProfile 1.f, // contrast 0.f, // brightness - 0, // colorProfile + 1.f, // saturation // [audio] 0, // Automatic audio output. @@ -86,7 +87,7 @@ OafConfig g_oafConfig = // [game] 0, // saveSlot - 0xFF, // saveType + 255, // saveType // [advanced] false, // saveOverride @@ -131,7 +132,7 @@ static u32 parseButtons(const char *str) return map & ~(1u<<12); } -static int cfgIniCallback(void* user, const char* section, const char* name, const char* value) +static int cfgIniCallback(void *user, const char *section, const char *name, const char *value) { OafConfig *const config = (OafConfig*)user; @@ -151,15 +152,14 @@ static int cfgIniCallback(void* user, const char* section, const char* name, con else if(strcmp(section, "video") == 0) { if(strcmp(name, "scaler") == 0) - config->scaler = (u8)strtoul(value, NULL, 10); - else if(strcmp(name, "gbaGamma") == 0) - config->gbaGamma = str2float(value); - else if(strcmp(name, "lcdGamma") == 0) - config->lcdGamma = str2float(value); - else if(strcmp(name, "contrast") == 0) - config->contrast = str2float(value); - else if(strcmp(name, "brightness") == 0) - config->brightness = str2float(value); + { + if(strcmp(value, "none") == 0) + config->scaler = 0; + else if(strcmp(value, "bilinear") == 0) + config->scaler = 1; + else if(strcmp(value, "matrix") == 0) + config->scaler = 2; + } else if(strcmp(name, "colorProfile") == 0) { if(strcmp(value, "none") == 0) @@ -170,14 +170,31 @@ static int cfgIniCallback(void* user, const char* section, const char* name, con config->colorProfile = 2; else if(strcmp(value, "nds_white") == 0) config->colorProfile = 3; + else if(strcmp(value, "nso") == 0) + config->colorProfile = 4; + else if(strcmp(value, "identity") == 0) + config->colorProfile = 5; //else if(strcmp(value, "custom") == 0) // TODO: Implement user provided profile. - // config->colorProfile = 4; + // config->colorProfile = 6; } + else if(strcmp(name, "contrast") == 0) + config->contrast = str2float(value); + else if(strcmp(name, "brightness") == 0) + config->brightness = str2float(value); + else if(strcmp(name, "saturation") == 0) + config->saturation = str2float(value); } else if(strcmp(section, "audio") == 0) { if(strcmp(name, "audioOut") == 0) - config->audioOut = (u8)strtoul(value, NULL, 10); + { + if(strcmp(value, "auto") == 0) + config->audioOut = 0; + else if(strcmp(value, "speakers") == 0) + config->audioOut = 1; + else if(strcmp(value, "headphones") == 0) + config->audioOut = 2; + } else if(strcmp(name, "volume") == 0) config->volume = (s8)strtol(value, NULL, 10); } @@ -197,14 +214,82 @@ static int cfgIniCallback(void* user, const char* section, const char* name, con if(strcmp(name, "saveSlot") == 0) config->saveSlot = (u8)strtoul(value, NULL, 10); if(strcmp(name, "saveType") == 0) - config->saveType = (u8)strtoul(value, NULL, 10); + { + if(strcmp(value, "eeprom_8k") == 0) + config->saveType = 0; + if(strcmp(value, "rom_256m_eeprom_8k") == 0) + config->saveType = 1; + if(strcmp(value, "eeprom_64k") == 0) + config->saveType = 2; + if(strcmp(value, "rom_256m_eeprom_64k") == 0) + config->saveType = 3; + if(strcmp(value, "flash_512k_atmel_rtc") == 0) + config->saveType = 4; + if(strcmp(value, "flash_512k_atmel") == 0) + config->saveType = 5; + if(strcmp(value, "flash_512k_sst_rtc") == 0) + config->saveType = 6; + if(strcmp(value, "flash_512k_sst") == 0) + config->saveType = 7; + if(strcmp(value, "flash_512k_panasonic_rtc") == 0) + config->saveType = 8; + if(strcmp(value, "flash_512k_panasonic") == 0) + config->saveType = 9; + if(strcmp(value, "flash_1m_macronix_rtc") == 0) + config->saveType = 10; + if(strcmp(value, "flash_1m_macronix") == 0) + config->saveType = 11; + if(strcmp(value, "flash_1m_sanyo_rtc") == 0) + config->saveType = 12; + if(strcmp(value, "flash_1m_sanyo") == 0) + config->saveType = 13; + if(strcmp(value, "sram_256k") == 0) + config->saveType = 14; + if(strcmp(value, "none") == 0) + config->saveType = 15; + if(strcmp(value, "auto") == 0) + config->saveType = 255; + } } else if(strcmp(section, "advanced") == 0) { if(strcmp(name, "saveOverride") == 0) config->saveOverride = (strcmp(value, "false") == 0 ? false : true); if(strcmp(name, "defaultSave") == 0) - config->defaultSave = (u16)strtoul(value, NULL, 10); + { + if(strcmp(value, "eeprom_8k") == 0) + config->defaultSave = 0; + if(strcmp(value, "rom_256m_eeprom_8k") == 0) + config->defaultSave = 1; + if(strcmp(value, "eeprom_64k") == 0) + config->defaultSave = 2; + if(strcmp(value, "rom_256m_eeprom_64k") == 0) + config->defaultSave = 3; + if(strcmp(value, "flash_512k_atmel_rtc") == 0) + config->defaultSave = 4; + if(strcmp(value, "flash_512k_atmel") == 0) + config->defaultSave = 5; + if(strcmp(value, "flash_512k_sst_rtc") == 0) + config->defaultSave = 6; + if(strcmp(value, "flash_512k_sst") == 0) + config->defaultSave = 7; + if(strcmp(value, "flash_512k_panasonic_rtc") == 0) + config->defaultSave = 8; + if(strcmp(value, "flash_512k_panasonic") == 0) + config->defaultSave = 9; + if(strcmp(value, "flash_1m_macronix_rtc") == 0) + config->defaultSave = 10; + if(strcmp(value, "flash_1m_macronix") == 0) + config->defaultSave = 11; + if(strcmp(value, "flash_1m_sanyo_rtc") == 0) + config->defaultSave = 12; + if(strcmp(value, "flash_1m_sanyo") == 0) + config->defaultSave = 13; + if(strcmp(value, "sram_256k") == 0) + config->defaultSave = 14; + if(strcmp(value, "none") == 0) + config->defaultSave = 15; + } } else return 0; // Error. diff --git a/source/arm11/oaf_video.c b/source/arm11/oaf_video.c index 3ba4e4c..5e47ee8 100644 --- a/source/arm11/oaf_video.c +++ b/source/arm11/oaf_video.c @@ -126,7 +126,7 @@ typedef struct float displayGamma; } ColorProfile; -static const ColorProfile g_colorProfiles[3] = +static const ColorProfile g_colorProfiles[5] = { { // libretro GBA color (sRGB). Credits: hunterk and Pokefan531. 2.f + 0.5f, @@ -151,6 +151,22 @@ static const ColorProfile g_colorProfiles[3] = 0.1f, 0.64f, 0.26f, 0.1075f, 0.1725f, 0.72f, 1.f / 2.f + }, + { // libretro Nintendo Switch Online (sRGB). Credits: hunterk and Pokefan531. + 2.2f + 0.8f, + 1.f, + 0.865f, 0.1225f, 0.0125f, + 0.0575f, 0.925f, 0.0125f, + 0.0575f, 0.1225f, 0.82f, + 1.f / 2.2f + }, + { // Identity. + 1.f, + 1.f, + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f, + 1.f / 1.f } }; @@ -159,9 +175,21 @@ ALWAYS_INLINE float clamp_float(const float x, const float min, const float max) return (x < min ? min : (x > max ? max : x)); } -static void makeColorLut(const ColorProfile *const p) +void makeColorLut(const ColorProfile *const p) { - u32 *colorLut = (u32*)COLOR_LUT_ADDR; + const float targetGamma = p->targetGamma; + const float contrast = g_oafConfig.contrast; + const float brightness = g_oafConfig.brightness / contrast; + const float targetContrast = powf(contrast, targetGamma); + + // Calculate saturation weights. + // Note: We are using the Rec. 709 luminance vector here. + const float sat = g_oafConfig.saturation; + const float rwgt = (1.f - sat) * 0.2126f; + const float gwgt = (1.f - sat) * 0.7152f; + const float bwgt = (1.f - sat) * 0.0722f; + + u32 *const colorLut = (u32*)COLOR_LUT_ADDR; for(u32 i = 0; i < 32768; i++) { // Convert to 8-bit and normalize. @@ -170,10 +198,9 @@ static void makeColorLut(const ColorProfile *const p) float r = (float)rgbFive2Eight(i>>10) / 255; // Convert to linear gamma. - const float targetGamma = p->targetGamma; - b = powf(b, targetGamma); - g = powf(g, targetGamma); - r = powf(r, targetGamma); + b = powf(b + brightness, targetGamma); + g = powf(g + brightness, targetGamma); + r = powf(r + brightness, targetGamma); // Apply luminance. const float lum = p->lum; @@ -193,33 +220,41 @@ static void makeColorLut(const ColorProfile *const p) * [rb][gb][ b] [b] */ // Assuming no alpha channel in original calculation. - float newB = p->rb * r + p->gb * g + p->b * b; - float newG = p->rg * r + p->g * g + p->bg * b; - float newR = p->r * r + p->gr * g + p->br * b; + float tmpB = p->rb * r + p->gb * g + p->b * b; + float tmpG = p->rg * r + p->g * g + p->bg * b; + float tmpR = p->r * r + p->gr * g + p->br * b; - newB = (newB < 0.f ? 0.f : newB); - newG = (newG < 0.f ? 0.f : newG); - newR = (newR < 0.f ? 0.f : newR); + // Apply saturation. + // Note: Some duplicated muls here. gcc optimizes them out. + b = rwgt * tmpR + gwgt * tmpG + (bwgt + sat) * tmpB; + g = rwgt * tmpR + (gwgt + sat) * tmpG + bwgt * tmpB; + r = (rwgt + sat) * tmpR + gwgt * tmpG + bwgt * tmpB; + + b = (b < 0.f ? 0.f : b); + g = (g < 0.f ? 0.f : g); + r = (r < 0.f ? 0.f : r); // Convert to display gamma. const float displayGamma = p->displayGamma; - newB = powf(newB, displayGamma); - newG = powf(newG, displayGamma); - newR = powf(newR, displayGamma); + b = powf(targetContrast * b, displayGamma); + g = powf(targetContrast * g, displayGamma); + r = powf(targetContrast * r, displayGamma); // Denormalize, clamp, convert to ABGR8 and write lut. - u32 tmp = 0xFF; // Alpha. - tmp |= clamp_s32(lroundf(newB * 255), 0, 255)<<8; - tmp |= clamp_s32(lroundf(newG * 255), 0, 255)<<16; - tmp |= clamp_s32(lroundf(newR * 255), 0, 255)<<24; - *colorLut++ = tmp; + u32 entry = 255; // Alpha. + entry |= clamp_s32(lroundf(b * 255), 0, 255)<<8; + entry |= clamp_s32(lroundf(g * 255), 0, 255)<<16; + entry |= clamp_s32(lroundf(r * 255), 0, 255)<<24; + colorLut[i] = entry; } - flushDCacheRange((void*)COLOR_LUT_ADDR, 1024u * 128); + flushDCacheRange(colorLut, 4 * 32768); } static Result dumpFrameTex(void) { + // Capture a single frame in native resolution. + // Note: This adds 1 frame of delay after pressing the screenshot buttons. if(LGYCAP_captureFrameUnscaled(LGYCAP_DEV_TOP) != KRES_OK) return RES_INVALID_ARG;