From 3a74cbe0dc1d46458946a4cec5f3c1c0bbf7751e Mon Sep 17 00:00:00 2001 From: DerKoun Date: Sun, 2 Feb 2020 01:18:32 +0100 Subject: [PATCH] beta 10.2 (bsnes 114.3) --- README.md | 21 +++- bsnes/emulator/emulator.hpp | 4 +- bsnes/emulator/hdtoolkit.hpp | 28 ++++++ bsnes/gb/Core/apu.c | 14 ++- bsnes/gb/Core/apu.h | 5 +- bsnes/gb/Core/debugger.c | 2 +- bsnes/gb/Core/display.c | 26 +++-- bsnes/gb/Core/gb.c | 80 ++++++++++++--- bsnes/gb/Core/gb.h | 32 +++++- bsnes/gb/Core/sm83_cpu.c | 34 +++---- bsnes/sfc/coprocessor/sdd1/serialization.cpp | 7 +- bsnes/sfc/interface/configuration.cpp | 5 + bsnes/sfc/interface/configuration.hpp | 7 +- bsnes/sfc/ppu-fast/background.cpp | 34 +++---- bsnes/sfc/ppu-fast/io.cpp | 7 +- bsnes/sfc/ppu-fast/line.cpp | 99 +++++++++++++++++-- bsnes/sfc/ppu-fast/mode7.cpp | 9 +- bsnes/sfc/ppu-fast/mode7hd.cpp | 2 +- bsnes/sfc/ppu-fast/object.cpp | 12 ++- bsnes/sfc/ppu-fast/ppu.cpp | 31 +++--- bsnes/sfc/ppu-fast/ppu.hpp | 19 +++- bsnes/sfc/ppu-fast/serialization.cpp | 9 +- bsnes/sfc/ppu/background.cpp | 56 ++++------- bsnes/sfc/ppu/background.hpp | 16 +-- bsnes/sfc/ppu/io.cpp | 7 +- bsnes/sfc/ppu/main.cpp | 65 ++++++------ bsnes/sfc/ppu/mode7.cpp | 17 ++-- bsnes/sfc/ppu/mosaic.cpp | 26 +++++ bsnes/sfc/ppu/mosaic.hpp | 13 +++ bsnes/sfc/ppu/ppu.cpp | 2 + bsnes/sfc/ppu/ppu.hpp | 2 + bsnes/sfc/ppu/serialization.cpp | 14 ++- .../presentation/presentation.cpp | 4 +- bsnes/target-bsnes/program/game.cpp | 1 - bsnes/target-bsnes/program/program.hpp | 1 - bsnes/target-bsnes/program/video.cpp | 59 +---------- bsnes/target-bsnes/program/viewport.cpp | 25 ++--- bsnes/target-bsnes/settings/enhancements.cpp | 30 +++--- bsnes/target-bsnes/settings/settings.hpp | 2 +- bsnes/target-bsnes/settings/video.cpp | 6 +- bsnes/target-libretro/libretro.cpp | 84 ++++++++++++---- bsnes/target-libretro/program.cpp | 3 +- 42 files changed, 590 insertions(+), 330 deletions(-) create mode 100644 bsnes/emulator/hdtoolkit.hpp create mode 100644 bsnes/sfc/ppu/mosaic.cpp create mode 100644 bsnes/sfc/ppu/mosaic.hpp diff --git a/README.md b/README.md index 8251ab40..a52fee1f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,17 @@ -# bsnes-hd *beta 10.1* +# bsnes-hd *beta 10.2* - [downloads](https://github.com/DerKoun/bsnes-hd/releases) for the latest betas - [GitHub project](https://github.com/DerKoun/bsnes-hd) for source code, issues, feature requests, ... - [Reddit](https://www.reddit.com/r/emulation/search/?q=bsnes-hd&restrict_sr=1&sort=new) for announcements and discussions on *r/emulation* - [Discord](https://discord.gg/7ahAzCV) if you prefer chatting (Thanks to everyone who set it up and keeps it running) +- [Forum](https://www.retrowide.com/forums) for widescreen discussions, both ROM-hacking and technical + +1. [What is it?](#what-is-it) +2. [Help wanted](#help-wanted) +3. [Settings](#settings) +4. [Differences in setting/options from bsnes](#differences-in-settingoptions-from-bsnes) +5. [Widescreen technical](#widescreen-technical) + ## What is it? @@ -152,21 +160,24 @@ The amount of neighboring lines used to smooth color gradients that are applied The amount of neighboring lines used to smooth Window effects, like iris transitions, shadows or spell effects. "*0*" disables smoothing and is the default. (*This feature is considered a preview, as the lines at the top and bottom of effects are currently not entirely HD and it still has noticeable issues. Please let me know about any games/scenes/effects that work noticeable badly or well*) -## Differences in setting/options from bsnes +## Differences in Setting/Options from bsnes ### Settings / Output / Show Overscan Area (Show Overscan) In *bsnes* the overscan setting allows switching between cropping 8 and 0 lines form top and bottom, which are unused due to the way TVs in the time of the SNES worked. In *bsnes-hd* it switches between 12 and 8 lines, defaulting to 12 (*off*). This cuts of 4 lines on both sides that technically contain content, but should not cut important information as these lines are still in an area that wasn't safe to use (12 lines is 5%). The reason to do is that the resulting height of 216 is exactly a 5th of 1080, so you can integer scale to HD and 4K resolutions, e.g. *5x* at *16:9* is exactly *1080 HD* with every Mode 7 pixel rendered specifically. -## Widescreen technical +## Widescreen Technical ### Dimensions -The amount of pixel columns added to both sides for the various aspect ratios are: (4:3, 16), (16:10, 40), (16:9, 64), (2:1, 88), (21:9, 120). Those currently are for overscan *off* (see above) and don't change when you change that setting. +The amount of pixel columns added to both sides for the various aspect ratios are, depending on some settings: +- overscan *off*, aspect correction *off*: (4:3, 16), (16:10, 44), (16:9, 64), (2:1, 88), (21:9, 124). +- overscan *on*, aspect correction *off*: (4:3, 20), (16:10, 52), (16:9, 72), (2:1, 96), (21:9, 132). +- overscan *on*, aspect correction *on*: (4:3, 0), (16:10, 24), (16:9, 44), (2:1, 64), (21:9, 96). ### Maximum width for objects/sprites -The maximum width for widescreen areas that still can have places objects in them is *96* (exactly 2:1 AR with overscan *on*). +The maximum width for widescreen areas that still can have places objects in them is *96* (exactly 2:1 AR with overscan *on* and aspect correction *off* or 21:9 with overscan *on* and aspect correction *on*). ### Object/sprite wrap-around diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index c8e531a8..f8d6e36f 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -31,13 +31,13 @@ using namespace nall; namespace Emulator { static const string Name = "bsnes-hd beta"; - static const string Version = "10.1";//bsnes/target-bsnes/presentation/presentation.cpp:create:about:setVersion + static const string Version = "10.2";//bsnes/target-bsnes/presentation/presentation.cpp:create:about:setVersion static const string Author = "DerKoun(byuu)"; static const string License = "GPLv3"; static const string Website = "https://github.com/DerKoun/bsnes-hd"; //incremented only when serialization format changes - static const string SerializerVersion = "112"; + static const string SerializerVersion = "114.2"; namespace Constants { namespace Colorburst { diff --git a/bsnes/emulator/hdtoolkit.hpp b/bsnes/emulator/hdtoolkit.hpp new file mode 100644 index 00000000..ef3d9bf5 --- /dev/null +++ b/bsnes/emulator/hdtoolkit.hpp @@ -0,0 +1,28 @@ +namespace HdToolkit { + static constexpr auto determineWsExt(int ws, bool overscan, bool aspectCorrection) -> int { + if (ws < 200) { + return ws; + } + int w = ws / 100; + int h = ws % 100; + double val = overscan ? 224.0 : 216.0; + val *= w; + val /= h; + if (aspectCorrection) { + val *= 7; + val /= 8; + } + val -= 256; + val /= 2; + val /= 4; + if (overscan || aspectCorrection) { + val += 0.5; + } + ws = (int)val; + if (ws <= 0) { + return 0; + } + ws *= 4; + return ws; + } +} diff --git a/bsnes/gb/Core/apu.c b/bsnes/gb/Core/apu.c index 9afa5c93..17aa4528 100644 --- a/bsnes/gb/Core/apu.c +++ b/bsnes/gb/Core/apu.c @@ -329,11 +329,16 @@ static void tick_noise_envelope(GB_gameboy_t *gb) void GB_apu_div_event(GB_gameboy_t *gb) { if (!gb->apu.global_enable) return; - if (gb->apu.skip_div_event) { - gb->apu.skip_div_event = false; + if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIP) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIPPED; return; } - gb->apu.div_divider++; + if (gb->apu.skip_div_event == GB_SKIP_DIV_EVENT_SKIPPED) { + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_INACTIVE; + } + else { + gb->apu.div_divider++; + } if ((gb->apu.div_divider & 1) == 0) { for (unsigned i = GB_SQUARE_2 + 1; i--;) { @@ -534,7 +539,8 @@ void GB_apu_init(GB_gameboy_t *gb) /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on, the first DIV/APU event is skipped. */ if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) { - gb->apu.skip_div_event = true; + gb->apu.skip_div_event = GB_SKIP_DIV_EVENT_SKIP; + gb->apu.div_divider = 1; } } diff --git a/bsnes/gb/Core/apu.h b/bsnes/gb/Core/apu.h index 885e0ce3..011412e3 100644 --- a/bsnes/gb/Core/apu.h +++ b/bsnes/gb/Core/apu.h @@ -114,7 +114,10 @@ typedef struct } noise_channel; - bool skip_div_event; +#define GB_SKIP_DIV_EVENT_INACTIVE 0 +#define GB_SKIP_DIV_EVENT_SKIPPED 1 +#define GB_SKIP_DIV_EVENT_SKIP 2 + uint8_t skip_div_event; bool current_lfsr_sample; } GB_apu_t; diff --git a/bsnes/gb/Core/debugger.c b/bsnes/gb/Core/debugger.c index df480f34..8b6d6cf4 100644 --- a/bsnes/gb/Core/debugger.c +++ b/bsnes/gb/Core/debugger.c @@ -819,7 +819,7 @@ static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */ (gb->f & GB_CARRY_FLAG)? 'C' : '-', (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-', - (gb->f & GB_SUBSTRACT_FLAG)? 'N' : '-', + (gb->f & GB_SUBTRACT_FLAG)? 'N' : '-', (gb->f & GB_ZERO_FLAG)? 'Z' : '-'); GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false)); GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false)); diff --git a/bsnes/gb/Core/display.c b/bsnes/gb/Core/display.c index 5c1935c6..7f3d23e0 100644 --- a/bsnes/gb/Core/display.c +++ b/bsnes/gb/Core/display.c @@ -142,9 +142,17 @@ static void display_vblank(GB_gameboy_t *gb) } } else { - uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ? - gb->rgb_encode_callback(gb, 0, 0, 0) : - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + uint32_t color = 0; + if (GB_is_cgb(gb)) { + color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->rgb_encode_callback(gb, 0, 0, 0) : + gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + } + else { + color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? + gb->background_palettes_rgb[3] : + gb->background_palettes_rgb[4]; + } for (unsigned i = 0; i < WIDTH * LINES; i++) { gb ->screen[i] = color; } @@ -966,10 +974,16 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) GB_STAT_update(gb); if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { - if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { - display_vblank(gb); + if (GB_is_cgb(gb)) { + GB_timing_sync(gb); + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; + } + else { + if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + display_vblank(gb); + } + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } - gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; } else { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; diff --git a/bsnes/gb/Core/gb.c b/bsnes/gb/Core/gb.c index f29d400a..4a9525cb 100644 --- a/bsnes/gb/Core/gb.c +++ b/bsnes/gb/Core/gb.c @@ -573,21 +573,41 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } +const GB_palette_t GB_PALETTE_GREY = {{{0x00, 0x00, 0x00}, {0x55, 0x55, 0x55}, {0xaa, 0xaa, 0xaa}, {0xff ,0xff, 0xff}, {0xff ,0xff, 0xff}}}; +const GB_palette_t GB_PALETTE_DMG = {{{0x08, 0x18, 0x10}, {0x39, 0x61, 0x39}, {0x84, 0xa5, 0x63}, {0xc6, 0xde, 0x8c}, {0xd2 ,0xe6 ,0xa6}}}; +const GB_palette_t GB_PALETTE_MGB = {{{0x07, 0x10, 0x0e}, {0x3a, 0x4c, 0x3a}, {0x81, 0x8d, 0x66}, {0xc2, 0xce, 0x93}, {0xcf, 0xda, 0xac}}}; +const GB_palette_t GB_PALETTE_GBL = {{{0x0a, 0x1c, 0x15}, {0x35, 0x78, 0x62}, {0x56, 0xb4, 0x95}, {0x7f, 0xe2, 0xc3}, {0x91, 0xea, 0xd0}}}; -void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) +static void update_dmg_palette(GB_gameboy_t *gb) { - if (!gb->rgb_encode_callback && !GB_is_cgb(gb)) { + const GB_palette_t *palette = gb->dmg_palette ?: &GB_PALETTE_GREY; + if (gb->rgb_encode_callback && !GB_is_cgb(gb)) { gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = - callback(gb, 0xFF, 0xFF, 0xFF); + gb->rgb_encode_callback(gb, palette->colors[3].r, palette->colors[3].g, palette->colors[3].b); gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = - callback(gb, 0xAA, 0xAA, 0xAA); + gb->rgb_encode_callback(gb, palette->colors[2].r, palette->colors[2].g, palette->colors[2].b); gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = - callback(gb, 0x55, 0x55, 0x55); + gb->rgb_encode_callback(gb, palette->colors[1].r, palette->colors[1].g, palette->colors[1].b); gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = - callback(gb, 0, 0, 0); + gb->rgb_encode_callback(gb, palette->colors[0].r, palette->colors[0].g, palette->colors[0].b); + + // LCD off color + gb->background_palettes_rgb[4] = + gb->rgb_encode_callback(gb, palette->colors[4].r, palette->colors[4].g, palette->colors[4].b); } +} + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette) +{ + gb->dmg_palette = palette; + update_dmg_palette(gb); +} + +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) +{ gb->rgb_encode_callback = callback; + update_dmg_palette(gb); for (unsigned i = 0; i < 32; i++) { GB_palette_changed(gb, true, i * 2); @@ -858,6 +878,36 @@ static void reset_ram(GB_gameboy_t *gb) } } +static void request_boot_rom(GB_gameboy_t *gb) +{ + if (gb->boot_rom_load_callback) { + GB_boot_rom_t type = 0; + switch (gb->model) { + case GB_MODEL_DMG_B: + type = GB_BOOT_ROM_DMG; + break; + case GB_MODEL_SGB_NTSC: + case GB_MODEL_SGB_PAL: + case GB_MODEL_SGB_NTSC_NO_SFC: + case GB_MODEL_SGB_PAL_NO_SFC: + type = GB_BOOT_ROM_SGB; + break; + case GB_MODEL_SGB2: + case GB_MODEL_SGB2_NO_SFC: + type = GB_BOOT_ROM_SGB2; + break; + case GB_MODEL_CGB_C: + case GB_MODEL_CGB_E: + type = GB_BOOT_ROM_CGB; + break; + case GB_MODEL_AGB: + type = GB_BOOT_ROM_AGB; + break; + } + gb->boot_rom_load_callback(gb, type); + } +} + void GB_reset(GB_gameboy_t *gb) { uint32_t mbc_ram_size = gb->mbc_ram_size; @@ -882,16 +932,7 @@ void GB_reset(GB_gameboy_t *gb) gb->vram_size = 0x2000; memset(gb->vram, 0, gb->vram_size); - if (gb->rgb_encode_callback) { - gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = - gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); - gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = - gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); - gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = - gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); - gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = - gb->rgb_encode_callback(gb, 0, 0, 0); - } + update_dmg_palette(gb); } reset_ram(gb); @@ -937,6 +978,7 @@ void GB_reset(GB_gameboy_t *gb) } gb->magic = state_magic(); + request_boot_rom(gb); } void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model) @@ -1082,3 +1124,9 @@ void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callb { gb->icd_vreset_callback = callback; } + +void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback) +{ + gb->boot_rom_load_callback = callback; + request_boot_rom(gb); +} diff --git a/bsnes/gb/Core/gb.h b/bsnes/gb/Core/gb.h index a5611116..487269fc 100644 --- a/bsnes/gb/Core/gb.h +++ b/bsnes/gb/Core/gb.h @@ -50,6 +50,17 @@ #error Unable to detect endianess #endif +typedef struct { + struct { + uint8_t r,g,b; + } colors[5]; +} GB_palette_t; + +extern const GB_palette_t GB_PALETTE_GREY; +extern const GB_palette_t GB_PALETTE_DMG; +extern const GB_palette_t GB_PALETTE_MGB; +extern const GB_palette_t GB_PALETTE_GBL; + typedef union { struct { uint8_t seconds; @@ -61,7 +72,6 @@ typedef union { uint8_t data[5]; } GB_rtc_time_t; - typedef enum { // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, @@ -98,7 +108,7 @@ enum { enum { GB_CARRY_FLAG = 16, GB_HALF_CARRY_FLAG = 32, - GB_SUBSTRACT_FLAG = 64, + GB_SUBTRACT_FLAG = 64, GB_ZERO_FLAG = 128, }; @@ -219,6 +229,17 @@ typedef enum { GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE } GB_log_attributes; +typedef enum { + GB_BOOT_ROM_DMG0, + GB_BOOT_ROM_DMG, + GB_BOOT_ROM_MGB, + GB_BOOT_ROM_SGB, + GB_BOOT_ROM_SGB2, + GB_BOOT_ROM_CGB0, + GB_BOOT_ROM_CGB, + GB_BOOT_ROM_AGB, +} GB_boot_rom_t; + #ifdef GB_INTERNAL #define LCDC_PERIOD 70224 #define CPU_FREQUENCY 0x400000 @@ -249,6 +270,7 @@ typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value); typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row); typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb); typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_boot_rom_load_callback_t)(GB_gameboy_t *gb, GB_boot_rom_t type); typedef struct { bool state; @@ -513,6 +535,7 @@ struct GB_gameboy_internal_s { uint32_t *screen; uint32_t background_palettes_rgb[0x20]; uint32_t sprite_palettes_rgb[0x20]; + const GB_palette_t *dmg_palette; GB_color_correction_mode_t color_correction_mode; bool keys[4][GB_KEY_MAX]; @@ -542,6 +565,7 @@ struct GB_gameboy_internal_s { GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_vreset_callback; GB_read_memory_callback_t read_memory_callback; + GB_boot_rom_load_callback_t boot_rom_load_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -695,6 +719,10 @@ void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callb void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback); +/* Called when a new boot ROM is needed. The callback should call GB_load_boot_rom or GB_load_boot_rom_from_buffer */ +void GB_set_boot_rom_load_callback(GB_gameboy_t *gb, GB_boot_rom_load_callback_t callback); + +void GB_set_palette(GB_gameboy_t *gb, const GB_palette_t *palette); /* These APIs are used when using internal clock */ void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback); diff --git a/bsnes/gb/Core/sm83_cpu.c b/bsnes/gb/Core/sm83_cpu.c index 77248b51..0009a69c 100644 --- a/bsnes/gb/Core/sm83_cpu.c +++ b/bsnes/gb/Core/sm83_cpu.c @@ -318,7 +318,7 @@ static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) uint8_t register_id; register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] += 0x100; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F00) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -334,7 +334,7 @@ static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) register_id = ((opcode >> 4) + 1) & 0x03; gb->registers[register_id] -= 0x100; gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F00) == 0xF00) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -396,7 +396,7 @@ static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) register_id = (opcode >> 4) + 1; rr = gb->registers[register_id]; gb->registers[GB_REGISTER_HL] = hl + rr; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); /* The meaning of the Half Carry flag is really hard to track -_- */ if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { @@ -432,7 +432,7 @@ static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) value = (gb->registers[register_id] & 0xFF) + 1; gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((gb->registers[register_id] & 0x0F) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -452,7 +452,7 @@ static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((gb->registers[register_id] & 0x0F) == 0xF) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; @@ -533,7 +533,7 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG); - if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_SUBTRACT_FLAG) { if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { result = (result - 0x06) & 0xFF; } @@ -567,19 +567,19 @@ static void daa(GB_gameboy_t *gb, uint8_t opcode) static void cpl(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] ^= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG; } static void scf(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ccf(GB_gameboy_t *gb, uint8_t opcode) { gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; - gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBTRACT_FLAG); } static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) @@ -610,7 +610,7 @@ static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1; cycle_write(gb, gb->registers[GB_REGISTER_HL], value); - gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); if ((value & 0x0F) == 0) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } @@ -627,7 +627,7 @@ static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) cycle_write(gb, gb->registers[GB_REGISTER_HL], value); gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if ((value & 0x0F) == 0x0F) { gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; } @@ -763,7 +763,7 @@ static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) uint8_t value, a; value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -781,7 +781,7 @@ static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; - gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; @@ -833,7 +833,7 @@ static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) value = get_src_value(gb, opcode); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -962,7 +962,7 @@ static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) uint8_t value, a; value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; - gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } @@ -980,7 +980,7 @@ static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; - gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBTRACT_FLAG; if ((uint8_t) (a - value - carry) == 0) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; @@ -1032,7 +1032,7 @@ static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) value = cycle_read_inc_oam_bug(gb, gb->pc++); a = gb->registers[GB_REGISTER_AF] >> 8; gb->registers[GB_REGISTER_AF] &= 0xFF00; - gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_SUBTRACT_FLAG; if (a == value) { gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; } diff --git a/bsnes/sfc/coprocessor/sdd1/serialization.cpp b/bsnes/sfc/coprocessor/sdd1/serialization.cpp index be381b9b..b4b57a83 100644 --- a/bsnes/sfc/coprocessor/sdd1/serialization.cpp +++ b/bsnes/sfc/coprocessor/sdd1/serialization.cpp @@ -6,9 +6,9 @@ auto SDD1::serialize(serializer& s) -> void { s.integer(r4806); s.integer(r4807); - for(auto n : range(8)) { - s.integer(dma[n].addr); - s.integer(dma[n].size); + for(auto& channel : dma) { + s.integer(channel.addr); + s.integer(channel.size); } s.integer(dmaReady); @@ -26,6 +26,7 @@ auto SDD1::Decompressor::serialize(serializer& s) -> void { bg5.serialize(s); bg6.serialize(s); bg7.serialize(s); + pem.serialize(s); cm.serialize(s); ol.serialize(s); } diff --git a/bsnes/sfc/interface/configuration.cpp b/bsnes/sfc/interface/configuration.cpp index b5aaec70..187dcf7f 100644 --- a/bsnes/sfc/interface/configuration.cpp +++ b/bsnes/sfc/interface/configuration.cpp @@ -14,8 +14,13 @@ auto Configuration::process(Markup::Node document, bool load) -> void { bind(natural, "System/PPU2/Version", system.ppu2.version); bind(text, "System/Serialization/Method", system.serialization.method); + bind(boolean, "Video/AspectCorrection", video.aspectCorrection); + bind(boolean, "Video/Overscan", video.overscan); bind(boolean, "Video/BlurEmulation", video.blurEmulation); bind(boolean, "Video/ColorEmulation", video.colorEmulation); + bind(natural, "Video/Saturation", video.saturation); + bind(natural, "Video/Gamma", video.gamma); + bind(natural, "Video/Luminance", video.luminance); bind(boolean, "Hacks/Hotfixes", hacks.hotfixes); bind(text, "Hacks/Entropy", hacks.entropy); diff --git a/bsnes/sfc/interface/configuration.hpp b/bsnes/sfc/interface/configuration.hpp index fd5b1940..35cf42aa 100644 --- a/bsnes/sfc/interface/configuration.hpp +++ b/bsnes/sfc/interface/configuration.hpp @@ -23,8 +23,13 @@ struct Configuration { } system; struct Video { + bool aspectCorrection = false; + bool overscan = false; bool blurEmulation = true; bool colorEmulation = true; + uint saturation = 100; + uint gamma = 150; + uint luminance = 100; } video; struct Hacks { @@ -53,7 +58,7 @@ struct Configuration { uint igwin = 1; uint igwinx = 128; uint bgGrad = 4; - uint windRad = 0; + uint windRad = 0; uint wsMode = 1; uint wsBgCol = 1; uint wsMarker = 0; diff --git a/bsnes/sfc/ppu-fast/background.cpp b/bsnes/sfc/ppu-fast/background.cpp index b4a440f4..6ff91ac0 100644 --- a/bsnes/sfc/ppu-fast/background.cpp +++ b/bsnes/sfc/ppu-fast/background.cpp @@ -1,15 +1,3 @@ -//single-threaded -auto PPU::Line::cacheBackground(PPU::IO::Background& bg) -> void { - if(y == 1) { - bg.mosaicCounter = ppu.io.mosaicSize + 1; - bg.mosaicOffset = 1; - } else if(--bg.mosaicCounter == 0) { - bg.mosaicCounter = ppu.io.mosaicSize + 1; - bg.mosaicOffset += ppu.io.mosaicSize + 1; - } -} - -//parallelized auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> void { if(!self.aboveEnable && !self.belowEnable) return; if(self.tileMode == TileMode::Mode7) return renderMode7(self, source); @@ -62,10 +50,14 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi uint hmask = (width << self.tileSize << !!(self.screenSize & 1)) - 1; uint vmask = (width << self.tileSize << !!(self.screenSize & 2)) - 1; - uint y = self.mosaicEnable ? self.mosaicOffset : this->y; + uint y = this->y; + if(self.mosaicEnable) y -= io.mosaic.size - io.mosaic.counter; if(hires) { hscroll <<= 1; - if(io.interlace) y = y << 1 | field(); + if(io.interlace) { + y = y << 1 | field(); + if(self.mosaicEnable) y -= io.mosaic.size - io.mosaic.counter + field(); + } } uint mosaicCounter = 1; @@ -128,7 +120,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi for(uint tileX = 0; tileX < 8; tileX++, x++) { if(x < -ws || x >= width + ws) continue; //if(x & width) continue; //x < 0 || x >= width - if(!self.mosaicEnable || --mosaicCounter == 0) { + if(--mosaicCounter == 0) { uint color, shift = mirrorX ? tileX : 7 - tileX; /*if(self.tileMode >= TileMode::BPP2)*/ { color = data >> shift + 0 & 1; @@ -145,7 +137,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi color += data >> shift + 49 & 128; } - mosaicCounter = 1 + io.mosaicSize; + mosaicCounter = self.mosaicEnable ? io.mosaic.size : 1; mosaicPalette = color; mosaicPriority = tilePriority; if(directColorMode) { @@ -160,8 +152,14 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> voi uint32 mctc = luma[mosaicColor]; if(!hires) { - if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotAbove(x, source, mosaicPriority, mctc); - if(self.belowEnable && !windowBelow[ppufast.winXad(x, true)]) plotBelow(x, source, mosaicPriority, mctc); + if(ppufast.hires() && ppufast.hd()) { + // In hires mode non-hires backgrounds seem to be rendered on every second pixel (see water in Kirby) + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(above, x, source, mosaicPriority, mctc, true, true); + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(below, x, source, mosaicPriority, mctc, true, true); + } else { + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotAbove(x, source, mosaicPriority, mctc); + if(self.belowEnable && !windowBelow[ppufast.winXad(x, true)]) plotBelow(x, source, mosaicPriority, mctc); + } } else { int X = x / 2; if(!ppufast.hd()) { diff --git a/bsnes/sfc/ppu-fast/io.cpp b/bsnes/sfc/ppu-fast/io.cpp index 9db01ea0..4e445a03 100644 --- a/bsnes/sfc/ppu-fast/io.cpp +++ b/bsnes/sfc/ppu-fast/io.cpp @@ -248,11 +248,16 @@ auto PPU::writeIO(uint address, uint8 data) -> void { } case 0x2106: { //MOSAIC + bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable; io.bg1.mosaicEnable = data >> 0 & 1; io.bg2.mosaicEnable = data >> 1 & 1; io.bg3.mosaicEnable = data >> 2 & 1; io.bg4.mosaicEnable = data >> 3 & 1; - io.mosaicSize = data >> 4 & 15; + io.mosaic.size = (data >> 4 & 15) + 1; + if(!mosaicEnable && (data >> 0 & 15)) { + //mosaic vcounter is reloaded when mosaic becomes enabled + io.mosaic.counter = io.mosaic.size + 1; + } return; } diff --git a/bsnes/sfc/ppu-fast/line.cpp b/bsnes/sfc/ppu-fast/line.cpp index 0865f840..e3549323 100644 --- a/bsnes/sfc/ppu-fast/line.cpp +++ b/bsnes/sfc/ppu-fast/line.cpp @@ -1,7 +1,75 @@ +#include uint PPU::Line::start = 0; uint PPU::Line::count = 0; auto PPU::Line::flush() -> void { + + ppu.wsExt = HdToolkit::determineWsExt(ppu.widescreenRaw(), + configuration.video.overscan, configuration.video.aspectCorrection); + + if (ppu.luminance != configuration.video.luminance || + ppu.saturation != configuration.video.saturation || + ppu.gamma != configuration.video.gamma) { + uint luminance = configuration.video.luminance; + uint saturation = configuration.video.saturation; + uint gamma = configuration.video.gamma; + ppu.luminance = luminance; + ppu.saturation = saturation; + ppu.gamma = gamma; + for(uint l : range(16)) { + ppu.lightTable[l] = new uint32_t[32768]; + for(uint r : range(32)) { + for(uint g : range(32)) { + for(uint b : range(32)) { + double dr = r << 3; + double dg = g << 3; + double db = b << 3; + + if(saturation != 100) { + double satVal = saturation / 100.0; + double grayInv = (dr + dg + db) / 3 * max(0.0, 1.0 - saturation / 100.0); + dr = dr * satVal + grayInv; + dg = dg * satVal + grayInv; + db = db * satVal + grayInv; + } + + if(gamma != 100) { + double reciprocal = 1.0 / 255.0; + double gamVal = gamma / 100.0; + dr = 255.0 * pow(dr * reciprocal, gamVal); + dg = 255.0 * pow(dg * reciprocal, gamVal); + db = 255.0 * pow(db * reciprocal, gamVal); + } + + if(luminance != 100) { + double lumVal = luminance / 100.0; + dr *= lumVal; + dg *= lumVal; + db *= lumVal; + } + + double lVal = l / 15.0; + dr *= lVal; + dg *= lVal; + db *= lVal; + + int ar = dr + 0.5; + int ag = dg + 0.5; + int ab = db + 0.5; + if (ar > 255) ar = 255; + if (ag > 255) ag = 255; + if (ab > 255) ab = 255; + if (ar < 0) ar = 0; + if (ag < 0) ag = 0; + if (ab < 0) ab = 0; + + ppu.lightTable[l][r << 10 | g << 5 | b << 0] = ab << 16 | ag << 8 | ar << 0; + } + } + } + } + } + if(Line::count) { if(ppu.hdScale() > 1) cacheMode7HD(); #pragma omp parallel for if(Line::count >= 8) @@ -26,11 +94,6 @@ auto PPU::Line::flush() -> void { } auto PPU::Line::cache() -> void { - cacheBackground(ppu.io.bg1); - cacheBackground(ppu.io.bg2); - cacheBackground(ppu.io.bg3); - cacheBackground(ppu.io.bg4); - uint y = ppu.vcounter(); if(ppu.io.displayDisable || y >= ppu.vdisp()) { io.displayDisable = true; @@ -151,6 +214,8 @@ auto PPU::Line::render(bool fieldID) -> void { if(io.extbg == 1) renderBackground(io.bg2, Source::BG2); //TODO: move to own method + int THRESHOLD = 4; + y = this->y; uint windRad = ppufast.windRad(); for (int offset = 0; offset < scale; offset++) { uint oneLeft = io.window.oneLeft; @@ -188,6 +253,22 @@ auto PPU::Line::render(bool fieldID) -> void { io.col.window.aboveMask != uL.io.col.window.aboveMask || io.col.window.belowMask != dL.io.col.window.belowMask || io.col.window.belowMask != uL.io.col.window.belowMask + || dL.io.window.oneLeft - io.window.oneLeft > THRESHOLD + || io.window.oneLeft - dL.io.window.oneLeft > THRESHOLD + || uL.io.window.oneLeft - io.window.oneLeft > THRESHOLD + || io.window.oneLeft - uL.io.window.oneLeft > THRESHOLD + || dL.io.window.oneRight - io.window.oneRight > THRESHOLD + || io.window.oneRight - dL.io.window.oneRight > THRESHOLD + || uL.io.window.oneRight - io.window.oneRight > THRESHOLD + || io.window.oneRight - uL.io.window.oneRight > THRESHOLD + || dL.io.window.twoLeft - io.window.twoLeft > THRESHOLD + || io.window.twoLeft - dL.io.window.twoLeft > THRESHOLD + || uL.io.window.twoLeft - io.window.twoLeft > THRESHOLD + || io.window.twoLeft - uL.io.window.twoLeft > THRESHOLD + || dL.io.window.twoRight - io.window.twoRight > THRESHOLD + || io.window.twoRight - dL.io.window.twoRight > THRESHOLD + || uL.io.window.twoRight - io.window.twoRight > THRESHOLD + || io.window.twoRight - uL.io.window.twoRight > THRESHOLD ) break; oneLeft += dL.io.window.oneLeft + uL.io.window.oneLeft; @@ -254,8 +335,8 @@ auto PPU::Line::render(bool fieldID) -> void { auto PPU::Line::pixel(uint x, Pixel above, Pixel below, uint wsm, uint wsma, uint32 bgFixedColor) const -> uint32 { uint32 r = 0; - if(!windowAbove[x]) above.color = 0x0000; - if(!windowBelow[x]) r = above.color; + if(!windowAbove[ppufast.winXadHd(x, false)]) above.color = 0x0000; + if(!windowBelow[ppufast.winXadHd(x, true)]) r = above.color; else if(!io.col.enable[above.source]) r = above.color; else if(!io.col.blendMode) r = blend(above.color, bgFixedColor, io.col.halve && windowAbove[x]); else r = blend(above.color, below.color, io.col.halve && windowAbove[x] && below.source != Source::COL); @@ -315,11 +396,11 @@ auto PPU::Line::plotBelow(int x, uint8 source, uint8 priority, uint32 color) -> auto PPU::Line::plotHD(Pixel* pixel, int x, uint8 source, uint8 priority, uint32 color, bool hires, bool subpixel) -> void { int scale = ppu.hdScale(); int wss = ppu.widescreen() * scale; - int xss = hires && subpixel ? scale / 2 : 0; + int xss = hires && subpixel ? (scale / 2 + ((scale & 1 == 1) && (x & 1 == 1))) : 0; int ys = ppu.interlace() && ppu.field() ? scale / 2 : 0; if(priority > pixel[x * scale + xss + ys * 256 * scale + wss].priority) { Pixel p = {source, priority, color}; - int xsm = hires && !subpixel ? scale / 2 : scale; + int xsm = hires && !subpixel ? (scale / 2 + ((scale & 1 == 1) && (x & 1 == 1))) : scale; int ysm = ppu.interlace() && !ppu.field() ? scale / 2 : scale; for(int xs = xss; xs < xsm; xs++) { pixel[x * scale + xs + ys * 256 * scale + wss] = p; diff --git a/bsnes/sfc/ppu-fast/mode7.cpp b/bsnes/sfc/ppu-fast/mode7.cpp index 892b1611..e59ba379 100644 --- a/bsnes/sfc/ppu-fast/mode7.cpp +++ b/bsnes/sfc/ppu-fast/mode7.cpp @@ -1,11 +1,12 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void { //HD mode 7 support if(ppufast.hdScale() > 0 && (ppufast.hdMosaic() != 0 - || !self.mosaicEnable || !io.mosaicSize)) { + || !self.mosaicEnable || io.mosaic.size == 1)) { return renderMode7HD(self, source); } - int Y = self.mosaicEnable ? self.mosaicOffset : this->y; + int Y = this->y; + if(self.mosaicEnable) Y -= io.mosaic.size - io.mosaic.counter; int y = !io.mode7.vflip ? Y : 255 - Y; int a = (int16)io.mode7.a; @@ -52,8 +53,8 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void { palette &= 0x7f; } - if(!self.mosaicEnable || --mosaicCounter == 0) { - mosaicCounter = 1 + io.mosaicSize; + if(--mosaicCounter == 0) { + mosaicCounter = self.mosaicEnable ? io.mosaic.size : 1; mosaicPalette = palette; mosaicPriority = priority; if(io.col.directColor && source == Source::BG1) { diff --git a/bsnes/sfc/ppu-fast/mode7hd.cpp b/bsnes/sfc/ppu-fast/mode7hd.cpp index a9c640b7..129714bc 100644 --- a/bsnes/sfc/ppu-fast/mode7hd.cpp +++ b/bsnes/sfc/ppu-fast/mode7hd.cpp @@ -97,7 +97,7 @@ auto PPU::Line::cacheMode7HD() -> void { auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint8 source) -> void { const bool extbg = source == Source::BG2; - bool mosSing = self.mosaicEnable && io.mosaicSize && ppu.hdMosaic() == 1; + bool mosSing = self.mosaicEnable && io.mosaic.size != 1 && ppu.hdMosaic() == 1; const uint sampScale = mosSing ? 1 : ppu.hdSupersample(); const uint scale = mosSing ? 1 : ppu.hdScale() * sampScale; diff --git a/bsnes/sfc/ppu-fast/object.cpp b/bsnes/sfc/ppu-fast/object.cpp index b4b4cee1..7598f07a 100644 --- a/bsnes/sfc/ppu-fast/object.cpp +++ b/bsnes/sfc/ppu-fast/object.cpp @@ -135,8 +135,16 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void { uint32 mctc = luma[cgram[palette[x]]]; - if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotAbove(xc, source, priority[x], mctc); - if(self.belowEnable && !windowBelow[ppufast.winXad(x, true)]) plotBelow(xc, source, priority[x], mctc); + if(ppufast.hires() && ppufast.hd()) { + // Match non-hires backgrounds in hires mode (see background) + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(above, xc, source, priority[x], mctc, true, false); + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(above, xc, source, priority[x], mctc, true, true); + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(below, xc, source, priority[x], mctc, true, false); + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotHD(below, xc, source, priority[x], mctc, true, true); + } else { + if(self.aboveEnable && !windowAbove[ppufast.winXad(x, false)]) plotAbove(xc, source, priority[x], mctc); + if(self.belowEnable && !windowBelow[ppufast.winXad(x, true)]) plotBelow(xc, source, priority[x], mctc); + } } } diff --git a/bsnes/sfc/ppu-fast/ppu.cpp b/bsnes/sfc/ppu-fast/ppu.cpp index 21cfc64a..475096e2 100644 --- a/bsnes/sfc/ppu-fast/ppu.cpp +++ b/bsnes/sfc/ppu-fast/ppu.cpp @@ -28,7 +28,8 @@ auto PPU::hdScale() const -> uint { return configuration.hacks.ppu.mode7.scale; auto PPU::hdPerspective() const -> uint { return configuration.hacks.ppu.mode7.perspective; } auto PPU::hdSupersample() const -> uint { return configuration.hacks.ppu.mode7.supersample; } auto PPU::hdMosaic() const -> uint { return configuration.hacks.ppu.mode7.mosaic; } -auto PPU::widescreen() const -> uint { return !hd() || configuration.hacks.ppu.mode7.wsMode == 0 ? 0 : configuration.hacks.ppu.mode7.widescreen; } +auto PPU::widescreen() const -> uint { return wsExt; } +auto PPU::widescreenRaw() const -> uint { return !hd() || configuration.hacks.ppu.mode7.wsMode == 0 ? 0 : configuration.hacks.ppu.mode7.widescreen; } auto PPU::wsbg(uint bg) const -> uint { if (bg == Source::BG1) return configuration.hacks.ppu.mode7.wsbg1; if (bg == Source::BG2) return configuration.hacks.ppu.mode7.wsbg2; @@ -41,6 +42,11 @@ auto PPU::winXad(uint x, bool bel) const -> uint { || configuration.hacks.ppu.mode7.igwin >= 2 && ((bel ? io.col.window.belowMask : io.col.window.aboveMask) == 0) || configuration.hacks.ppu.mode7.igwin >= 1 && ((bel ? io.col.window.belowMask : io.col.window.aboveMask) == 2))) ? configuration.hacks.ppu.mode7.igwinx : (x < 0 ? 0 : (x > 255 ? 255 : x)); } +auto PPU::winXadHd(uint x, bool bel) const -> uint { + return (configuration.hacks.ppu.mode7.igwin != 0 && (configuration.hacks.ppu.mode7.igwin >= 3 + || configuration.hacks.ppu.mode7.igwin >= 2 && ((bel ? io.col.window.belowMask : io.col.window.aboveMask) == 0) + || configuration.hacks.ppu.mode7.igwin >= 1 && ((bel ? io.col.window.belowMask : io.col.window.aboveMask) == 2))) + ? configuration.hacks.ppu.mode7.igwinx * PPU::hdScale() : x; } auto PPU::bgGrad() const -> uint { return !hd() ? 0 : configuration.hacks.ppu.mode7.bgGrad; } auto PPU::windRad() const -> uint { return !hd() ? 0 : configuration.hacks.ppu.mode7.windRad; } auto PPU::wsOverrideCandidate() const -> bool { return configuration.hacks.ppu.mode7.wsMode == 1; } @@ -56,22 +62,6 @@ auto PPU::noVRAMBlocking() const -> bool { return configuration.hacks.ppu.noVRAM PPU::PPU() { output = new uint32_t[256 * 61440](); - - for(uint l : range(16)) { - lightTable[l] = new uint32_t[32768]; - for(uint r : range(32)) { - for(uint g : range(32)) { - for(uint b : range(32)) { - double luma = (double)l * 8.0 / 15.0; - uint ar = (luma * r + 0.5); - uint ag = (luma * g + 0.5); - uint ab = (luma * b + 0.5); - lightTable[l][r << 10 | g << 5 | b << 0] = ab << 16 | ag << 8 | ar << 0; - } - } - } - } - for(uint y : range(240)) { lines[y].y = y; } @@ -106,6 +96,13 @@ auto PPU::main() -> void { uint y = vcounter(); if(y >= 1 && y <= 239) { step(renderCycle()); + bool mosaicEnable = io.bg1.mosaicEnable || io.bg2.mosaicEnable || io.bg3.mosaicEnable || io.bg4.mosaicEnable; + if(y == 1) { + io.mosaic.counter = mosaicEnable ? io.mosaic.size + 1 : 0; + } + if(io.mosaic.counter && !--io.mosaic.counter) { + io.mosaic.counter = mosaicEnable ? io.mosaic.size + 0 : 0; + } lines[y].cache(); } } diff --git a/bsnes/sfc/ppu-fast/ppu.hpp b/bsnes/sfc/ppu-fast/ppu.hpp index b72cde7e..9a6e245f 100644 --- a/bsnes/sfc/ppu-fast/ppu.hpp +++ b/bsnes/sfc/ppu-fast/ppu.hpp @@ -19,9 +19,11 @@ struct PPU : PPUcounter { alwaysinline auto hdSupersample() const -> uint; alwaysinline auto hdMosaic() const -> uint; alwaysinline auto widescreen() const -> uint; + alwaysinline auto widescreenRaw() const -> uint; alwaysinline auto wsbg(uint bg) const -> uint; alwaysinline auto wsobj() const -> uint; alwaysinline auto winXad(uint x, bool bel) const -> uint; + alwaysinline auto winXadHd(uint x, bool bel) const -> uint; alwaysinline auto bgGrad() const -> uint; alwaysinline auto windRad() const -> uint; alwaysinline auto wsOverrideCandidate() const -> bool; @@ -96,7 +98,6 @@ struct PPU : PPUcounter { bool oamPriority = 0; bool bgPriority = 0; uint8 bgMode = 0; - uint8 mosaicSize = 0; bool vramIncrementMode = 0; uint8 vramMapping = 0; uint8 vramIncrementSize = 0; @@ -110,6 +111,14 @@ struct PPU : PPUcounter { bool pseudoHires = 0; bool extbg = 0; + struct Mosaic { + //serialization.cpp + auto serialize(serializer&) -> void; + + uint8 size = 1; + uint8 counter = 0; + } mosaic; + struct Mode7 { //serialization.cpp auto serialize(serializer&) -> void; @@ -172,8 +181,6 @@ struct PPU : PPUcounter { bool aboveEnable = 0; bool belowEnable = 0; bool mosaicEnable = 0; - uint16 mosaicCounter = 0; - uint16 mosaicOffset = 0; uint16 tiledataAddress = 0; uint16 screenAddress = 0; uint8 screenSize = 0; @@ -286,6 +293,11 @@ struct PPU : PPUcounter { //[unserialized] uint32* output = {}; uint32* lightTable[16] = {}; + uint luminance = 222; + uint saturation = 222; + uint gamma = 222; + uint wsExt = 0; + uint ItemLimit = 0; uint TileLimit = 0; @@ -307,7 +319,6 @@ struct PPU : PPUcounter { alwaysinline auto avgBgC(uint dist, uint offset) const -> uint32; //background.cpp - auto cacheBackground(PPU::IO::Background&) -> void; auto renderBackground(PPU::IO::Background&, uint8 source) -> void; alwaysinline auto getTile(PPU::IO::Background&, uint hoffset, uint voffset) -> uint; diff --git a/bsnes/sfc/ppu-fast/serialization.cpp b/bsnes/sfc/ppu-fast/serialization.cpp index 7dbdb6b0..ff6b4171 100644 --- a/bsnes/sfc/ppu-fast/serialization.cpp +++ b/bsnes/sfc/ppu-fast/serialization.cpp @@ -44,7 +44,6 @@ auto PPU::IO::serialize(serializer& s) -> void { s.integer(oamPriority); s.integer(bgPriority); s.integer(bgMode); - s.integer(mosaicSize); s.integer(vramIncrementMode); s.integer(vramMapping); s.integer(vramIncrementSize); @@ -58,6 +57,7 @@ auto PPU::IO::serialize(serializer& s) -> void { s.integer(pseudoHires); s.integer(extbg); + mosaic.serialize(s); mode7.serialize(s); window.serialize(s); bg1.serialize(s); @@ -68,6 +68,11 @@ auto PPU::IO::serialize(serializer& s) -> void { col.serialize(s); } +auto PPU::IO::Mosaic::serialize(serializer& s) -> void { + s.integer(size); + s.integer(counter); +} + auto PPU::IO::Mode7::serialize(serializer& s) -> void { s.integer(hflip); s.integer(vflip); @@ -114,8 +119,6 @@ auto PPU::IO::Background::serialize(serializer& s) -> void { s.integer(aboveEnable); s.integer(belowEnable); s.integer(mosaicEnable); - s.integer(mosaicCounter); - s.integer(mosaicOffset); s.integer(tiledataAddress); s.integer(screenAddress); s.integer(screenSize); diff --git a/bsnes/sfc/ppu/background.cpp b/bsnes/sfc/ppu/background.cpp index a03cc28f..cfe3d22f 100644 --- a/bsnes/sfc/ppu/background.cpp +++ b/bsnes/sfc/ppu/background.cpp @@ -1,5 +1,4 @@ #include "mode7.cpp" -uint4 PPU::Background::Mosaic::size; auto PPU::Background::hires() const -> bool { return ppu.io.bgMode == 5 || ppu.io.bgMode == 6; @@ -11,54 +10,41 @@ auto PPU::Background::frame() -> void { //H = 0 auto PPU::Background::scanline() -> void { - if(ppu.vcounter() == 1) { - mosaic.vcounter = mosaic.size + 1; - mosaic.voffset = 1; - latch.hoffset = io.hoffset; - latch.voffset = io.voffset; - } else if(--mosaic.vcounter == 0) { - mosaic.vcounter = mosaic.size + 1; - mosaic.voffset += mosaic.size + 1; - latch.hoffset = io.hoffset; - latch.voffset = io.voffset; - } - - mosaic.hcounter = mosaic.size + 1; + mosaic.hcounter = ppu.mosaic.size; mosaic.hoffset = 0; - if(io.mode == Mode::Mode7) return beginMode7(); - if(mosaic.size == 0) { - latch.hoffset = io.hoffset; - latch.voffset = io.voffset; - } - - nameTableIndex = 0; - characterIndex = 0; renderingIndex = 0; opt.hoffset = 0; opt.voffset = 0; + + pixelCounter = io.hoffset & 7; } //H = 56 auto PPU::Background::begin() -> void { //remove partial tile columns that have been scrolled offscreen - pixelCounter = io.hoffset & 7; for(auto& data : tiles[0].data) data >>= pixelCounter << 1; } auto PPU::Background::fetchNameTable() -> void { if(ppu.vcounter() == 0) return; + uint nameTableIndex = ppu.hcounter() >> 5 << hires(); int x = (ppu.hcounter() & ~31) >> 2; + uint hpixel = x << hires(); - uint vpixel = mosaic.enable ? (uint)mosaic.voffset : ppu.vcounter(); + uint vpixel = ppu.vcounter(); + uint hscroll = io.hoffset; + uint vscroll = io.voffset; - uint hscroll = mosaic.enable ? latch.hoffset : io.hoffset; - uint vscroll = mosaic.enable ? latch.voffset : io.voffset; + if(mosaic.enable) vpixel -= ppu.mosaic.voffset(); if(hires()) { hscroll <<= 1; - if(ppu.io.interlace) vpixel = vpixel << 1 | (ppu.field() && !mosaic.enable); + if(ppu.io.interlace) { + vpixel = vpixel << 1 | ppu.field(); + if(mosaic.enable) vpixel -= ppu.mosaic.voffset() + ppu.field(); + } } bool repeated = false; @@ -141,6 +127,7 @@ auto PPU::Background::fetchNameTable() -> void { auto PPU::Background::fetchOffset(uint y) -> void { if(ppu.vcounter() == 0) return; + uint characterIndex = ppu.hcounter() >> 5 << hires(); uint x = characterIndex << 3; uint hoffset = x + (io.hoffset & ~7); @@ -162,13 +149,13 @@ auto PPU::Background::fetchOffset(uint y) -> void { uint16 address = io.screenAddress + offset; if(y == 0) opt.hoffset = ppu.vram[address]; if(y == 8) opt.voffset = ppu.vram[address]; - - if(y == 0) characterIndex++; } -auto PPU::Background::fetchCharacter(uint index) -> void { +auto PPU::Background::fetchCharacter(uint index, bool half) -> void { if(ppu.vcounter() == 0) return; + uint characterIndex = (ppu.hcounter() >> 5 << hires()) + half; + auto& tile = tiles[characterIndex]; uint16 data = ppu.vram[tile.address + (index << 3)]; @@ -184,8 +171,6 @@ auto PPU::Background::fetchCharacter(uint index) -> void { ((uint8(data >> 0) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 49) & 0x5555 | ((uint8(data >> 8) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 48) & 0xaaaa ); - - if(index == 0) characterIndex++; } auto PPU::Background::run(bool screen) -> void { @@ -214,10 +199,10 @@ auto PPU::Background::run(bool screen) -> void { uint x = ppu.hcounter() - 56 >> 2; if(x == 0) { - mosaic.hcounter = mosaic.size + 1; + mosaic.hcounter = ppu.mosaic.size; mosaic.pixel = pixel; } else if((!hires() || screen == Screen::Below) && --mosaic.hcounter == 0) { - mosaic.hcounter = mosaic.size + 1; + mosaic.hcounter = ppu.mosaic.size; mosaic.pixel = pixel; } else if(mosaic.enable) { pixel = mosaic.pixel; @@ -240,12 +225,9 @@ auto PPU::Background::power() -> void { io.hoffset = random(); io.voffset = random(); - latch = {}; - output.above = {}; output.below = {}; mosaic = {}; - mosaic.size = random(); mosaic.enable = random(); } diff --git a/bsnes/sfc/ppu/background.hpp b/bsnes/sfc/ppu/background.hpp index d952277f..5df47dc4 100644 --- a/bsnes/sfc/ppu/background.hpp +++ b/bsnes/sfc/ppu/background.hpp @@ -9,13 +9,12 @@ struct Background { auto begin() -> void; auto fetchNameTable() -> void; auto fetchOffset(uint y) -> void; - auto fetchCharacter(uint index) -> void; + auto fetchCharacter(uint index, bool half = 0) -> void; auto run(bool screen) -> void; auto power() -> void; //mode7.cpp alwaysinline auto clip(int n) -> int; - auto beginMode7() -> void; auto runMode7() -> void; auto serialize(serializer&) -> void; @@ -44,11 +43,6 @@ struct Background { uint16 voffset; } io; - struct Latch { - uint16 hoffset; - uint16 voffset; - } latch; - struct Pixel { uint8 priority; //0 = none (transparent) uint8 palette; @@ -61,15 +55,9 @@ struct Background { } output; struct Mosaic { - static uint4 size; uint1 enable; - - uint16 vcounter; uint16 hcounter; - - uint16 voffset; uint16 hoffset; - Pixel pixel; } mosaic; @@ -90,8 +78,6 @@ struct Background { uint16 data[4]; } tiles[66]; - uint7 nameTableIndex; - uint7 characterIndex; uint7 renderingIndex; uint3 pixelCounter; diff --git a/bsnes/sfc/ppu/io.cpp b/bsnes/sfc/ppu/io.cpp index e809c785..745b3331 100644 --- a/bsnes/sfc/ppu/io.cpp +++ b/bsnes/sfc/ppu/io.cpp @@ -262,11 +262,16 @@ auto PPU::writeIO(uint addr, uint8 data) -> void { //MOSAIC case 0x2106: { + bool mosaicEnable = mosaic.enable(); bg1.mosaic.enable = data >> 0 & 1; bg2.mosaic.enable = data >> 1 & 1; bg3.mosaic.enable = data >> 2 & 1; bg4.mosaic.enable = data >> 3 & 1; - Background::Mosaic::size = data >> 4 & 15; + mosaic.size = (data >> 4 & 15) + 1; + if(!mosaicEnable && mosaic.enable()) { + //mosaic vcounter is reloaded when mosaic becomes enabled + mosaic.vcounter = mosaic.size + 1; + } return; } diff --git a/bsnes/sfc/ppu/main.cpp b/bsnes/sfc/ppu/main.cpp index d2330d7c..68f994ca 100644 --- a/bsnes/sfc/ppu/main.cpp +++ b/bsnes/sfc/ppu/main.cpp @@ -17,6 +17,7 @@ auto PPU::main() -> void { obj.frame(); } + mosaic.scanline(); bg1.scanline(); bg2.scanline(); bg3.scanline(); @@ -94,60 +95,60 @@ auto PPU::cycleBackgroundFetch() -> void { if constexpr(Cycle == 1) bg2.fetchNameTable(); if constexpr(Cycle == 2) bg1.fetchNameTable(); if constexpr(Cycle == 3) bg3.fetchCharacter(0); - if constexpr(Cycle == 4) bg2.fetchCharacter(1); - if constexpr(Cycle == 5) bg2.fetchCharacter(0); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 4) bg2.fetchCharacter(0); + if constexpr(Cycle == 5) bg2.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(0); + if constexpr(Cycle == 7) bg1.fetchCharacter(1); break; case 2: if constexpr(Cycle == 0) bg2.fetchNameTable(); if constexpr(Cycle == 1) bg1.fetchNameTable(); - if constexpr(Cycle == 2) bg3.fetchOffset(8); - if constexpr(Cycle == 3) bg3.fetchOffset(0); - if constexpr(Cycle == 4) bg2.fetchCharacter(1); - if constexpr(Cycle == 5) bg2.fetchCharacter(0); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 2) bg3.fetchOffset(0); + if constexpr(Cycle == 3) bg3.fetchOffset(8); + if constexpr(Cycle == 4) bg2.fetchCharacter(0); + if constexpr(Cycle == 5) bg2.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(0); + if constexpr(Cycle == 7) bg1.fetchCharacter(1); break; case 3: if constexpr(Cycle == 0) bg2.fetchNameTable(); if constexpr(Cycle == 1) bg1.fetchNameTable(); - if constexpr(Cycle == 2) bg2.fetchCharacter(1); - if constexpr(Cycle == 3) bg2.fetchCharacter(0); - if constexpr(Cycle == 4) bg1.fetchCharacter(3); - if constexpr(Cycle == 5) bg1.fetchCharacter(2); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 2) bg2.fetchCharacter(0); + if constexpr(Cycle == 3) bg2.fetchCharacter(1); + if constexpr(Cycle == 4) bg1.fetchCharacter(0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(2); + if constexpr(Cycle == 7) bg1.fetchCharacter(3); break; case 4: if constexpr(Cycle == 0) bg2.fetchNameTable(); if constexpr(Cycle == 1) bg1.fetchNameTable(); if constexpr(Cycle == 2) bg3.fetchOffset(0); if constexpr(Cycle == 3) bg2.fetchCharacter(0); - if constexpr(Cycle == 4) bg1.fetchCharacter(3); - if constexpr(Cycle == 5) bg1.fetchCharacter(2); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 4) bg1.fetchCharacter(0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1); + if constexpr(Cycle == 6) bg1.fetchCharacter(2); + if constexpr(Cycle == 7) bg1.fetchCharacter(3); break; case 5: if constexpr(Cycle == 0) bg2.fetchNameTable(); if constexpr(Cycle == 1) bg1.fetchNameTable(); - if constexpr(Cycle == 2) bg2.fetchCharacter(0); - if constexpr(Cycle == 3) bg2.fetchCharacter(0); - if constexpr(Cycle == 4) bg1.fetchCharacter(1); - if constexpr(Cycle == 5) bg1.fetchCharacter(0); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 2) bg2.fetchCharacter(0, 0); + if constexpr(Cycle == 3) bg2.fetchCharacter(0, 1); + if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0); + if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1); + if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1); break; case 6: if constexpr(Cycle == 0) bg2.fetchNameTable(); if constexpr(Cycle == 1) bg1.fetchNameTable(); - if constexpr(Cycle == 2) bg3.fetchOffset(8); - if constexpr(Cycle == 3) bg3.fetchOffset(0); - if constexpr(Cycle == 4) bg1.fetchCharacter(1); - if constexpr(Cycle == 5) bg1.fetchCharacter(0); - if constexpr(Cycle == 6) bg1.fetchCharacter(1); - if constexpr(Cycle == 7) bg1.fetchCharacter(0); + if constexpr(Cycle == 2) bg3.fetchOffset(0); + if constexpr(Cycle == 3) bg3.fetchOffset(8); + if constexpr(Cycle == 4) bg1.fetchCharacter(0, 0); + if constexpr(Cycle == 5) bg1.fetchCharacter(1, 0); + if constexpr(Cycle == 6) bg1.fetchCharacter(0, 1); + if constexpr(Cycle == 7) bg1.fetchCharacter(1, 1); break; case 7: //handled separately by mode7.cpp diff --git a/bsnes/sfc/ppu/mode7.cpp b/bsnes/sfc/ppu/mode7.cpp index 3af38b9b..4b4413b9 100644 --- a/bsnes/sfc/ppu/mode7.cpp +++ b/bsnes/sfc/ppu/mode7.cpp @@ -3,12 +3,6 @@ auto PPU::Background::clip(int n) -> int { return n & 0x2000 ? (n | ~1023) : (n & 1023); } -//H = 0 -auto PPU::Background::beginMode7() -> void { - latch.hoffset = ppu.io.hoffsetMode7; - latch.voffset = ppu.io.voffsetMode7; -} - auto PPU::Background::runMode7() -> void { int a = (int16)ppu.io.m7a; int b = (int16)ppu.io.m7b; @@ -17,17 +11,18 @@ auto PPU::Background::runMode7() -> void { int hcenter = (int13)ppu.io.m7x; int vcenter = (int13)ppu.io.m7y; - int hoffset = (int13)latch.hoffset; - int voffset = (int13)latch.voffset; + int hoffset = (int13)ppu.io.hoffsetMode7; + int voffset = (int13)ppu.io.voffsetMode7; uint x = mosaic.hoffset; - uint y = !mosaic.enable ? ppu.vcounter() : ppu.bg1.mosaic.voffset; //BG2 vertical mosaic uses BG1 mosaic size + uint y = ppu.vcounter(); + if(ppu.bg1.mosaic.enable) y -= ppu.mosaic.voffset(); //BG2 vertical mosaic uses BG1 mosaic enable if(!mosaic.enable) { mosaic.hoffset += 1; } else if(--mosaic.hcounter == 0) { - mosaic.hcounter = mosaic.size + 1; - mosaic.hoffset += mosaic.size + 1; + mosaic.hcounter = ppu.mosaic.size; + mosaic.hoffset += ppu.mosaic.size; } if(ppu.io.hflipMode7) x = 255 - x; diff --git a/bsnes/sfc/ppu/mosaic.cpp b/bsnes/sfc/ppu/mosaic.cpp new file mode 100644 index 00000000..79fe4d51 --- /dev/null +++ b/bsnes/sfc/ppu/mosaic.cpp @@ -0,0 +1,26 @@ +auto PPU::Mosaic::enable() const -> bool { + if(ppu.bg1.mosaic.enable) return true; + if(ppu.bg2.mosaic.enable) return true; + if(ppu.bg3.mosaic.enable) return true; + if(ppu.bg4.mosaic.enable) return true; + return false; +} + +auto PPU::Mosaic::voffset() const -> uint { + return size - vcounter; +} + +//H = 0 +auto PPU::Mosaic::scanline() -> void { + if(ppu.vcounter() == 1) { + vcounter = enable() ? size + 1 : 0; + } + if(vcounter && !--vcounter) { + vcounter = enable() ? size + 0 : 0; + } +} + +auto PPU::Mosaic::power() -> void { + size = (random() & 15) + 1; + vcounter = 0; +} diff --git a/bsnes/sfc/ppu/mosaic.hpp b/bsnes/sfc/ppu/mosaic.hpp new file mode 100644 index 00000000..261739e6 --- /dev/null +++ b/bsnes/sfc/ppu/mosaic.hpp @@ -0,0 +1,13 @@ +struct Mosaic { + //mosaic.cpp + alwaysinline auto enable() const -> bool; + alwaysinline auto voffset() const -> uint; + auto scanline() -> void; + auto power() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + uint5 size; + uint5 vcounter; +}; diff --git a/bsnes/sfc/ppu/ppu.cpp b/bsnes/sfc/ppu/ppu.cpp index ea740688..163974dc 100644 --- a/bsnes/sfc/ppu/ppu.cpp +++ b/bsnes/sfc/ppu/ppu.cpp @@ -5,6 +5,7 @@ namespace SuperFamicom { PPU ppu; #include "main.cpp" #include "io.cpp" +#include "mosaic.cpp" #include "background.cpp" #include "object.cpp" #include "window.cpp" @@ -173,6 +174,7 @@ auto PPU::power(bool reset) -> void { //$213d OPVCT io.vcounter = 0; + mosaic.power(); bg1.power(); bg2.power(); bg3.power(); diff --git a/bsnes/sfc/ppu/ppu.hpp b/bsnes/sfc/ppu/ppu.hpp index c5cd69b4..ed18b553 100644 --- a/bsnes/sfc/ppu/ppu.hpp +++ b/bsnes/sfc/ppu/ppu.hpp @@ -153,11 +153,13 @@ struct PPU : Thread, PPUcounter { uint16 vcounter; } io; + #include "mosaic.hpp" #include "background.hpp" #include "object.hpp" #include "window.hpp" #include "screen.hpp" + Mosaic mosaic; Background bg1; Background bg2; Background bg3; diff --git a/bsnes/sfc/ppu/serialization.cpp b/bsnes/sfc/ppu/serialization.cpp index 777ca41d..9a022211 100644 --- a/bsnes/sfc/ppu/serialization.cpp +++ b/bsnes/sfc/ppu/serialization.cpp @@ -73,6 +73,7 @@ auto PPU::serialize(serializer& s) -> void { s.integer(io.hcounter); s.integer(io.vcounter); + mosaic.serialize(s); bg1.serialize(s); bg2.serialize(s); bg3.serialize(s); @@ -82,6 +83,11 @@ auto PPU::serialize(serializer& s) -> void { screen.serialize(s); } +auto PPU::Mosaic::serialize(serializer& s) -> void { + s.integer(size); + s.integer(vcounter); +} + auto PPU::Background::serialize(serializer& s) -> void { s.integer(io.tiledataAddress); s.integer(io.screenAddress); @@ -94,9 +100,6 @@ auto PPU::Background::serialize(serializer& s) -> void { s.integer(io.hoffset); s.integer(io.voffset); - s.integer(latch.hoffset); - s.integer(latch.voffset); - s.integer(output.above.priority); s.integer(output.above.palette); s.integer(output.above.paletteGroup); @@ -105,11 +108,8 @@ auto PPU::Background::serialize(serializer& s) -> void { s.integer(output.below.palette); s.integer(output.below.paletteGroup); - s.integer(mosaic.size); s.integer(mosaic.enable); - s.integer(mosaic.vcounter); s.integer(mosaic.hcounter); - s.integer(mosaic.voffset); s.integer(mosaic.hoffset); s.integer(mosaic.pixel.priority); @@ -130,8 +130,6 @@ auto PPU::Background::serialize(serializer& s) -> void { s.array(tile.data); } - s.integer(nameTableIndex); - s.integer(characterIndex); s.integer(renderingIndex); s.integer(pixelCounter); } diff --git a/bsnes/target-bsnes/presentation/presentation.cpp b/bsnes/target-bsnes/presentation/presentation.cpp index afce3562..93f40832 100644 --- a/bsnes/target-bsnes/presentation/presentation.cpp +++ b/bsnes/target-bsnes/presentation/presentation.cpp @@ -42,10 +42,12 @@ auto Presentation::create() -> void { if(settings.video.output == "Stretch") stretchViewport.setChecked(); aspectCorrection.setText("Aspect Correction").setChecked(settings.video.aspectCorrection).onToggle([&] { settings.video.aspectCorrection = aspectCorrection.checked(); + emulator->configure("Video/AspectCorrection", settings.video.aspectCorrection); resizeWindow(); }); showOverscanArea.setText("Show Overscan Area").setChecked(settings.video.overscan).onToggle([&] { settings.video.overscan = showOverscanArea.checked(); + emulator->configure("Video/Overscan", settings.video.overscan); resizeWindow(); }); blurEmulation.setText("Hires Blur Emulation").setChecked(settings.video.blur).onToggle([&] { @@ -201,7 +203,7 @@ auto Presentation::create() -> void { .setName("bsnes (upstream)") .setLogo(Resource::Logo) .setDescription("Super Nintendo emulator") - .setVersion("114")//bsnes/emulator/emulator.hpp:Emulator:Version + .setVersion("114.3")//bsnes/emulator/emulator.hpp:Emulator:Version .setAuthor("byuu") .setLicense("GPLv3") .setWebsite("https://byuu.org") diff --git a/bsnes/target-bsnes/program/game.cpp b/bsnes/target-bsnes/program/game.cpp index da37cb7f..3769280e 100644 --- a/bsnes/target-bsnes/program/game.cpp +++ b/bsnes/target-bsnes/program/game.cpp @@ -75,7 +75,6 @@ auto Program::load() -> void { if(auto& game = sufamiTurboB) games.append(game.option, ";", game.location, "|"); presentation.addRecentGame(games.trimRight("|", 1L)); - updateVideoPalette(); updateVideoEffects(); updateAudioEffects(); updateAudioFrequency(); diff --git a/bsnes/target-bsnes/program/program.hpp b/bsnes/target-bsnes/program/program.hpp index f6d90007..f24b6032 100644 --- a/bsnes/target-bsnes/program/program.hpp +++ b/bsnes/target-bsnes/program/program.hpp @@ -95,7 +95,6 @@ struct Program : Lock, Emulator::Platform { auto updateVideoMonitor() -> void; auto updateVideoFormat() -> void; auto updateVideoShader() -> void; - auto updateVideoPalette() -> void; auto updateVideoEffects() -> void; auto toggleVideoFullScreen() -> void; auto toggleVideoPseudoFullScreen() -> void; diff --git a/bsnes/target-bsnes/program/video.cpp b/bsnes/target-bsnes/program/video.cpp index 362de8e1..7b7a3492 100644 --- a/bsnes/target-bsnes/program/video.cpp +++ b/bsnes/target-bsnes/program/video.cpp @@ -62,64 +62,11 @@ auto Program::updateVideoShader() -> void { video.setShader(settings.video.shader); } -auto Program::updateVideoPalette() -> void { - double luminance = settings.video.luminance / 100.0; - double saturation = settings.video.saturation / 100.0; - double gamma = settings.video.gamma / 100.0; - - uint depth = 24; - if(video.format() == "RGB30") depth = 30; - - for(uint color : range(32768)) { - uint16 r = (color >> 10) & 31; - uint16 g = (color >> 5) & 31; - uint16 b = (color >> 0) & 31; - - r = r << 3 | r >> 2; r = r << 8 | r << 0; - g = g << 3 | g >> 2; g = g << 8 | g << 0; - b = b << 3 | b >> 2; b = b << 8 | b << 0; - - if(saturation != 1.0) { - uint16 grayscale = uclamp<16>((r + g + b) / 3); - double inverse = max(0.0, 1.0 - saturation); - r = uclamp<16>(r * saturation + grayscale * inverse); - g = uclamp<16>(g * saturation + grayscale * inverse); - b = uclamp<16>(b * saturation + grayscale * inverse); - } - - if(gamma != 1.0) { - double reciprocal = 1.0 / 32767.0; - r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma)); - g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma)); - b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma)); - } - - if(luminance != 1.0) { - r = uclamp<16>(r * luminance); - g = uclamp<16>(g * luminance); - b = uclamp<16>(b * luminance); - } - - switch(depth) { - case 24: palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break; - case 30: palette[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break; - } - - r >>= 1; - g >>= 1; - b >>= 1; - - switch(depth) { - case 24: paletteDimmed[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break; - case 30: paletteDimmed[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break; - } - } - - emulator->configure("Video/ColorEmulation", false); -} - auto Program::updateVideoEffects() -> void { emulator->configure("Video/BlurEmulation", settings.video.blur); + // Widescreen AR relevant settings: + emulator->configure("Video/AspectCorrection", settings.video.aspectCorrection); + emulator->configure("Video/Overscan", settings.video.overscan); } auto Program::toggleVideoFullScreen() -> void { diff --git a/bsnes/target-bsnes/program/viewport.cpp b/bsnes/target-bsnes/program/viewport.cpp index 049f4ea5..06ef1003 100644 --- a/bsnes/target-bsnes/program/viewport.cpp +++ b/bsnes/target-bsnes/program/viewport.cpp @@ -55,25 +55,26 @@ auto Program::viewportRefresh() -> void { scale = screenshot.scale; } - if(!settings.video.overscan) { - uint multiplier = height / 240; - data += 8 * multiplier * (pitch >> 1); - height -= 16 * multiplier; - } - - if(settings.emulator.hack.ppu.mode7.widescreen > 0) { - width += 2*settings.emulator.hack.ppu.mode7.widescreen; - } + uint offset = settings.video.overscan ? 8 : 12; + uint multiplier = height / 215; + data += offset * multiplier * (pitch >> 2); + height -= offset * multiplier * 2; uint outputWidth = width, outputHeight = height; viewportSize(outputWidth, outputHeight, scale); uint filterWidth = width, filterHeight = height; - auto filterRender = filterSelect(filterWidth, filterHeight, scale); if(auto [output, length] = video.acquire(filterWidth, filterHeight); output) { - bool dimmed = settings.video.dimming && !presentation.frameAdvance.checked(); - filterRender(dimmed ? paletteDimmed : palette, output, length, (const uint16_t*)data, pitch, width, height); + //HD-TODO: add back 'dimmed' (rgb each >>=1) + if (length == pitch) { + memory::copy(output, data, width * height); + } else { + for(uint y = 0; y < height; y++) { + memory::copy(output + y * (length >> 2), data + y * (pitch >> 2), width); + } + } + length >>= 2; if(settings.video.snow) { diff --git a/bsnes/target-bsnes/settings/enhancements.cpp b/bsnes/target-bsnes/settings/enhancements.cpp index 601fa57c..5fbf4798 100644 --- a/bsnes/target-bsnes/settings/enhancements.cpp +++ b/bsnes/target-bsnes/settings/enhancements.cpp @@ -152,18 +152,18 @@ auto EnhancementSettings::create() -> void { mode7Widescreen.append(ComboButtonItem().setText( "2:1")); mode7Widescreen.append(ComboButtonItem().setText("21:9")); if (8 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(0).setSelected(); - else if ( 28 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(1).setSelected(); - else if ( 52 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(2).setSelected(); - else if ( 76 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(3).setSelected(); - else if (104 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(4).setSelected(); - else if (160 > settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(5).setSelected(); + else if ( 28 > settings.emulator.hack.ppu.mode7.widescreen || 403 == settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(1).setSelected(); + else if ( 52 > settings.emulator.hack.ppu.mode7.widescreen || 1610 == settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(2).setSelected(); + else if ( 76 > settings.emulator.hack.ppu.mode7.widescreen || 1609 == settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(3).setSelected(); + else if (104 > settings.emulator.hack.ppu.mode7.widescreen || 201 == settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(4).setSelected(); + else if (160 > settings.emulator.hack.ppu.mode7.widescreen || 2109 == settings.emulator.hack.ppu.mode7.widescreen) mode7Widescreen.item(5).setSelected(); mode7Widescreen.onChange([&] { - if (0 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 0; - else if (1 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 16; - else if (2 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 40; - else if (3 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 64; - else if (4 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 88; - else if (5 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 120; + if (0 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 0; + else if (1 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 403; + else if (2 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 1610; + else if (3 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 1609; + else if (4 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 201; + else if (5 == mode7Widescreen.selected().offset()) settings.emulator.hack.ppu.mode7.widescreen = 2109; emulator->configure("Hacks/PPU/Mode7/Widescreen", settings.emulator.hack.ppu.mode7.widescreen); }); @@ -322,13 +322,13 @@ auto EnhancementSettings::create() -> void { //settings.emulator.hack.ppu.mode7.igwinx = igwinx.selected().offset(); if (0 == mode7Widescreen.selected().offset()) { settings.emulator.hack.ppu.mode7.igwinx = 40; - } else if (1 == mode7Widescreen.selected().offset()) { + } else if (1 == igwinx.selected().offset()) { settings.emulator.hack.ppu.mode7.igwinx = 88; - } else if (2 == mode7Widescreen.selected().offset()) { + } else if (2 == igwinx.selected().offset()) { settings.emulator.hack.ppu.mode7.igwinx = 128; - } else if (3 == mode7Widescreen.selected().offset()) { + } else if (3 == igwinx.selected().offset()) { settings.emulator.hack.ppu.mode7.igwinx = 168; - } else if (4 == mode7Widescreen.selected().offset()) { + } else if (4 == igwinx.selected().offset()) { settings.emulator.hack.ppu.mode7.igwinx = 216; } emulator->configure("Hacks/PPU/Mode7/Igwinx", settings.emulator.hack.ppu.mode7.igwinx); diff --git a/bsnes/target-bsnes/settings/settings.hpp b/bsnes/target-bsnes/settings/settings.hpp index d7b9eae9..66df7e6f 100644 --- a/bsnes/target-bsnes/settings/settings.hpp +++ b/bsnes/target-bsnes/settings/settings.hpp @@ -126,7 +126,7 @@ struct Settings : Markup::Node { uint igwin = 1; uint igwinx = 128; uint bgGrad = 4; - uint windRad = 0; + uint windRad = 0; uint wsMode = 1; uint wsBgCol = 1; uint wsMarker = 0; diff --git a/bsnes/target-bsnes/settings/video.cpp b/bsnes/target-bsnes/settings/video.cpp index bdd67769..ccbb61a2 100644 --- a/bsnes/target-bsnes/settings/video.cpp +++ b/bsnes/target-bsnes/settings/video.cpp @@ -10,24 +10,24 @@ auto VideoSettings::create() -> void { luminanceSlider.setLength(101).setPosition(settings.video.luminance).onChange([&] { string value = {luminanceSlider.position(), "%"}; settings.video.luminance = value.natural(); + emulator->configure("Video/Luminance", settings.video.luminance); luminanceValue.setText(value); - program.updateVideoPalette(); }).doChange(); saturationLabel.setText("Saturation:"); saturationValue.setAlignment(0.5); saturationSlider.setLength(201).setPosition(settings.video.saturation).onChange([&] { string value = {saturationSlider.position(), "%"}; settings.video.saturation = value.natural(); + emulator->configure("Video/Saturation", settings.video.saturation); saturationValue.setText(value); - program.updateVideoPalette(); }).doChange(); gammaLabel.setText("Gamma:"); gammaValue.setAlignment(0.5); gammaSlider.setLength(101).setPosition(settings.video.gamma - 100).onChange([&] { string value = {100 + gammaSlider.position(), "%"}; settings.video.gamma = value.natural(); + emulator->configure("Video/Gamma", settings.video.gamma); gammaValue.setText(value); - program.updateVideoPalette(); }).doChange(); dimmingOption.setText("Dim video when idle").setToolTip( diff --git a/bsnes/target-libretro/libretro.cpp b/bsnes/target-libretro/libretro.cpp index 409bd137..3b6c0d5a 100644 --- a/bsnes/target-libretro/libretro.cpp +++ b/bsnes/target-libretro/libretro.cpp @@ -1,5 +1,6 @@ #include #include "libretro.h" +#include static retro_environment_t environ_cb; static retro_video_refresh_t video_cb; @@ -43,7 +44,7 @@ static vector cheatList; #define RETRO_MEMORY_SGB_SRAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) #define RETRO_MEMORY_GB_SRAM ((2 << 8) | RETRO_MEMORY_SAVE_RAM) -static bool flush_variables() // returns whether video dimensions have changed (scale or widescreen AR) +static bool flush_variables() // returns whether video dimensions have changed (overscan, aspectcorrection scale or widescreen AR) { retro_variable variable = { "bsnes_blur_emulation", nullptr }; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) @@ -140,14 +141,16 @@ static bool flush_variables() // returns whether video dimensions have changed ( emulator->configure("Hacks/PPU/NoVRAMBlocking", false); } + bool overscan = program->overscan; variable = { "bsnes_ppu_show_overscan", nullptr }; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) { if (strcmp(variable.value, "ON") == 0) - program->overscan = true; + overscan = true; else if (strcmp(variable.value, "OFF") == 0) - program->overscan = false; + overscan = false; } + emulator->configure("Video/Overscan", overscan); variable = { "bsnes_dsp_fast", nullptr }; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) @@ -231,12 +234,12 @@ static bool flush_variables() // returns whether video dimensions have changed ( int ws = 0; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) { - if (strcmp(variable.value, "none") == 0) ws = 0; - else if (strcmp(variable.value, "4:3") == 0) ws = 16; - else if (strcmp(variable.value, "16:10") == 0) ws = 40; - else if (strcmp(variable.value, "16:9") == 0) ws = 64; - else if (strcmp(variable.value, "2:1") == 0) ws = 88; - else if (strcmp(variable.value, "21:9") == 0) ws = 120; + if (strcmp(variable.value, "none") == 0) ws = 0; + else if (strcmp(variable.value, "4:3") == 0) ws = 403; + else if (strcmp(variable.value, "16:10") == 0) ws = 1610; + else if (strcmp(variable.value, "16:9") == 0) ws = 1609; + else if (strcmp(variable.value, "2:1") == 0) ws = 201; + else if (strcmp(variable.value, "21:9") == 0) ws = 2109; } if (scale == 0) ws = 0; emulator->configure("Hacks/PPU/Mode7/Widescreen", ws); @@ -499,19 +502,55 @@ static bool flush_variables() // returns whether video dimensions have changed ( emulator->configure("Hacks/PPU/Mode7/'WindRad", val); } - bool vc = false; - vc = program->scale != scale; // scale changed + bool aspectcorrection = program->aspectcorrection; + variable = { "bsnes_video_aspectcorrection", nullptr }; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) + { + if (strcmp(variable.value, "ON") == 0) + aspectcorrection = true; + else if (strcmp(variable.value, "OFF") == 0) + aspectcorrection = false; + } + emulator->configure("Video/AspectCorrection", aspectcorrection); + + variable = { "bsnes_video_luminance", nullptr }; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) + { + int val = atoi(variable.value); + emulator->configure("Video/Luminance", val); + } + + variable = { "bsnes_video_saturation", nullptr }; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) + { + int val = atoi(variable.value); + emulator->configure("Video/Saturation", val); + } + + variable = { "bsnes_video_gamma", nullptr }; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) + { + int val = atoi(variable.value); + emulator->configure("Video/Gamma", val); + } + + bool vc = program->overscan != overscan; // overscan changed + program->overscan = overscan; + vc = vc | program->aspectcorrection != aspectcorrection; // aspectcorrection changed + program->aspectcorrection = aspectcorrection; + vc = vc | program->scale != scale; // scale changed program->scale = scale; + ws = HdToolkit::determineWsExt(ws, overscan, aspectcorrection); vc = vc | program->ws != ws; // widescreen AR changed program->ws = ws; - return vc; // returns whether video dimensions have changed (scale or widescreen AR) + return vc; // returns whether video dimensions have changed (overscan, aspectcorrection scale or widescreen AR) } static void check_variables() { bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { - if (flush_variables()) { // returns whether video dimensions have changed (scale or widescreen AR) + if (flush_variables()) { // returns whether video dimensions have changed (overscan, aspectcorrection scale or widescreen AR) struct retro_system_av_info info; retro_get_system_av_info(&info); environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &info); @@ -684,18 +723,19 @@ static void set_environment_info(retro_environment_t cb) { "bsnes_mode7_wsMarkerAlpha", "WideScreen Marker Alpha; 1/1|1/2|1/3|1/4|1/5|1/6|1/7|1/8|1/9|1/10" }, { "bsnes_mode7_bgGrad", "HD Background Color Radius; 4|5|6|7|8|0|1|2|3" }, { "bsnes_mode7_windRad", "HD Windowing (experimental); 0|1|2|3|4|5|6|7|8" }, + { "bsnes_ppu_show_overscan", "Show Overscan; OFF|ON" }, + { "bsnes_video_aspectcorrection", "Aspect Correction; OFF|ON" }, { "bsnes_blur_emulation", "Blur emulation; OFF|ON" }, { "bsnes_entropy", "Entropy (randomization); Low|High|None" }, { "bsnes_hotfixes", "Hotfixes; OFF|ON" }, - { "bsnes_cpu_overclock", "CPU Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400|10|20|30|40|50|60|70|80|90" }, { "bsnes_cpu_fastmath", "CPU Fast Math; OFF|ON" }, - { "bsnes_sa1_overclock", "SA1 Coprocessor Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400|10|20|30|40|50|60|70|80|90" }, - { "bsnes_sfx_overclock", "SuperFX Coprocessor Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400|410|420|430|440|450|460|470|480|490|500|510|520|530|540|550|560|570|580|590|600|610|620|630|640|650|660|670|680|690|700|710|720|730|740|750|760|770|780|790|800|10|20|30|40|50|60|70|80|90" }, + { "bsnes_cpu_overclock", "CPU Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400" }, + { "bsnes_sa1_overclock", "SA1 Coprocessor Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400" }, + { "bsnes_sfx_overclock", "SuperFX Coprocessor Overclocking; 100|110|120|130|140|150|160|170|180|190|200|210|220|230|240|250|260|270|280|290|300|310|320|330|340|350|360|370|380|390|400|410|420|430|440|450|460|470|480|490|500|510|520|530|540|550|560|570|580|590|600|610|620|630|640|650|660|670|680|690|700|710|720|730|740|750|760|770|780|790|800" }, { "bsnes_ppu_fast", "PPU Fast mode; ON|OFF" }, { "bsnes_ppu_deinterlace", "PPU Deinterlace; ON|OFF" }, { "bsnes_ppu_no_sprite_limit", "PPU No sprite limit; ON|OFF" }, { "bsnes_ppu_no_vram_blocking", "PPU No VRAM blocking; OFF|ON" }, - { "bsnes_ppu_show_overscan", "Show Overscan; OFF|ON" }, { "bsnes_dsp_fast", "DSP Fast mode; ON|OFF" }, { "bsnes_dsp_cubic", "DSP Cubic interpolation; OFF|ON" }, { "bsnes_dsp_echo_shadow", "DSP Echo shadow RAM; OFF|ON" }, @@ -703,6 +743,9 @@ static void set_environment_info(retro_environment_t cb) { "bsnes_coprocessor_prefer_hle", "Coprocessor Prefer HLE; ON|OFF" }, { "bsnes_sgb_bios", "Preferred Super GameBoy BIOS (restart); SGB1.sfc|SGB2.sfc" }, { "bsnes_run_ahead_frames", "Amount of frames for run-ahead; OFF|1|2|3|4" }, + { "bsnes_video_luminance", "Luminance; 100|90|80|70|60|50|40|30|20|10|0" }, + { "bsnes_video_saturation", "Saturation; 100|90|80|70|60|50|40|30|20|10|0|200|190|180|170|160|150|140|130|120|110" }, + { "bsnes_video_gamma", "Gamma; 150|140|130|120|110|100|200|190|180|170|160" }, { nullptr }, }; cb(RETRO_ENVIRONMENT_SET_VARIABLES, const_cast(vars)); @@ -784,7 +827,8 @@ RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info) info->geometry.max_width = w; info->geometry.max_height = h; } - info->geometry.aspect_ratio = -1.0; + info->geometry.aspect_ratio = !program->aspectcorrection ? -1.0 + : 8.0 / 7.0 * info->geometry.base_width / info->geometry.base_height; info->timing.sample_rate = SAMPLERATE; if (retro_get_region() == RETRO_REGION_NTSC) { info->timing.fps = 21477272.0 / 357366.0; @@ -917,6 +961,10 @@ RETRO_API bool retro_load_game(const retro_game_info *game) RETRO_API bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) { + retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) + return false; + emulator->configure("Audio/Frequency", SAMPLERATE); flush_variables(); diff --git a/bsnes/target-libretro/program.cpp b/bsnes/target-libretro/program.cpp index b38d68a8..5e0d369d 100644 --- a/bsnes/target-libretro/program.cpp +++ b/bsnes/target-libretro/program.cpp @@ -47,6 +47,7 @@ struct Program : Emulator::Platform string base_name; bool overscan = false; + bool aspectcorrection = false; uint ws = 0; uint scale = 1; @@ -226,7 +227,7 @@ auto Program::load(uint id, string name, string type, vector options) -> auto Program::videoFrame(const uint32* data, uint pitch, uint width, uint height, uint scale) -> void { uint offset = overscan ? 8 : 12; - uint multiplier = height / 240; + uint multiplier = height / 215; data += offset * (pitch >> 2) * multiplier; height -= offset * 2 * multiplier;