diff --git a/include/mgba/internal/ds/renderers/software.h b/include/mgba/internal/ds/renderers/software.h index f15e0d934..6d7eb4673 100644 --- a/include/mgba/internal/ds/renderers/software.h +++ b/include/mgba/internal/ds/renderers/software.h @@ -27,6 +27,11 @@ struct DSVideoSoftwareRenderer { int outputBufferStride; uint32_t row[DS_VIDEO_HORIZONTAL_PIXELS]; + + color_t extPaletteA[16384]; + color_t extPaletteB[16384]; + color_t variantPaletteA[16384]; + color_t variantPaletteB[16384]; }; void DSVideoSoftwareRendererCreate(struct DSVideoSoftwareRenderer* renderer); diff --git a/include/mgba/internal/ds/video.h b/include/mgba/internal/ds/video.h index 34db43286..35cf9ba41 100644 --- a/include/mgba/internal/ds/video.h +++ b/include/mgba/internal/ds/video.h @@ -57,11 +57,15 @@ DECL_BITS(DSRegisterDISPCNT, TileBoundary, 20, 2); DECL_BITS(DSRegisterDISPCNT, CharBase, 24, 3); DECL_BITS(DSRegisterDISPCNT, ScreenBase, 27, 3); // TODO +DECL_BIT(DSRegisterDISPCNT, BgExtPalette, 30); +DECL_BIT(DSRegisterDISPCNT, ObjExtPalette, 31); DECL_BITFIELD(DSRegisterPOWCNT1, uint16_t); // TODO DECL_BIT(DSRegisterPOWCNT1, Swap, 15); +DECL_BIT(GBARegisterBGCNT, ExtPaletteSlot, 13); + struct DSVideoRenderer { void (*init)(struct DSVideoRenderer* renderer); void (*reset)(struct DSVideoRenderer* renderer); @@ -70,6 +74,7 @@ struct DSVideoRenderer { uint16_t (*writeVideoRegister)(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); void (*writePalette)(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); void (*writeOAM)(struct DSVideoRenderer* renderer, uint32_t oam); + void (*invalidateExtPal)(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot); void (*drawScanline)(struct DSVideoRenderer* renderer, int y); void (*finishFrame)(struct DSVideoRenderer* renderer); @@ -80,8 +85,12 @@ struct DSVideoRenderer { uint16_t* vram; uint16_t* vramABG[32]; uint16_t* vramAOBJ[32]; + uint16_t* vramABGExtPal[4]; + uint16_t* vramAOBJExtPal[4]; uint16_t* vramBBG[32]; uint16_t* vramBOBJ[32]; + uint16_t* vramBBGExtPal[4]; + uint16_t* vramBOBJExtPal[4]; union DSOAM* oam; }; @@ -99,8 +108,12 @@ struct DSVideo { uint16_t* vram; uint16_t* vramABG[32]; uint16_t* vramAOBJ[32]; + uint16_t* vramABGExtPal[4]; + uint16_t* vramAOBJExtPal[4]; uint16_t* vramBBG[32]; uint16_t* vramBOBJ[32]; + uint16_t* vramBBGExtPal[4]; + uint16_t* vramBOBJExtPal[4]; union DSOAM oam; int32_t frameCounter; @@ -117,7 +130,7 @@ struct DSCommon; void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value); struct DSMemory; -void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value); +void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value, uint8_t oldValue); CXX_GUARD_START diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h index a9347975c..7f930a4e8 100644 --- a/include/mgba/internal/gba/renderers/video-software.h +++ b/include/mgba/internal/gba/renderers/video-software.h @@ -42,6 +42,8 @@ struct GBAVideoSoftwareBackground { int16_t dmy; int32_t sx; int32_t sy; + color_t* extPalette; + color_t* variantPalette; }; enum BlendEffect { @@ -116,7 +118,7 @@ struct GBAVideoSoftwareRenderer { uint32_t* temporaryBuffer; - GBARegisterDISPCNT dispcnt; + uint32_t dispcnt; uint32_t row[256]; uint32_t spriteLayer[256]; diff --git a/src/ds/io.c b/src/ds/io.c index 972c6b792..420bf3bfd 100644 --- a/src/ds/io.c +++ b/src/ds/io.c @@ -424,24 +424,28 @@ void DS9IOWrite(struct DS* ds, uint32_t address, uint16_t value) { } else if (address >= DS9_REG_B_DISPCNT_LO && address <= DS9_REG_B_BLDY) { value = ds->video.renderer->writeVideoRegister(ds->video.renderer, address, value); } else { + uint16_t oldValue; switch (address) { // VRAM control case DS9_REG_VRAMCNT_A: case DS9_REG_VRAMCNT_C: case DS9_REG_VRAMCNT_E: + oldValue = ds->memory.io9[address >> 1]; value &= 0x9F9F; - DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A, value & 0xFF); - DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A + 1, value >> 8); + DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A, value & 0xFF, oldValue & 0xFF); + DSVideoConfigureVRAM(ds, address - DS9_REG_VRAMCNT_A + 1, value >> 8, oldValue >> 8); break; case DS9_REG_VRAMCNT_G: + oldValue = ds->memory.io9[address >> 1]; value &= 0x9F03; - DSVideoConfigureVRAM(ds, 6, value & 0xFF); + DSVideoConfigureVRAM(ds, 6, value & 0xFF, oldValue & 0xFF); DSConfigureWRAM(&ds->memory, value >> 8); break; case DS9_REG_VRAMCNT_H: + oldValue = ds->memory.io9[address >> 1]; value &= 0x9F9F; - DSVideoConfigureVRAM(ds, 7, value & 0xFF); - DSVideoConfigureVRAM(ds, 8, value >> 8); + DSVideoConfigureVRAM(ds, 7, value & 0xFF, oldValue & 0xFF); + DSVideoConfigureVRAM(ds, 8, value >> 8, oldValue >> 8); break; case DS9_REG_EXMEMCNT: diff --git a/src/ds/memory.c b/src/ds/memory.c index eb28e32b1..5744af509 100644 --- a/src/ds/memory.c +++ b/src/ds/memory.c @@ -202,15 +202,15 @@ void DSMemoryReset(struct DS* ds) { DSSPIReset(ds); DSSlot1Reset(ds); - DSVideoConfigureVRAM(ds, 0, 0); - DSVideoConfigureVRAM(ds, 1, 0); - DSVideoConfigureVRAM(ds, 2, 0); - DSVideoConfigureVRAM(ds, 3, 0); - DSVideoConfigureVRAM(ds, 4, 0); - DSVideoConfigureVRAM(ds, 5, 0); - DSVideoConfigureVRAM(ds, 6, 0); - DSVideoConfigureVRAM(ds, 7, 0); - DSVideoConfigureVRAM(ds, 8, 0); + DSVideoConfigureVRAM(ds, 0, 0, 1); + DSVideoConfigureVRAM(ds, 1, 0, 1); + DSVideoConfigureVRAM(ds, 2, 0, 1); + DSVideoConfigureVRAM(ds, 3, 0, 1); + DSVideoConfigureVRAM(ds, 4, 0, 1); + DSVideoConfigureVRAM(ds, 5, 0, 1); + DSVideoConfigureVRAM(ds, 6, 0, 1); + DSVideoConfigureVRAM(ds, 7, 0, 1); + DSVideoConfigureVRAM(ds, 8, 0, 1); DSConfigureWRAM(&ds->memory, 3); if (!ds->memory.wram || !ds->memory.wram7 || !ds->memory.ram || !ds->memory.itcm || !ds->memory.dtcm) { diff --git a/src/ds/renderers/software.c b/src/ds/renderers/software.c index a800a4918..f7e66d578 100644 --- a/src/ds/renderers/software.c +++ b/src/ds/renderers/software.c @@ -4,6 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include "gba/renderers/software-private.h" #include #include @@ -14,11 +15,61 @@ static void DSVideoSoftwareRendererReset(struct DSVideoRenderer* renderer); static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); static void DSVideoSoftwareRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam); +static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot); static void DSVideoSoftwareRendererDrawScanline(struct DSVideoRenderer* renderer, int y); static void DSVideoSoftwareRendererFinishFrame(struct DSVideoRenderer* renderer); static void DSVideoSoftwareRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels); static void DSVideoSoftwareRendererPutPixels(struct DSVideoRenderer* renderer, size_t stride, const void* pixels); +static bool _regenerateExtPalette(struct DSVideoSoftwareRenderer* renderer, bool engB, int slot) { + color_t* palette; + color_t* variantPalette; + struct GBAVideoSoftwareRenderer* softwareRenderer; + uint16_t* vram; + if (!engB) { + palette = &renderer->extPaletteA[slot * 4096]; + variantPalette = &renderer->variantPaletteA[slot * 4096]; + softwareRenderer = &renderer->engA; + vram = renderer->d.vramABGExtPal[slot]; + } else { + palette = &renderer->extPaletteB[slot * 4096]; + variantPalette = &renderer->variantPaletteB[slot * 4096]; + softwareRenderer = &renderer->engB; + vram = renderer->d.vramBBGExtPal[slot]; + } + if (!vram) { + return false; + } + int i; + for (i = 0; i < 4096; ++i) { + uint16_t value = vram[i]; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + unsigned color = 0; + color |= (value & 0x001F) << 11; + color |= (value & 0x03E0) << 1; + color |= (value & 0x7C00) >> 10; +#else + unsigned color = value; +#endif +#else + unsigned color = 0; + color |= (value << 3) & 0xF8; + color |= (value << 6) & 0xF800; + color |= (value << 9) & 0xF80000; + color |= (color >> 5) & 0x070707; +#endif + palette[i] = color; + if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { + variantPalette[i] = _brighten(color, softwareRenderer->bldy); + } else if (softwareRenderer->blendEffect == BLEND_DARKEN) { + variantPalette[i] = _darken(color, softwareRenderer->bldy); + } + } + return true; +} + + void DSVideoSoftwareRendererCreate(struct DSVideoSoftwareRenderer* renderer) { renderer->d.init = DSVideoSoftwareRendererInit; renderer->d.reset = DSVideoSoftwareRendererReset; @@ -26,6 +77,7 @@ void DSVideoSoftwareRendererCreate(struct DSVideoSoftwareRenderer* renderer) { renderer->d.writeVideoRegister = DSVideoSoftwareRendererWriteVideoRegister; renderer->d.writePalette = DSVideoSoftwareRendererWritePalette; renderer->d.writeOAM = DSVideoSoftwareRendererWriteOAM; + renderer->d.invalidateExtPal = DSVideoSoftwareRendererInvalidateExtPal; renderer->d.drawScanline = DSVideoSoftwareRendererDrawScanline; renderer->d.finishFrame = DSVideoSoftwareRendererFinishFrame; renderer->d.getPixels = DSVideoSoftwareRendererGetPixels; @@ -75,6 +127,25 @@ static void DSVideoSoftwareRendererUpdateDISPCNTA(struct DSVideoSoftwareRenderer fakeDispcnt = GBARegisterDISPCNTFillObjCharacterMapping(fakeDispcnt); } softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_DISPCNT_LO, fakeDispcnt); + softwareRenderer->engA.dispcnt |= softwareRenderer->dispcntA & 0xFFFF000; + if (DSRegisterDISPCNTIsBgExtPalette(softwareRenderer->dispcntA)) { + int i; + for (i = 0; i < 4; ++i) { + // TODO: Regenerate on change + int slot = i; + if (i < 2 && GBARegisterBGCNTIsExtPaletteSlot(softwareRenderer->engA.bg[i].control)) { + slot += 2; + } + if (softwareRenderer->engA.bg[i].extPalette != &softwareRenderer->extPaletteA[slot * 4096] && _regenerateExtPalette(softwareRenderer, false, slot)) { + softwareRenderer->engA.bg[i].extPalette = &softwareRenderer->extPaletteA[slot * 4096]; + } + } + } else { + softwareRenderer->engA.bg[0].extPalette = NULL; + softwareRenderer->engA.bg[1].extPalette = NULL; + softwareRenderer->engA.bg[2].extPalette = NULL; + softwareRenderer->engA.bg[3].extPalette = NULL; + } uint32_t charBase = DSRegisterDISPCNTGetCharBase(softwareRenderer->dispcntA) << 16; uint32_t screenBase = DSRegisterDISPCNTGetScreenBase(softwareRenderer->dispcntA) << 16; softwareRenderer->engA.d.writeVideoRegister(&softwareRenderer->engA.d, DS9_REG_A_BG0CNT, softwareRenderer->engA.bg[0].control); @@ -96,6 +167,7 @@ static void DSVideoSoftwareRendererUpdateDISPCNTA(struct DSVideoSoftwareRenderer } static void DSVideoSoftwareRendererUpdateDISPCNTB(struct DSVideoSoftwareRenderer* softwareRenderer) { + // TODO: Share code with DISPCNTA uint16_t fakeDispcnt = softwareRenderer->dispcntB & 0xFF87; if (!DSRegisterDISPCNTIsTileObjMapping(softwareRenderer->dispcntB)) { softwareRenderer->engB.tileStride = 0x20; @@ -104,6 +176,25 @@ static void DSVideoSoftwareRendererUpdateDISPCNTB(struct DSVideoSoftwareRenderer fakeDispcnt = GBARegisterDISPCNTFillObjCharacterMapping(fakeDispcnt); } softwareRenderer->engB.d.writeVideoRegister(&softwareRenderer->engB.d, DS9_REG_A_DISPCNT_LO, fakeDispcnt); + softwareRenderer->engB.dispcnt |= softwareRenderer->dispcntB & 0xFFFF000; + if (DSRegisterDISPCNTIsBgExtPalette(softwareRenderer->dispcntB)) { + int i; + for (i = 0; i < 4; ++i) { + // TODO: Regenerate on change + int slot = i; + if (i < 2 && GBARegisterBGCNTIsExtPaletteSlot(softwareRenderer->engB.bg[i].control)) { + slot += 2; + } + if (softwareRenderer->engB.bg[i].extPalette != &softwareRenderer->extPaletteB[slot * 4096] && _regenerateExtPalette(softwareRenderer, true, slot)) { + softwareRenderer->engB.bg[i].extPalette = &softwareRenderer->extPaletteB[slot * 4096]; + } + } + } else { + softwareRenderer->engA.bg[0].extPalette = NULL; + softwareRenderer->engA.bg[1].extPalette = NULL; + softwareRenderer->engA.bg[2].extPalette = NULL; + softwareRenderer->engA.bg[3].extPalette = NULL; + } } static uint16_t DSVideoSoftwareRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value) { @@ -161,6 +252,89 @@ static void DSVideoSoftwareRendererWriteOAM(struct DSVideoRenderer* renderer, ui } } +static void DSVideoSoftwareRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) { + struct DSVideoSoftwareRenderer* softwareRenderer = (struct DSVideoSoftwareRenderer*) renderer; + _regenerateExtPalette(softwareRenderer, engB, slot); +} + +static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* renderer, int y) { + struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer; + + color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y]; + if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) { + int x; + for (x = 0; x < softwareRenderer->masterEnd; ++x) { + row[x] = GBA_COLOR_WHITE; + } + return; + } + + GBAVideoSoftwareRendererPreprocessBuffer(softwareRenderer, y); + int spriteLayers = GBAVideoSoftwareRendererPreprocessSpriteLayer(softwareRenderer, y); + + int w; + unsigned priority; + for (priority = 0; priority < 4; ++priority) { + softwareRenderer->end = 0; + for (w = 0; w < softwareRenderer->nWindows; ++w) { + softwareRenderer->start = softwareRenderer->end; + softwareRenderer->end = softwareRenderer->windows[w].endX; + softwareRenderer->currentWindow = softwareRenderer->windows[w].control; + if (spriteLayers & (1 << priority)) { + GBAVideoSoftwareRendererPostprocessSprite(softwareRenderer, priority); + } + if (TEST_LAYER_ENABLED(0)) { + GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[0], y); + } + if (TEST_LAYER_ENABLED(1)) { + GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[1], y); + } + if (TEST_LAYER_ENABLED(2)) { + switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) { + case 0: + case 1: + case 3: + GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[2], y); + break; + case 2: + case 4: + GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[2], y); + break; + } + } + if (TEST_LAYER_ENABLED(3)) { + switch (GBARegisterDISPCNTGetMode(softwareRenderer->dispcnt)) { + case 0: + GBAVideoSoftwareRendererDrawBackgroundMode0(softwareRenderer, &softwareRenderer->bg[3], y); + break; + case 1: + case 2: + GBAVideoSoftwareRendererDrawBackgroundMode2(softwareRenderer, &softwareRenderer->bg[3], y); + break; + } + } + } + } + softwareRenderer->bg[2].sx += softwareRenderer->bg[2].dmx; + softwareRenderer->bg[2].sy += softwareRenderer->bg[2].dmy; + softwareRenderer->bg[3].sx += softwareRenderer->bg[3].dmx; + softwareRenderer->bg[3].sy += softwareRenderer->bg[3].dmy; + + GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer); + +#ifdef COLOR_16_BIT +#if defined(__ARM_NEON) && !defined(__APPLE__) + _to16Bit(row, softwareRenderer->row, softwareRenderer->masterEnd); +#else + for (x = 0; x < softwareRenderer->masterEnd; ++x) { + row[x] = softwareRenderer->row[x]; + } +#endif +#else + memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row)); +#endif +} + static void _drawScanlineA(struct DSVideoSoftwareRenderer* softwareRenderer, int y) { memcpy(softwareRenderer->engA.d.vramBG, softwareRenderer->d.vramABG, sizeof(softwareRenderer->engA.d.vramBG)); memcpy(softwareRenderer->engA.d.vramOBJ, softwareRenderer->d.vramAOBJ, sizeof(softwareRenderer->engA.d.vramOBJ)); @@ -174,7 +348,7 @@ static void _drawScanlineA(struct DSVideoSoftwareRenderer* softwareRenderer, int } return; case 1: - softwareRenderer->engA.d.drawScanline(&softwareRenderer->engA.d, y); + DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engA.d, y); return; case 2: { uint16_t* vram = &softwareRenderer->d.vram[0x10000 * DSRegisterDISPCNTGetVRAMBlock(softwareRenderer->dispcntA)]; @@ -229,7 +403,7 @@ static void _drawScanlineB(struct DSVideoSoftwareRenderer* softwareRenderer, int } return; case 1: - softwareRenderer->engB.d.drawScanline(&softwareRenderer->engB.d, y); + DSVideoSoftwareRendererDrawGBAScanline(&softwareRenderer->engB.d, y); return; } diff --git a/src/ds/video.c b/src/ds/video.c index 0a14da02f..8d8dad84e 100644 --- a/src/ds/video.c +++ b/src/ds/video.c @@ -20,6 +20,7 @@ static void DSVideoDummyRendererDeinit(struct DSVideoRenderer* renderer); static uint16_t DSVideoDummyRendererWriteVideoRegister(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); static void DSVideoDummyRendererWritePalette(struct DSVideoRenderer* renderer, uint32_t address, uint16_t value); static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint32_t oam); +static void DSVideoDummyRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot); static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y); static void DSVideoDummyRendererFinishFrame(struct DSVideoRenderer* renderer); static void DSVideoDummyRendererGetPixels(struct DSVideoRenderer* renderer, size_t* stride, const void** pixels); @@ -42,60 +43,86 @@ static const uint32_t _vramSize[9] = { 0x04000 }; +enum DSVRAMBankMode { + MODE_A_BG = 0, + MODE_B_BG = 1, + MODE_A_OBJ = 2, + MODE_B_OBJ = 3, + MODE_LCDC, + MODE_7_VRAM, + MODE_A_BG_EXT_PAL, + MODE_B_BG_EXT_PAL, + MODE_A_OBJ_EXT_PAL, + MODE_B_OBJ_EXT_PAL, + MODE_3D_TEX, + MODE_3D_TEX_PAL, +}; + const struct DSVRAMBankInfo { int base; uint32_t mirrorSize; - int mode; + enum DSVRAMBankMode mode; int offset[4]; } _vramInfo[9][8] = { { // A - { 0x000, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG - { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ + { 0x000, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } }, + { 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x08, 0x80, 0x80 } }, + { 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } }, }, { // B - { 0x008, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG - { 0x000, 0x10, 2, { 0x00, 0x08, 0x80, 0x80 } }, // A-OBJ + { 0x008, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } }, + { 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x08, 0x80, 0x80 } }, + { 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } }, }, { // C - { 0x010, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG - { 0x000, 0x40, 5, { 0x00, 0x08, 0x80, 0x80 } }, // 7-VRAM - {}, - { 0x000, 0x08, 1 }, // B-BG + { 0x010, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } }, + { 0x000, 0x40, MODE_7_VRAM, { 0x00, 0x08, 0x80, 0x80 } }, + { 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } }, + { 0x000, 0x08, MODE_B_BG }, }, { // D - { 0x018, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0, { 0x00, 0x08, 0x10, 0x18 } }, // A-BG - { 0x000, 0x40, 8, { 0x00, 0x08, 0x80, 0x80 } }, // 7-VRAM - {}, - { 0x000, 0x08, 3 }, // B-OBJ + { 0x018, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x08, 0x10, 0x18 } }, + { 0x000, 0x40, MODE_7_VRAM, { 0x00, 0x08, 0x80, 0x80 } }, + { 0x000, 0x10, MODE_3D_TEX, { 0x00, 0x01, 0x02, 0x03 } }, + { 0x000, 0x08, MODE_B_OBJ }, }, { // E - { 0x020, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0 }, // A-BG - { 0x000, 0x10, 2 }, // A-OBJ + { 0x020, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG }, + { 0x000, 0x10, MODE_A_OBJ }, + { 0x000, 0x04, MODE_3D_TEX_PAL }, + { 0x000, 0x04, MODE_A_BG_EXT_PAL }, }, { // F - { 0x024, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0, { 0x00, 0x01, 0x04, 0x05 } }, // A-BG - { 0x000, 0x10, 2, { 0x00, 0x01, 0x04, 0x05 } }, // A-OBJ + { 0x024, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x01, MODE_3D_TEX_PAL, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x02, MODE_A_BG_EXT_PAL, { 0x00, 0x02, 0x00, 0x02 } }, + { 0x000, 0x01, MODE_A_OBJ_EXT_PAL}, }, { // G - { 0x025, 0x40, 4 }, // LCDC - { 0x000, 0x20, 0 }, // A-BG - { 0x000, 0x10, 2 }, // A-OBJ + { 0x025, 0x40, MODE_LCDC }, + { 0x000, 0x20, MODE_A_BG, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x10, MODE_A_OBJ, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x01, MODE_3D_TEX_PAL, { 0x00, 0x01, 0x04, 0x05 } }, + { 0x000, 0x02, MODE_A_BG_EXT_PAL, { 0x00, 0x02, 0x00, 0x02 } }, + { 0x000, 0x01, MODE_A_OBJ_EXT_PAL}, }, { // H - { 0x026, 0x40, 4 }, // LCDC - { 0x000, 0x04, 1 }, // B-BG - { 0x000, 0x10, 2 }, // A-OBJ + { 0x026, 0x40, MODE_LCDC }, + { 0x000, 0x04, MODE_B_BG }, + { 0x000, 0x04, MODE_B_BG_EXT_PAL }, }, { // I - { 0x028, 0x40, 4 }, // LCDC - { 0x002, 0x04, 1 }, // B-BG - { 0x000, 0x01, 3 }, // B-OBJ + { 0x028, 0x40, MODE_LCDC }, + { 0x002, 0x04, MODE_B_BG }, + { 0x000, 0x01, MODE_B_OBJ }, + { 0x000, 0x01, MODE_B_OBJ_EXT_PAL }, }, }; @@ -166,8 +193,12 @@ void DSVideoAssociateRenderer(struct DSVideo* video, struct DSVideoRenderer* ren renderer->vram = video->vram; memcpy(renderer->vramABG, video->vramABG, sizeof(renderer->vramABG)); memcpy(renderer->vramAOBJ, video->vramAOBJ, sizeof(renderer->vramAOBJ)); + memcpy(renderer->vramABGExtPal, video->vramABGExtPal, sizeof(renderer->vramABGExtPal)); + memcpy(renderer->vramAOBJExtPal, video->vramAOBJExtPal, sizeof(renderer->vramAOBJExtPal)); memcpy(renderer->vramBBG, video->vramBBG, sizeof(renderer->vramBBG)); memcpy(renderer->vramBOBJ, video->vramBOBJ, sizeof(renderer->vramBOBJ)); + memcpy(renderer->vramBBGExtPal, video->vramBBGExtPal, sizeof(renderer->vramBBGExtPal)); + memcpy(renderer->vramBOBJExtPal, video->vramBOBJExtPal, sizeof(renderer->vramBOBJExtPal)); renderer->oam = &video->oam; video->renderer->init(video->renderer); } @@ -300,18 +331,83 @@ void DSVideoWriteDISPSTAT(struct DSCommon* dscore, uint16_t value) { // TODO: Does a VCounter IRQ trigger on write? } -void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value) { +void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value, uint8_t oldValue) { struct DSMemory* memory = &ds->memory; + if (value == oldValue) { + return; + } + uint32_t i, j; + uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET; + struct DSVRAMBankInfo oldInfo = _vramInfo[index][oldValue & 0x7]; + uint32_t offset = oldInfo.base + oldInfo.offset[(oldValue >> 3) & 3]; + switch (oldInfo.mode) { + case MODE_A_BG: + for (i = 0; i < size; ++i) { + if (ds->video.vramABG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) { + ds->video.vramABG[offset + i] = NULL; + ds->video.renderer->vramABG[offset + i] = NULL; + } + } + break; + case MODE_B_BG: + for (i = 0; i < size; ++i) { + if (ds->video.vramBBG[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) { + ds->video.vramBBG[offset + i] = NULL; + ds->video.renderer->vramBBG[offset + i] = NULL; + } + } + break; + case MODE_A_OBJ: + for (i = 0; i < size; ++i) { + if (ds->video.vramAOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) { + ds->video.vramAOBJ[offset + i] = NULL; + ds->video.renderer->vramAOBJ[offset + i] = NULL; + } + } + break; + case MODE_B_OBJ: + for (i = 0; i < size; ++i) { + if (ds->video.vramBOBJ[offset + i] == &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]) { + ds->video.vramBOBJ[offset + i] = NULL; + ds->video.renderer->vramBOBJ[offset + i] = NULL; + } + } + break; + case MODE_A_BG_EXT_PAL: + for (i = 0; i < oldInfo.mirrorSize; ++i) { + if (ds->video.vramABGExtPal[offset + i] == &memory->vramBank[index][i << 12]) { + ds->video.vramABGExtPal[offset + i] = NULL; + ds->video.renderer->vramABGExtPal[offset + i] = NULL; + ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i); + } + } + break; + case MODE_B_BG_EXT_PAL: + for (i = 0; i < oldInfo.mirrorSize; ++i) { + if (ds->video.vramBBGExtPal[offset + i] == &memory->vramBank[index][i << 12]) { + ds->video.vramBBGExtPal[offset + i] = NULL; + ds->video.renderer->vramBBGExtPal[offset + i] = NULL; + ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i); + } + } + break; + case MODE_7_VRAM: + for (i = 0; i < size; i += 16) { + ds->memory.vram7[(offset + i) >> 4] = NULL; + } + break; + case MODE_LCDC: + break; + } + struct DSVRAMBankInfo info = _vramInfo[index][value & 0x7]; memset(&memory->vramMirror[index], 0, sizeof(memory->vramMirror[index])); memset(&memory->vramMode[index], 0, sizeof(memory->vramMode[index])); if (!(value & 0x80) || !info.mirrorSize) { return; } - uint32_t size = _vramSize[index] >> DS_VRAM_OFFSET; - uint32_t i, j; - uint32_t offset = info.base + info.offset[(value >> 3) & 3]; - if (info.mode < 4) { + offset = info.base + info.offset[(value >> 3) & 3]; + if (info.mode <= MODE_LCDC) { memory->vramMode[index][info.mode] = 0xFFFF; for (j = offset; j < 0x40; j += info.mirrorSize) { for (i = 0; i < size; ++i) { @@ -320,35 +416,51 @@ void DSVideoConfigureVRAM(struct DS* ds, int index, uint8_t value) { } } switch (info.mode) { - case 0: + case MODE_A_BG: for (i = 0; i < size; ++i) { ds->video.vramABG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]; ds->video.renderer->vramABG[offset + i] = ds->video.vramABG[offset + i]; } break; - case 1: + case MODE_B_BG: for (i = 0; i < size; ++i) { ds->video.vramBBG[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]; ds->video.renderer->vramBBG[offset + i] = ds->video.vramBBG[offset + i]; } break; - case 2: + case MODE_A_OBJ: for (i = 0; i < size; ++i) { ds->video.vramAOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]; ds->video.renderer->vramAOBJ[offset + i] = ds->video.vramAOBJ[offset + i]; } break; - case 3: + case MODE_B_OBJ: for (i = 0; i < size; ++i) { ds->video.vramBOBJ[offset + i] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 1)]; ds->video.renderer->vramBOBJ[offset + i] = ds->video.vramBOBJ[offset + i]; } break; - case 5: + case MODE_A_BG_EXT_PAL: + for (i = 0; i < info.mirrorSize; ++i) { + ds->video.vramABGExtPal[offset + i] = &memory->vramBank[index][i << 12]; + ds->video.renderer->vramABGExtPal[offset + i] = ds->video.vramABGExtPal[offset + i]; + ds->video.renderer->invalidateExtPal(ds->video.renderer, false, false, offset + i); + } + break; + case MODE_B_BG_EXT_PAL: + for (i = 0; i < info.mirrorSize; ++i) { + ds->video.vramBBGExtPal[offset + i] = &memory->vramBank[index][i << 12]; + ds->video.renderer->vramBBGExtPal[offset + i] = ds->video.vramBBGExtPal[offset + i]; + ds->video.renderer->invalidateExtPal(ds->video.renderer, false, true, offset + i); + } + break; + case MODE_7_VRAM: for (i = 0; i < size; i += 16) { ds->memory.vram7[(offset + i) >> 4] = &memory->vramBank[index][i << (DS_VRAM_OFFSET - 5)]; } break; + case MODE_LCDC: + break; } } @@ -385,6 +497,13 @@ static void DSVideoDummyRendererWriteOAM(struct DSVideoRenderer* renderer, uint3 // Nothing to do } +static void DSVideoDummyRendererInvalidateExtPal(struct DSVideoRenderer* renderer, bool obj, bool engB, int slot) { + UNUSED(renderer); + UNUSED(obj); + UNUSED(engB); + // Nothing to do +} + static void DSVideoDummyRendererDrawScanline(struct DSVideoRenderer* renderer, int y) { UNUSED(renderer); UNUSED(y); diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index b61d55cf3..da8ff5e1f 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -15,6 +15,9 @@ } \ screenBase = background->screenBase + yBase + (xBase >> 2); \ uint16_t* screenBlock = renderer->d.vramBG[screenBase >> VRAM_BLOCK_OFFSET]; \ + if (UNLIKELY(!screenBlock)) { \ + return; \ + } \ LOAD_16(mapData, screenBase & VRAM_BLOCK_MASK, screenBlock); \ localY = inY & 0x7; \ if (GBA_TEXT_MAP_VFLIP(mapData)) { \ @@ -361,6 +364,195 @@ } \ } +#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256Ext(BLEND, OBJWIN) \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ + int end2 = end - 4; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + int shift = inX & 0x3; \ + if (LIKELY(vram)) { \ + if (end2 > outX) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + tileData >>= 8 * shift; \ + shift = 0; \ + for (; outX < end2; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + } \ + } \ + \ + if (LIKELY(vram)) { \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + tileData >>= 8 * shift; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + } \ + } else { \ + int start = outX; \ + outX = end - 1; \ + pixel = &renderer->row[outX]; \ + if (LIKELY(vram)) { \ + if (end2 > start) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + for (; outX >= end2; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + charBase += 4; \ + } \ + } \ + \ + if (LIKELY(vram)) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= renderer->start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + } \ + outX = end; \ + pixel = &renderer->row[outX]; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256Ext(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ + if (UNLIKELY(!vram)) { \ + return; \ + } \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \ + palette = &mainPalette[paletteData]; \ + int end = mod8 - 4; \ + pixel = &renderer->row[outX]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (end > 0) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + for (; outX < renderer->end - end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + charBase += 4; \ + } \ + \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + for (; outX < renderer->end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + } else { \ + int shift = (8 - mod8) & 0x3; \ + int start = outX; \ + outX = renderer->end - 1; \ + pixel = &renderer->row[outX]; \ + if (end > 0) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + tileData >>= 8 * shift; \ + for (; outX >= start + 4; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + shift = 0; \ + } \ + \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + tileData >>= 8 * shift; \ + for (; outX >= start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + /* Needed for consistency checks */ \ + if (VIDEO_CHECKS) { \ + outX = renderer->end; \ + pixel = &renderer->row[outX]; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILES_256Ext(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ + if (UNLIKELY(!vram)) { \ + pixel += 8; \ + continue; \ + } \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 1); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 2); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 3); \ + } \ + pixel += 4; \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 1); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 2); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 3); \ + } \ + pixel += 4; \ + } else { \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 3); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 2); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 1); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + pixel += 4; \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 3); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 2); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 1); \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + } \ + pixel += 4; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_MOSAIC_256Ext(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + vram = renderer->d.vramBG[charBase >> VRAM_BLOCK_OFFSET]; \ + tileData = carryData; \ + for (x = 0; x < 8; ++x) { \ + if (!mosaicWait) { \ + if (UNLIKELY(!vram)) { \ + carryData = 0; \ + } else { \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 8; \ + palette = &mainPalette[paletteData]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (x >= 4) { \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + tileData >>= (x - 4) * 8; \ + } else { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + tileData >>= x * 8; \ + } \ + } else { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase & VRAM_BLOCK_MASK, vram); \ + tileData >>= (7 - x) * 8; \ + } else { \ + LOAD_32(tileData, (charBase + 4) & VRAM_BLOCK_MASK, vram); \ + tileData >>= (3 - x) * 8; \ + } \ + } \ + tileData &= 0xFF; \ + carryData = tileData; \ + } \ + mosaicWait = mosaicH; \ + } \ + tileData |= tileData << 8; \ + --mosaicWait; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN, 0); \ + ++pixel; \ + } \ + } + #define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \ uint32_t* pixel = &renderer->row[outX]; \ if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ @@ -460,9 +652,18 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer uint32_t screenBase; uint32_t charBase; int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - color_t* mainPalette = renderer->normalPalette; - if (variant) { - mainPalette = renderer->variantPalette; + color_t* mainPalette; + if (background->multipalette && background->extPalette) { + mainPalette = background->extPalette; + // TODO + /*if (variant) { + mainPalette = background->variantPalette; + }*/ + } else { + mainPalette = renderer->normalPalette; + if (variant) { + mainPalette = renderer->variantPalette; + } } color_t* palette = mainPalette; PREPARE_OBJWIN; @@ -481,28 +682,36 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer if (!(flags & FLAG_TARGET_2)) { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); - } else { + } else if (!background->extPalette) { DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256Ext, NoBlend, NO_OBJWIN); } } else { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN); - } else { + } else if (!background->extPalette) { DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256Ext, Blend, NO_OBJWIN); } } } else { if (!(flags & FLAG_TARGET_2)) { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); - } else { + } else if (!background->extPalette) { DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256Ext, NoBlend, OBJWIN); } } else { if (!background->multipalette) { DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN); - } else { + } else if (!background->extPalette) { DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256Ext, Blend, OBJWIN); } } } diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 1bb815597..1a41a37eb 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -135,6 +135,8 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) { bg->dmy = 256; bg->sx = 0; bg->sy = 0; + bg->extPalette = NULL; + bg->variantPalette = NULL; } }