!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.
This commit is contained in:
profi200 2024-12-03 20:34:53 +01:00
parent 877f7c61d0
commit dd90d498c4
No known key found for this signature in database
GPG Key ID: FD2BAB7782919B0A
4 changed files with 220 additions and 93 deletions

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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;