all repos — mgba @ 52c66de6945adefa72d87cf5c461af4b30fd1d68

mGBA Game Boy Advance Emulator

Merge branch 'master' into port/wii
Jeffrey Pfau jeffrey@endrift.com
Sat, 20 Jun 2015 03:25:01 -0700
commit

52c66de6945adefa72d87cf5c461af4b30fd1d68

parent

9739f177c76de09233544144d68130337280deb3

M CHANGESCHANGES

@@ -21,6 +21,7 @@ - Ability to cap fast forward speed

- Finer control over FPS target - Holdable shortcut for rewinding one frame at a time - Ability to boot directly into the BIOS + - Preliminary support for yanking out the game pak while a game is running Bugfixes: - ARM7: Fix SWI and IRQ timings - GBA Audio: Force audio FIFOs to 32-bit

@@ -46,6 +47,7 @@ - ARM7: Fix timing of multiplies to use N cycles

- GBA: Fix calls to endian-independent loadstores - GBA Video: Fix windows not affecting sprites - VFS: Fix line-reading to return proper values + - GBA Memory: Fix load/store multiple video memory waitstates Misc: - Qt: Handle saving input settings better - Debugger: Free watchpoints in addition to breakpoints

@@ -76,6 +78,9 @@ - All: Threads are now named

- Qt: Rename "Fullscreen" to "Toggle fullscreen" - Qt: Don't save window size when entering fullscreen - Qt: Make the default fullscreen binding for Windows be Alt-Enter + - GBA Video: Refactor software renderer into separate files + - ARM7: Add emulation for Undefined CPU mode + - GBA: More accurate cycle estimation for ROM prefetch and flash save chips 0.2.1: (2015-05-13) Bugfixes:
M CMakeLists.txtCMakeLists.txt

@@ -23,7 +23,7 @@ file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)

file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) -file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c) +file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/*.c) file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c) file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
A PORTING.md

@@ -0,0 +1,35 @@

+Porting +======= + +Porting is preferentially done upstream so as to avoid fragmenting the codebase into individually unmergeable forks. As such, precaution must be taken to keep changes separate enough to not interfere with other ports, while still maintaining the ability to add new port-specific code seamlessly. + +Folders for each port should be under the `src/platform` folder, and make minimally invasive changes to the rest of the tree. If any changes are needed, try to make sure they are generic and have the ability to be ironed out in the future. For example, if a function doesn't work on a specific platform, maybe a way to make that function more portable should be added. + +The general porting process involves branching `master`, making the needed changes, and, when the port is mature enough to not have major effects to other ports, merged into `port/crucible`. The crucible is used for mixing upcoming ports to make sure they aren't fragile when `master` merges into it every so often. At this time, the crucible hasn't yet been merged into `master`, but in the future this may occur regularly. Until then, if a port is to get merged into master, make sure the changes to each port occur on the port-specific branch before being merged into `port/crucible`. + +Port-specific TODO +------------------ + +The ports are vaguely usable, but by no means should be considered stable. + +### 3DS +* Add menu +* Add audio +* Thread support testing +* Make it faster + * ARMv6 dynarec + * Hardware acceleration + +### PSP +* Add menu +* Add audio +* Thread support +* Make it faster + * MIPS dynarec + * Hardware acceleration + +### Wii +* Add menu +* Add audio +* Thread support +* Clean up video detection
M src/arm/arm.csrc/arm/arm.c

@@ -190,6 +190,26 @@ cpu->cpsr.i = 1;

cpu->cycles += currentCycles; } +void ARMRaiseUndefined(struct ARMCore* cpu) { + union PSR cpsr = cpu->cpsr; + int instructionWidth; + if (cpu->executionMode == MODE_THUMB) { + instructionWidth = WORD_SIZE_THUMB; + } else { + instructionWidth = WORD_SIZE_ARM; + } + ARMSetPrivilegeMode(cpu, MODE_UNDEFINED); + cpu->cpsr.priv = MODE_UNDEFINED; + cpu->gprs[ARM_LR] = cpu->gprs[ARM_PC] - instructionWidth; + cpu->gprs[ARM_PC] = BASE_UNDEF; + int currentCycles = 0; + ARM_WRITE_PC; + _ARMSetMode(cpu, MODE_ARM); + cpu->spsr = cpsr; + cpu->cpsr.i = 1; + cpu->cycles += currentCycles; +} + static inline void ARMStep(struct ARMCore* cpu) { uint32_t opcode = cpu->prefetch[0]; cpu->prefetch[0] = cpu->prefetch[1];
M src/arm/arm.hsrc/arm/arm.h

@@ -172,6 +172,7 @@ void ARMReset(struct ARMCore* cpu);

void ARMSetPrivilegeMode(struct ARMCore*, enum PrivilegeMode); void ARMRaiseIRQ(struct ARMCore*); void ARMRaiseSWI(struct ARMCore*); +void ARMRaiseUndefined(struct ARMCore*); void ARMRun(struct ARMCore* cpu); void ARMRunLoop(struct ARMCore* cpu);
M src/gba/gba.csrc/gba/gba.c

@@ -96,14 +96,22 @@

gba->performingDMA = false; } -void GBADestroy(struct GBA* gba) { +void GBAUnloadROM(struct GBA* gba) { if (gba->pristineRom == gba->memory.rom) { gba->memory.rom = 0; + } else { + mappedMemoryFree(gba->pristineRom, gba->pristineRomSize); } if (gba->romVf) { gba->romVf->unmap(gba->romVf, gba->pristineRom, gba->pristineRomSize); + gba->pristineRom = 0; + gba->romVf = 0; } +} + +void GBADestroy(struct GBA* gba) { + GBAUnloadROM(gba); if (gba->biosVf) { gba->biosVf->unmap(gba->biosVf, gba->memory.bios, SIZE_BIOS);

@@ -139,6 +147,11 @@

struct GBA* gba = (struct GBA*) cpu->master; if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) { GBASavedataUnmask(&gba->memory.savedata); + } + + if (gba->yankedRomSize) { + gba->memory.romSize = gba->yankedRomSize; + gba->yankedRomSize = 0; } GBAMemoryReset(gba); GBAVideoReset(&gba->video);

@@ -365,6 +378,7 @@ gba->cpu->components[GBA_COMPONENT_DEBUGGER] = 0;

} void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) { + GBAUnloadROM(gba); gba->romVf = vf; gba->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET);

@@ -376,6 +390,7 @@ if (!gba->pristineRom) {

GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM"); return; } + gba->yankedRomSize = 0; gba->memory.rom = gba->pristineRom; gba->activeFile = fname; gba->memory.romSize = gba->pristineRomSize;

@@ -385,6 +400,12 @@ GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]);

// TODO: error check } +void GBAYankROM(struct GBA* gba) { + gba->yankedRomSize = gba->memory.romSize; + gba->memory.romSize = 0; + GBARaiseIRQ(gba, IRQ_GAMEPAK); +} + void GBALoadBIOS(struct GBA* gba, struct VFile* vf) { gba->biosVf = vf; uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ);

@@ -489,10 +510,6 @@

void GBAWriteIE(struct GBA* gba, uint16_t value) { if (value & (1 << IRQ_KEYPAD)) { GBALog(gba, GBA_LOG_STUB, "Keypad interrupts not implemented"); - } - - if (value & (1 << IRQ_GAMEPAK)) { - GBALog(gba, GBA_LOG_STUB, "Gamepak interrupts not implemented"); } if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) {

@@ -650,7 +667,7 @@ }

void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) { struct GBA* gba = (struct GBA*) cpu->master; - enum GBALogLevel level = GBA_LOG_FATAL; + enum GBALogLevel level = GBA_LOG_ERROR; if (gba->debugger) { level = GBA_LOG_STUB; struct DebuggerEntryInfo info = {

@@ -664,13 +681,17 @@ }

void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) { struct GBA* gba = (struct GBA*) cpu->master; - GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode); + if (!gba->yankedRomSize) { + GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode); + } if (gba->debugger) { struct DebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), .opcode = opcode }; ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info); + } else { + ARMRaiseUndefined(cpu); } }
M src/gba/gba.hsrc/gba/gba.h

@@ -146,6 +146,7 @@

struct GBARRContext* rr; void* pristineRom; size_t pristineRomSize; + size_t yankedRomSize; uint32_t romCrc32; struct VFile* romVf; struct VFile* biosVf;

@@ -206,6 +207,8 @@ void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);

void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode); void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname); +void GBAYankROM(struct GBA* gba); +void GBAUnloadROM(struct GBA* gba); void GBALoadBIOS(struct GBA* gba, struct VFile* vf); void GBAApplyPatch(struct GBA* gba, struct Patch* patch);
M src/gba/memory.csrc/gba/memory.c

@@ -18,15 +18,15 @@ #define IDLE_LOOP_THRESHOLD 10000

static uint32_t _popcount32(unsigned bits); static void _pristineCow(struct GBA* gba); -static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE }; +static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info); static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 }; -static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 7, 7, 9, 9, 13, 13, 9 }; +static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 9 }; static const char GBA_BASE_WAITSTATES_SEQ[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 }; -static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 5, 5, 9, 9, 17, 17, 9 }; +static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 5, 5, 9, 9, 17, 17, 9 }; static const char GBA_ROM_WAITSTATES[] = { 4, 3, 2, 8 }; static const char GBA_ROM_WAITSTATES_SEQ[] = { 2, 1, 4, 1, 8, 1 }; static const int DMA_OFFSET[] = { 1, -1, 0, 1 };

@@ -270,18 +270,20 @@ break;

} // Fall through default: - memory->activeRegion = 0; + memory->activeRegion = -1; cpu->memory.activeRegion = _deadbeef; cpu->memory.activeMask = 0; - GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); - break; + if (!gba->yankedRomSize) { + GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address"); + } + return; } - cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[memory->activeRegion]; - cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[memory->activeRegion]; - cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[memory->activeRegion]; - cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[memory->activeRegion]; - cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[memory->activeRegion]; - cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[memory->activeRegion]; + cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[newRegion]; + cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[newRegion]; + cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[newRegion]; + cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[newRegion]; + cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[newRegion]; + cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[newRegion]; } #define LOAD_BAD \

@@ -334,7 +336,7 @@ #define LOAD_IO value = GBAIORead(gba, (address & (SIZE_IO - 1)) & ~2) | (GBAIORead(gba, (address & (SIZE_IO - 1)) | 2) << 16);

#define LOAD_PALETTE_RAM \ LOAD_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ - ++wait; + wait += waitstatesRegion[REGION_PALETTE_RAM]; #define LOAD_VRAM \ if ((address & 0x0001FFFF) < SIZE_VRAM) { \

@@ -342,7 +344,7 @@ LOAD_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \

} else { \ LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ } \ - ++wait; + wait += waitstatesRegion[REGION_VRAM]; #define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);

@@ -611,7 +613,7 @@

#define STORE_PALETTE_RAM \ STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \ gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); \ - ++wait; \ + wait += waitstatesRegion[REGION_PALETTE_RAM]; \ gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value); #define STORE_VRAM \

@@ -620,7 +622,7 @@ STORE_32(value, address & 0x0001FFFC, gba->video.renderer->vram); \

} else { \ STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \ } \ - ++wait; + wait += waitstatesRegion[REGION_VRAM]; #define STORE_OAM \ STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); \

@@ -1268,21 +1270,23 @@ memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = memory->waitstatesNonseq32[REGION_CART0];

memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq32[REGION_CART1]; memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq32[REGION_CART2]; } else { - memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = 0; + // Assume it stalls one cycle to pull a value from the prefetch + // This needs more research to tell if it's accurate or not + memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = 1; + memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = 1; + memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = 1; - memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = 0; + memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = 2; + memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = 2; + memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = 2; - memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = 0; + memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = 1; + memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = 1; + memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = 1; - memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = 0; - memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = 0; - memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = 0; + memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = 2; + memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = 2; + memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = 2; } cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[memory->activeRegion];
A src/gba/renderers/software-bg.c

@@ -0,0 +1,195 @@

+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 "software-private.h" + +#include "gba/gba.h" + +void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + int sizeAdjusted = 0x8000 << background->size; + + BACKGROUND_BITMAP_INIT; + + uint32_t screenBase = background->screenBase; + uint32_t charBase = background->charBase; + uint8_t mapData; + uint8_t tileData = 0; + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + x += background->dx; + y += background->dy; + + if (!mosaicWait) { + if (background->overflow) { + localX = x & (sizeAdjusted - 1); + localY = y & (sizeAdjusted - 1); + } else if ((x | y) & ~(sizeAdjusted - 1)) { + continue; + } else { + localX = x; + localY = y; + } + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; + tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; + + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (tileData && IS_WRITABLE(current)) { + if (!objwinSlowPath) { + _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); + } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { + color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint32_t color = renderer->normalPalette[0]; + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + + if (!mosaicWait) { + LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram); +#ifndef COLOR_16_BIT + unsigned color32; + color32 = 0; + color32 |= (color << 3) & 0xF8; + color32 |= (color << 6) & 0xF800; + color32 |= (color << 9) & 0xF80000; + color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; +#endif + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + if (!variant) { + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_BRIGHTEN) { + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_DARKEN) { + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint16_t color = renderer->normalPalette[0]; + uint32_t offset = 0; + if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { + offset = 0xA000; + } + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + + if (!mosaicWait) { + color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS]; + + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (color && IS_WRITABLE(current)) { + if (!objwinSlowPath) { + _compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current); + } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { + color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + _compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current); + } + } + } +} + +void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { + BACKGROUND_BITMAP_INIT; + + uint32_t color = renderer->normalPalette[0]; + uint32_t offset = 0; + if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { + offset = 0xA000; + } + + int outX; + uint32_t* pixel; + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { + BACKGROUND_BITMAP_ITERATE(160, 128); + + if (!mosaicWait) { + LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); +#ifndef COLOR_16_BIT + unsigned color32 = 0; + color32 |= (color << 9) & 0xF80000; + color32 |= (color << 3) & 0xF8; + color32 |= (color << 6) & 0xF800; + color = color32; +#elif COLOR_5_6_5 + uint16_t color16 = 0; + color16 |= (color & 0x001F) << 11; + color16 |= (color & 0x03E0) << 1; + color16 |= (color & 0x7C00) >> 10; + color = color16; +#endif + mosaicWait = mosaicH; + } else { + --mosaicWait; + } + + uint32_t current = *pixel; + if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { + unsigned mergedFlags = flags; + if (current & FLAG_OBJWIN) { + mergedFlags = objwinFlags; + } + if (!variant) { + _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_BRIGHTEN) { + _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); + } else if (renderer->blendEffect == BLEND_DARKEN) { + _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); + } + } + } +}
A src/gba/renderers/software-mode0.c

@@ -0,0 +1,538 @@

+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 "software-private.h" + +#include "gba/gba.h" + +#define BACKGROUND_TEXT_SELECT_CHARACTER \ + localX = tileX * 8 + inX; \ + xBase = localX & 0xF8; \ + if (background->size & 1) { \ + xBase += (localX & 0x100) << 5; \ + } \ + screenBase = yBase + (xBase >> 3); \ + LOAD_16(mapData, screenBase << 1, vram); \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * mod8; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } else { \ + for (outX = end - 1; outX >= renderer->start; --outX) { \ + uint32_t* pixel = &renderer->row[outX]; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + LOAD_32(tileData, charBase, vram); \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + pixel = &renderer->row[outX]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (outX < renderer->start) { \ + tileData >>= 4 * (renderer->start - outX); \ + outX = renderer->start; \ + pixel = &renderer->row[outX]; \ + } \ + for (; outX < renderer->end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + } else { \ + tileData >>= 4 * (0x8 - mod8); \ + int end = renderer->end - 8; \ + if (end < -1) { \ + end = -1; \ + } \ + outX = renderer->end - 1; \ + pixel = &renderer->row[outX]; \ + for (; outX > end; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + } \ + /* Needed for consistency checks */ \ + if (VIDEO_CHECKS) { \ + outX = renderer->end; \ + pixel = &renderer->row[outX]; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \ + x = inX & 7; \ + if (mosaicWait) { \ + int baseX = x - (mosaicH - mosaicWait); \ + if (baseX < 0) { \ + int disturbX = (16 + baseX) >> 3; \ + inX -= disturbX << 3; \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + baseX -= disturbX << 3; \ + inX += disturbX << 3; \ + } else { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + } \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= 4 * baseX; \ + } else { \ + tileData >>= 4 * (7 - baseX); \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 12; \ + tileData |= tileData << 16; \ + tileData |= tileData << 20; \ + tileData |= tileData << 24; \ + tileData |= tileData << 28; \ + carryData = tileData; \ + } \ + } \ + for (; length; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + tileData = carryData; \ + for (; x < 8 && length; ++x, --length) { \ + if (!mosaicWait) { \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + LOAD_32(tileData, charBase, vram); \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + tileData >>= x * 4; \ + } else { \ + tileData >>= (7 - x) * 4; \ + } \ + tileData &= 0xF; \ + tileData |= tileData << 4; \ + tileData |= tileData << 8; \ + tileData |= tileData << 12; \ + tileData |= tileData << 16; \ + tileData |= tileData << 20; \ + tileData |= tileData << 24; \ + tileData |= tileData << 28; \ + carryData = tileData; \ + } \ + mosaicWait = mosaicH; \ + } \ + --mosaicWait; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + } \ + x = 0; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ + palette = &mainPalette[paletteData]; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 7; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ + pixel += 8; \ + } \ + } else { \ + pixel += 8; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + int end2 = end - 4; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + int shift = inX & 0x3; \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > outX) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= 8 * shift; \ + shift = 0; \ + for (; outX < end2; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + } \ + \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= 8 * shift; \ + for (; outX < end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + } else { \ + int start = outX; \ + outX = end - 1; \ + pixel = &renderer->row[outX]; \ + if (LIKELY(charBase < 0x10000)) { \ + if (end2 > start) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= end2; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + charBase += 4; \ + } \ + } \ + \ + if (LIKELY(charBase < 0x10000)) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX >= renderer->start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } \ + outX = end; \ + pixel = &renderer->row[outX]; \ + } + +#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + return; \ + } \ + int end = mod8 - 4; \ + pixel = &renderer->row[outX]; \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (end > 0) { \ + LOAD_32(tileData, charBase, vram); \ + for (; outX < renderer->end - end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + charBase += 4; \ + } \ + \ + LOAD_32(tileData, charBase, vram); \ + for (; outX < renderer->end; ++outX, ++pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + } 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); \ + tileData >>= 8 * shift; \ + for (; outX >= start + 4; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + shift = 0; \ + } \ + \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= 8 * shift; \ + for (; outX >= start; --outX, --pixel) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + /* Needed for consistency checks */ \ + if (VIDEO_CHECKS) { \ + outX = renderer->end; \ + pixel = &renderer->row[outX]; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + if (UNLIKELY(charBase >= 0x10000)) { \ + pixel += 8; \ + continue; \ + } \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 4; \ + } \ + LOAD_32(tileData, charBase + 4, vram); \ + if (tileData) { \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } else { \ + pixel += 4; \ + } \ + } else { \ + LOAD_32(tileData, charBase + 4, vram); \ + if (tileData) { \ + pixel += 3; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + pixel += 4; \ + LOAD_32(tileData, charBase, vram); \ + if (tileData) { \ + pixel += 3; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + --pixel; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + } \ + pixel += 4; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ + for (; tileX < tileEnd; ++tileX) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ + tileData = carryData; \ + for (x = 0; x < 8; ++x) { \ + if (!mosaicWait) { \ + if (UNLIKELY(charBase >= 0x10000)) { \ + carryData = 0; \ + } else { \ + if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (x - 4) * 8; \ + } else { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= x * 8; \ + } \ + } else { \ + if (x >= 4) { \ + LOAD_32(tileData, charBase, vram); \ + tileData >>= (7 - x) * 8; \ + } else { \ + LOAD_32(tileData, charBase + 4, vram); \ + tileData >>= (3 - x) * 8; \ + } \ + } \ + tileData &= 0xFF; \ + carryData = tileData; \ + } \ + mosaicWait = mosaicH; \ + } \ + tileData |= tileData << 8; \ + --mosaicWait; \ + BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ + ++pixel; \ + } \ + } + +#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \ + uint32_t* pixel = &renderer->row[outX]; \ + if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ + int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \ + int x; \ + int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \ + int carryData = 0; \ + paletteData = 0; /* Quiets compiler warning */ \ + DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \ + return; \ + } \ + \ + if (inX & 0x7) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + \ + int mod8 = inX & 0x7; \ + int end = outX + 0x8 - mod8; \ + if (end > renderer->end) { \ + end = renderer->end; \ + } \ + if (UNLIKELY(end == outX)) { \ + return; \ + } \ + if (UNLIKELY(end < outX)) { \ + GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \ + return; \ + } \ + DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ + outX = end; \ + if (tileX < tileEnd) { \ + ++tileX; \ + } else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \ + GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \ + return; \ + } \ + length -= end - renderer->start; \ + } \ + /*! TODO: Make sure these lines can be removed */ \ + /*!*/ pixel = &renderer->row[outX]; \ + outX += (tileEnd - tileX) * 8; \ + /*!*/ if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ + /*!*/ GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \ + /*!*/ return; \ + /*!*/ } \ + DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ + if (length & 0x7) { \ + BACKGROUND_TEXT_SELECT_CHARACTER; \ + \ + int mod8 = length & 0x7; \ + if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ + GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \ + return; \ + } \ + DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \ + } \ + if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \ + GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \ + } \ + if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ + GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \ + return; \ + } + +void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { + int inX = renderer->start + background->x; + int length = renderer->end - renderer->start; + if (background->mosaic) { + int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; + y -= y % mosaicV; + } + int inY = y + background->y; + uint16_t mapData; + + unsigned yBase = inY & 0xF8; + if (background->size == 2) { + yBase += inY & 0x100; + } else if (background->size == 3) { + yBase += (inY & 0x100) << 1; + } + yBase = (background->screenBase >> 1) + (yBase << 2); + + int localX; + int localY; + + unsigned xBase; + + int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; + flags |= FLAG_TARGET_2 * background->target2; + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); + objwinFlags |= flags; + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); + if (renderer->blda == 0x10 && renderer->bldb == 0) { + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + } + + 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* palette = mainPalette; + PREPARE_OBJWIN; + + int outX = renderer->start; + + uint32_t tileData; + uint32_t current; + int pixelData; + int paletteData; + int tileX = 0; + int tileEnd = ((length + inX) >> 3) - (inX >> 3); + uint16_t* vram = renderer->d.vram; + + if (!objwinSlowPath) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN); + } + } else { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN); + } + } + } else { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN); + } + } else { + if (!background->multipalette) { + DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN); + } + } + } +}
A src/gba/renderers/software-obj.c

@@ -0,0 +1,269 @@

+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 "software-private.h" + +#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \ + SPRITE_YBASE_ ## DEPTH(inY); \ + unsigned tileData; \ + for (; outX < condition; ++outX, inX += xOffset) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + SPRITE_XBASE_ ## DEPTH(inX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \ + } + +#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ + SPRITE_YBASE_ ## DEPTH(inY); \ + unsigned tileData; \ + if (outX % mosaicH) { \ + if (!inX && xOffset > 0) { \ + inX = mosaicH - (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } else if (inX == width - xOffset) { \ + inX = mosaicH + (outX % mosaicH); \ + outX += mosaicH - (outX % mosaicH); \ + } \ + } \ + for (; outX < condition; ++outX, inX += xOffset) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + int localX = inX - xOffset * (outX % mosaicH); \ + if (localX < 0 || localX > width - 1) { \ + continue; \ + } \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + +#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ + unsigned tileData; \ + for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ + if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ + continue; \ + } \ + xAccum += mat.a; \ + yAccum += mat.c; \ + int localX = (xAccum >> 8) + (width >> 1); \ + int localY = (yAccum >> 8) + (height >> 1); \ + \ + if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ + continue; \ + } \ + \ + SPRITE_YBASE_ ## DEPTH(localY); \ + SPRITE_XBASE_ ## DEPTH(localX); \ + SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ + } + +#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); +#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4; + +#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + +#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + if (tileData) { \ + renderer->row[outX] |= FLAG_OBJWIN; \ + } + +#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); +#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; + +#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + +#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + if (tileData) { \ + renderer->row[outX] |= FLAG_OBJWIN; \ + } + +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { + int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0]; + int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1]; + int start = renderer->start; + int end = renderer->end; + uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; + flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); + flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN); + int32_t x = GBAObjAttributesBGetX(sprite->b) << 23; + x >>= 23; + uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; + unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; + int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); + if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { + int target2 = renderer->target2Bd << 4; + target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); + target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); + target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); + target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); + if (GBAObjAttributesCGetPriority(sprite->c) < target2) { + variant = 0; + } + } + color_t* palette = &renderer->normalPalette[0x100]; + if (variant) { + palette = &renderer->variantPalette[0x100]; + } + + int inY = y - (int) GBAObjAttributesAGetY(sprite->a); + + uint32_t current; + if (GBAObjAttributesAIsTransformed(sprite->a)) { + int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); + int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); + struct GBAOAMMatrix mat; + LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); + LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b); + LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c); + LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d); + + if (inY < 0) { + inY += 256; + } + int outX = x >= start ? x : start; + int inX = outX - x; + int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)); + int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)); + + if (!GBAObjAttributesAIs256Color(sprite->a)) { + palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + if (flags & FLAG_OBJWIN) { + SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else { + SPRITE_TRANSFORMED_LOOP(16, NORMAL); + } + } else { + if (flags & FLAG_OBJWIN) { + SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else { + SPRITE_TRANSFORMED_LOOP(256, NORMAL); + } + } + } else { + int outX = x >= start ? x : start; + int condition = x + width; + int mosaicH = 1; + if (GBAObjAttributesAIsMosaic(sprite->a)) { + mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; + if (condition % mosaicH) { + condition += mosaicH - (condition % mosaicH); + } + } + if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) { + inY += 256; + } + if (GBAObjAttributesBIsVFlip(sprite->b)) { + inY = height - inY - 1; + } + if (end < condition) { + condition = end; + } + int inX = outX - x; + int xOffset = 1; + if (GBAObjAttributesBIsHFlip(sprite->b)) { + inX = width - inX - 1; + xOffset = -1; + } + if (!GBAObjAttributesAIs256Color(sprite->a)) { + palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + if (flags & FLAG_OBJWIN) { + SPRITE_NORMAL_LOOP(16, OBJWIN); + } else if (GBAObjAttributesAIsMosaic(sprite->a)) { + SPRITE_MOSAIC_LOOP(16, NORMAL); + } else { + SPRITE_NORMAL_LOOP(16, NORMAL); + } + } else { + if (flags & FLAG_OBJWIN) { + SPRITE_NORMAL_LOOP(256, OBJWIN); + } else if (GBAObjAttributesAIsMosaic(sprite->a)) { + SPRITE_MOSAIC_LOOP(256, NORMAL); + } else { + SPRITE_NORMAL_LOOP(256, NORMAL); + } + } + } + return 1; +} + +void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) { + int x; + uint32_t* pixel = &renderer->row[renderer->start]; + uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj; + + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); + bool objwinDisable = false; + bool objwinOnly = false; + if (objwinSlowPath) { + objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed); + objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed); + if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; + } + + if (objwinDisable) { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } else if (objwinOnly) { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } else { + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendObjwin(renderer, pixel, color | flags, current); + } + } + return; + } + } else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { + return; + } + for (x = renderer->start; x < renderer->end; ++x, ++pixel) { + uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; + uint32_t current = *pixel; + if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { + _compositeBlendNoObjwin(renderer, pixel, color | flags, current); + } + } +}
A src/gba/renderers/software-private.h

@@ -0,0 +1,320 @@

+/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ +#ifndef SOFTWARE_PRIVATE_H +#define SOFTWARE_PRIVATE_H + +#include "video-software.h" + +#ifdef NDEBUG +#define VIDEO_CHECKS false +#else +#define VIDEO_CHECKS true +#endif + +void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); +void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); + +int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); +void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); + +static inline unsigned _brighten(unsigned color, int y); +static inline unsigned _darken(unsigned color, int y); +static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB); + + +// We stash the priority on the top bits so we can do a one-operator comparison +// The lower the number, the higher the priority, and sprites take precendence over backgrounds +// We want to do special processing if the color pixel is target 1, however + +static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + if (color >= current) { + if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { + color = _mix(renderer->blda, current, renderer->bldb, color); + } else { + color = current & 0x00FFFFFF; + } + } else { + color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); + } + *pixel = color; +} + +static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + if (color >= current) { + if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { + color = _mix(renderer->blda, current, renderer->bldb, color); + } else { + color = current & 0x00FFFFFF; + } + } else { + color = color & ~FLAG_TARGET_2; + } + *pixel = color; +} + +static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + UNUSED(renderer); + if (color < current) { + *pixel = color | (current & FLAG_OBJWIN); + } +} + +static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { + UNUSED(renderer); + if (color < current) { + *pixel = color; + } +} + +#define COMPOSITE_16_OBJWIN(BLEND) \ + if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ + unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ + } + +#define COMPOSITE_16_NO_OBJWIN(BLEND) \ + _composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current); + +#define COMPOSITE_256_OBJWIN(BLEND) \ + if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ + unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \ + unsigned mergedFlags = flags; \ + if (current & FLAG_OBJWIN) { \ + mergedFlags = objwinFlags; \ + } \ + _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ + } + +#define COMPOSITE_256_NO_OBJWIN(BLEND) \ + COMPOSITE_16_NO_OBJWIN(BLEND) + +#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \ + pixelData = tileData & 0xF; \ + current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_16_ ## OBJWIN (BLEND); \ + } \ + tileData >>= 4; + +#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \ + pixelData = tileData & 0xFF; \ + current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_256_ ## OBJWIN (BLEND); \ + } \ + tileData >>= 8; + +// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5 +#define PREPARE_OBJWIN \ + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \ + int objwinOnly = 0; \ + int objwinForceEnable = 0; \ + UNUSED(objwinForceEnable); \ + color_t* objwinPalette = renderer->normalPalette; \ + UNUSED(objwinPalette); \ + if (objwinSlowPath) { \ + if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ + objwinPalette = renderer->variantPalette; \ + } \ + switch (background->index) { \ + case 0: \ + objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \ + break; \ + case 1: \ + objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \ + break; \ + case 2: \ + objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \ + break; \ + case 3: \ + objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \ + objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \ + break; \ + } \ + } + +#define BACKGROUND_BITMAP_INIT \ + int32_t x = background->sx + (renderer->start - 1) * background->dx; \ + int32_t y = background->sy + (renderer->start - 1) * background->dy; \ + int mosaicH = 0; \ + int mosaicWait = 0; \ + if (background->mosaic) { \ + int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \ + y -= (inY % mosaicV) * background->dmy; \ + x -= (inY % mosaicV) * background->dmx; \ + mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \ + mosaicWait = renderer->start % (mosaicH + 1); \ + } \ + int32_t localX; \ + int32_t localY; \ + \ + int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \ + flags |= FLAG_TARGET_2 * background->target2; \ + int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \ + objwinFlags |= flags; \ + flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ + if (renderer->blda == 0x10 && renderer->bldb == 0) { \ + flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + } \ + int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ + color_t* palette = renderer->normalPalette; \ + if (variant) { \ + palette = renderer->variantPalette; \ + } \ + UNUSED(palette); \ + PREPARE_OBJWIN; + +#define BACKGROUND_BITMAP_ITERATE(W, H) \ + x += background->dx; \ + y += background->dy; \ + \ + if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \ + continue; \ + } else { \ + localX = x; \ + localY = y; \ + } + +static inline unsigned _brighten(unsigned color, int y) { + unsigned c = 0; + unsigned a; +#ifdef COLOR_16_BIT + a = color & 0x1F; + c |= (a + ((0x1F - a) * y) / 16) & 0x1F; + +#ifdef COLOR_5_6_5 + a = color & 0x7C0; + c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0; + + a = color & 0xF800; + c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; +#else + a = color & 0x3E0; + c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0; + + a = color & 0x7C00; + c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00; +#endif +#else + a = color & 0xF8; + c |= (a + ((0xF8 - a) * y) / 16) & 0xF8; + + a = color & 0xF800; + c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; + + a = color & 0xF80000; + c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000; +#endif + return c; +} + +static inline unsigned _darken(unsigned color, int y) { + unsigned c = 0; + unsigned a; +#ifdef COLOR_16_BIT + a = color & 0x1F; + c |= (a - (a * y) / 16) & 0x1F; + +#ifdef COLOR_5_6_5 + a = color & 0x7C0; + c |= (a - (a * y) / 16) & 0x7C0; + + a = color & 0xF800; + c |= (a - (a * y) / 16) & 0xF800; +#else + a = color & 0x3E0; + c |= (a - (a * y) / 16) & 0x3E0; + + a = color & 0x7C00; + c |= (a - (a * y) / 16) & 0x7C00; +#endif +#else + a = color & 0xF8; + c |= (a - (a * y) / 16) & 0xF8; + + a = color & 0xF800; + c |= (a - (a * y) / 16) & 0xF800; + + a = color & 0xF80000; + c |= (a - (a * y) / 16) & 0xF80000; +#endif + return c; +} + +static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) { + unsigned c = 0; + unsigned a, b; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + a = colorA & 0xF81F; + b = colorB & 0xF81F; + a |= (colorA & 0x7C0) << 16; + b |= (colorB & 0x7C0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x08000000) { + c = (c & ~0x0FC00000) | 0x07C00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0xF81F) | ((c >> 16) & 0x07C0); +#else + a = colorA & 0x7C1F; + b = colorB & 0x7C1F; + a |= (colorA & 0x3E0) << 16; + b |= (colorB & 0x3E0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x04000000) { + c = (c & ~0x07E00000) | 0x03E00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); +#endif +#else + a = colorA & 0xF8; + b = colorB & 0xF8; + c |= ((a * weightA + b * weightB) / 16) & 0x1F8; + if (c & 0x00000100) { + c = 0x000000F8; + } + + a = colorA & 0xF800; + b = colorB & 0xF800; + c |= ((a * weightA + b * weightB) / 16) & 0x1F800; + if (c & 0x00010000) { + c = (c & 0x000000F8) | 0x0000F800; + } + + a = colorA & 0xF80000; + b = colorB & 0xF80000; + c |= ((a * weightA + b * weightB) / 16) & 0x1F80000; + if (c & 0x01000000) { + c = (c & 0x0000F8F8) | 0x00F80000; + } +#endif + return c; +} + +#endif
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -3,38 +3,13 @@ *

* This Source Code Form is subject to the terms of the Mozilla Public * 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 "video-software.h" +#include "software-private.h" #include "gba/gba.h" #include "gba/io.h" #include "util/arm-algo.h" -#ifdef NDEBUG -#define VIDEO_CHECKS false -#else -#define VIDEO_CHECKS true -#endif - -static const int _objSizes[32] = { - 8, 8, - 16, 16, - 32, 32, - 64, 64, - 16, 8, - 32, 8, - 32, 16, - 64, 32, - 8, 16, - 8, 32, - 16, 32, - 32, 64, - 0, 0, - 0, 0, - 0, 0, - 0, 0 -}; - static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer); static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);

@@ -58,20 +33,10 @@ static void GBAVideoSoftwareRendererWriteBGY_LO(struct GBAVideoSoftwareBackground* bg, uint16_t value);

static void GBAVideoSoftwareRendererWriteBGY_HI(struct GBAVideoSoftwareBackground* bg, uint16_t value); static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* renderer, uint16_t value); -static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y); -static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); -static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y); static void _cleanOAM(struct GBAVideoSoftwareRenderer* renderer); -static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y); -static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority); +static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y); static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer); -static inline unsigned _brighten(unsigned color, int y); -static inline unsigned _darken(unsigned color, int y); -static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB); static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y); static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win);

@@ -477,7 +442,7 @@ LOAD_16(obj.a, 0, &renderer->d.oam->obj[i].a);

LOAD_16(obj.b, 0, &renderer->d.oam->obj[i].b); LOAD_16(obj.c, 0, &renderer->d.oam->obj[i].c); if (GBAObjAttributesAIsTransformed(obj.a) || !GBAObjAttributesAIsDisable(obj.a)) { - int height = _objSizes[GBAObjAttributesAGetShape(obj.a) * 8 + GBAObjAttributesBGetSize(obj.b) * 2 + 1]; + int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(obj.a) * 4 + GBAObjAttributesBGetSize(obj.b)][1]; if (GBAObjAttributesAIsTransformed(obj.a)) { height <<= GBAObjAttributesAGetDoubleSize(obj.a); }

@@ -492,7 +457,6 @@ }

renderer->oamMax = oamMax; renderer->oamDirty = 0; } - static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) { struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;

@@ -723,7 +687,7 @@ }

if ((localY < sprite->y && (sprite->endY - 256 < 0 || localY >= sprite->endY - 256)) || localY >= sprite->endY) { continue; } - drawn = _preprocessSprite(renderer, &sprite->obj, localY); + drawn = GBAVideoSoftwareRendererPreprocessSprite(renderer, &sprite->obj, localY); spriteLayers |= drawn << GBAObjAttributesCGetPriority(sprite->obj.c); } }

@@ -737,41 +701,41 @@ renderer->start = renderer->end;

renderer->end = renderer->windows[w].endX; renderer->currentWindow = renderer->windows[w].control; if (spriteLayers & (1 << priority)) { - _postprocessSprite(renderer, priority); + GBAVideoSoftwareRendererPostprocessSprite(renderer, priority); } if (TEST_LAYER_ENABLED(0) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) { - _drawBackgroundMode0(renderer, &renderer->bg[0], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[0], y); } if (TEST_LAYER_ENABLED(1) && GBARegisterDISPCNTGetMode(renderer->dispcnt) < 2) { - _drawBackgroundMode0(renderer, &renderer->bg[1], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[1], y); } if (TEST_LAYER_ENABLED(2)) { switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) { case 0: - _drawBackgroundMode0(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[2], y); break; case 1: case 2: - _drawBackgroundMode2(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[2], y); break; case 3: - _drawBackgroundMode3(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode3(renderer, &renderer->bg[2], y); break; case 4: - _drawBackgroundMode4(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode4(renderer, &renderer->bg[2], y); break; case 5: - _drawBackgroundMode5(renderer, &renderer->bg[2], y); + GBAVideoSoftwareRendererDrawBackgroundMode5(renderer, &renderer->bg[2], y); break; } } if (TEST_LAYER_ENABLED(3)) { switch (GBARegisterDISPCNTGetMode(renderer->dispcnt)) { case 0: - _drawBackgroundMode0(renderer, &renderer->bg[3], y); + GBAVideoSoftwareRendererDrawBackgroundMode0(renderer, &renderer->bg[3], y); break; case 2: - _drawBackgroundMode2(renderer, &renderer->bg[3], y); + GBAVideoSoftwareRendererDrawBackgroundMode2(renderer, &renderer->bg[3], y); break; } }

@@ -783,1150 +747,6 @@ renderer->bg[3].sx += renderer->bg[3].dmx;

renderer->bg[3].sy += renderer->bg[3].dmy; } -// We stash the priority on the top bits so we can do a one-operator comparison -// The lower the number, the higher the priority, and sprites take precendence over backgrounds -// We want to do special processing if the color pixel is target 1, however - -static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - if (color >= current) { - if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { - color = _mix(renderer->blda, current, renderer->bldb, color); - } else { - color = current & 0x00FFFFFF; - } - } else { - color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); - } - *pixel = color; -} - -static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - // We stash the priority on the top bits so we can do a one-operator comparison - // The lower the number, the higher the priority, and sprites take precendence over backgrounds - // We want to do special processing if the color pixel is target 1, however - if (color >= current) { - if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { - color = _mix(renderer->blda, current, renderer->bldb, color); - } else { - color = current & 0x00FFFFFF; - } - } else { - color = color & ~FLAG_TARGET_2; - } - *pixel = color; -} - -static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - UNUSED(renderer); - if (color < current) { - *pixel = color | (current & FLAG_OBJWIN); - } -} - -static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { - UNUSED(renderer); - if (color < current) { - *pixel = color; - } -} - -#define COMPOSITE_16_OBJWIN(BLEND) \ - if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ - unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \ - unsigned mergedFlags = flags; \ - if (current & FLAG_OBJWIN) { \ - mergedFlags = objwinFlags; \ - } \ - _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ - } - -#define COMPOSITE_16_NO_OBJWIN(BLEND) \ - _composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current); - -#define COMPOSITE_256_OBJWIN(BLEND) \ - if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \ - unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \ - unsigned mergedFlags = flags; \ - if (current & FLAG_OBJWIN) { \ - mergedFlags = objwinFlags; \ - } \ - _composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \ - } - -#define COMPOSITE_256_NO_OBJWIN(BLEND) \ - COMPOSITE_16_NO_OBJWIN(BLEND) - -#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \ - pixelData = tileData & 0xF; \ - current = *pixel; \ - if (pixelData && IS_WRITABLE(current)) { \ - COMPOSITE_16_ ## OBJWIN (BLEND); \ - } \ - tileData >>= 4; - -#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \ - pixelData = tileData & 0xFF; \ - current = *pixel; \ - if (pixelData && IS_WRITABLE(current)) { \ - COMPOSITE_256_ ## OBJWIN (BLEND); \ - } \ - tileData >>= 8; - -#define BACKGROUND_TEXT_SELECT_CHARACTER \ - localX = tileX * 8 + inX; \ - xBase = localX & 0xF8; \ - if (background->size & 1) { \ - xBase += (localX & 0x100) << 5; \ - } \ - screenBase = yBase + (xBase >> 3); \ - LOAD_16(mapData, screenBase << 1, vram); \ - localY = inY & 0x7; \ - if (GBA_TEXT_MAP_VFLIP(mapData)) { \ - localY = 7 - localY; \ - } - -// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5 -#define PREPARE_OBJWIN \ - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \ - int objwinOnly = 0; \ - int objwinForceEnable = 0; \ - UNUSED(objwinForceEnable); \ - color_t* objwinPalette = renderer->normalPalette; \ - UNUSED(objwinPalette); \ - if (objwinSlowPath) { \ - if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \ - objwinPalette = renderer->variantPalette; \ - } \ - switch (background->index) { \ - case 0: \ - objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \ - break; \ - case 1: \ - objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \ - break; \ - case 2: \ - objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \ - break; \ - case 3: \ - objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \ - objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \ - break; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= 4 * mod8; \ - for (; outX < end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } else { \ - for (outX = end - 1; outX >= renderer->start; --outX) { \ - uint32_t* pixel = &renderer->row[outX]; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - LOAD_32(tileData, charBase, vram); \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - pixel = &renderer->row[outX]; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (outX < renderer->start) { \ - tileData >>= 4 * (renderer->start - outX); \ - outX = renderer->start; \ - pixel = &renderer->row[outX]; \ - } \ - for (; outX < renderer->end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - } else { \ - tileData >>= 4 * (0x8 - mod8); \ - int end = renderer->end - 8; \ - if (end < -1) { \ - end = -1; \ - } \ - outX = renderer->end - 1; \ - pixel = &renderer->row[outX]; \ - for (; outX > end; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - } \ - /* Needed for consistency checks */ \ - if (VIDEO_CHECKS) { \ - outX = renderer->end; \ - pixel = &renderer->row[outX]; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \ - x = inX & 7; \ - if (mosaicWait) { \ - int baseX = x - (mosaicH - mosaicWait); \ - if (baseX < 0) { \ - int disturbX = (16 + baseX) >> 3; \ - inX -= disturbX << 3; \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - baseX -= disturbX << 3; \ - inX += disturbX << 3; \ - } else { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - } \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= 4 * baseX; \ - } else { \ - tileData >>= 4 * (7 - baseX); \ - } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ - } \ - } \ - for (; length; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - tileData = carryData; \ - for (; x < 8 && length; ++x, --length) { \ - if (!mosaicWait) { \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - LOAD_32(tileData, charBase, vram); \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - tileData >>= x * 4; \ - } else { \ - tileData >>= (7 - x) * 4; \ - } \ - tileData &= 0xF; \ - tileData |= tileData << 4; \ - tileData |= tileData << 8; \ - tileData |= tileData << 12; \ - tileData |= tileData << 16; \ - tileData |= tileData << 20; \ - tileData |= tileData << 24; \ - tileData |= tileData << 28; \ - carryData = tileData; \ - } \ - mosaicWait = mosaicH; \ - } \ - --mosaicWait; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - } \ - x = 0; \ - } - -#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ - palette = &mainPalette[paletteData]; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - pixel += 8; \ - continue; \ - } \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 7; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \ - pixel += 8; \ - } \ - } else { \ - pixel += 8; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - int end2 = end - 4; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - int shift = inX & 0x3; \ - if (LIKELY(charBase < 0x10000)) { \ - if (end2 > outX) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= 8 * shift; \ - shift = 0; \ - for (; outX < end2; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - } \ - \ - if (LIKELY(charBase < 0x10000)) { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= 8 * shift; \ - for (; outX < end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - } else { \ - int start = outX; \ - outX = end - 1; \ - pixel = &renderer->row[outX]; \ - if (LIKELY(charBase < 0x10000)) { \ - if (end2 > start) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= end2; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - charBase += 4; \ - } \ - } \ - \ - if (LIKELY(charBase < 0x10000)) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX >= renderer->start; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } \ - outX = end; \ - pixel = &renderer->row[outX]; \ - } - -#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - return; \ - } \ - int end = mod8 - 4; \ - pixel = &renderer->row[outX]; \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (end > 0) { \ - LOAD_32(tileData, charBase, vram); \ - for (; outX < renderer->end - end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - charBase += 4; \ - } \ - \ - LOAD_32(tileData, charBase, vram); \ - for (; outX < renderer->end; ++outX, ++pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - } 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); \ - tileData >>= 8 * shift; \ - for (; outX >= start + 4; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - shift = 0; \ - } \ - \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= 8 * shift; \ - for (; outX >= start; --outX, --pixel) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - /* Needed for consistency checks */ \ - if (VIDEO_CHECKS) { \ - outX = renderer->end; \ - pixel = &renderer->row[outX]; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - if (UNLIKELY(charBase >= 0x10000)) { \ - pixel += 8; \ - continue; \ - } \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 4; \ - } \ - LOAD_32(tileData, charBase + 4, vram); \ - if (tileData) { \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } else { \ - pixel += 4; \ - } \ - } else { \ - LOAD_32(tileData, charBase + 4, vram); \ - if (tileData) { \ - pixel += 3; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - pixel += 4; \ - LOAD_32(tileData, charBase, vram); \ - if (tileData) { \ - pixel += 3; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - --pixel; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - } \ - pixel += 4; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ - for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ - tileData = carryData; \ - for (x = 0; x < 8; ++x) { \ - if (!mosaicWait) { \ - if (UNLIKELY(charBase >= 0x10000)) { \ - carryData = 0; \ - } else { \ - if (!GBA_TEXT_MAP_HFLIP(mapData)) { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (x - 4) * 8; \ - } else { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= x * 8; \ - } \ - } else { \ - if (x >= 4) { \ - LOAD_32(tileData, charBase, vram); \ - tileData >>= (7 - x) * 8; \ - } else { \ - LOAD_32(tileData, charBase + 4, vram); \ - tileData >>= (3 - x) * 8; \ - } \ - } \ - tileData &= 0xFF; \ - carryData = tileData; \ - } \ - mosaicWait = mosaicH; \ - } \ - tileData |= tileData << 8; \ - --mosaicWait; \ - BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \ - ++pixel; \ - } \ - } - -#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \ - uint32_t* pixel = &renderer->row[outX]; \ - if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \ - int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \ - int x; \ - int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \ - int carryData = 0; \ - paletteData = 0; /* Quiets compiler warning */ \ - DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \ - return; \ - } \ - \ - if (inX & 0x7) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ - int mod8 = inX & 0x7; \ - int end = outX + 0x8 - mod8; \ - if (end > renderer->end) { \ - end = renderer->end; \ - } \ - if (UNLIKELY(end == outX)) { \ - return; \ - } \ - if (UNLIKELY(end < outX)) { \ - GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \ - return; \ - } \ - DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ - outX = end; \ - if (tileX < tileEnd) { \ - ++tileX; \ - } else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \ - GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \ - return; \ - } \ - length -= end - renderer->start; \ - } \ - /*! TODO: Make sure these lines can be removed */ \ - /*!*/ pixel = &renderer->row[outX]; \ - outX += (tileEnd - tileX) * 8; \ - /*!*/ if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ - /*!*/ GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \ - /*!*/ return; \ - /*!*/ } \ - DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ - if (length & 0x7) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ - int mod8 = length & 0x7; \ - if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ - GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \ - return; \ - } \ - DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \ - } \ - if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \ - GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \ - } \ - if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \ - GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \ - return; \ - } - -static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { - int inX = renderer->start + background->x; - int length = renderer->end - renderer->start; - if (background->mosaic) { - int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; - y -= y % mosaicV; - } - int inY = y + background->y; - uint16_t mapData; - - unsigned yBase = inY & 0xF8; - if (background->size == 2) { - yBase += inY & 0x100; - } else if (background->size == 3) { - yBase += (inY & 0x100) << 1; - } - yBase = (background->screenBase >> 1) + (yBase << 2); - - int localX; - int localY; - - unsigned xBase; - - int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; - flags |= FLAG_TARGET_2 * background->target2; - int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); - objwinFlags |= flags; - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); - if (renderer->blda == 0x10 && renderer->bldb == 0) { - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } - - 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* palette = mainPalette; - PREPARE_OBJWIN; - - int outX = renderer->start; - - uint32_t tileData; - uint32_t current; - int pixelData; - int paletteData; - int tileX = 0; - int tileEnd = ((length + inX) >> 3) - (inX >> 3); - uint16_t* vram = renderer->d.vram; - - if (!objwinSlowPath) { - if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN); - } - } else { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN); - } - } - } else { - if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN); - } - } else { - if (!background->multipalette) { - DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN); - } else { - DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN); - } - } - } -} - -#define BACKGROUND_BITMAP_INIT \ - int32_t x = background->sx + (renderer->start - 1) * background->dx; \ - int32_t y = background->sy + (renderer->start - 1) * background->dy; \ - int mosaicH = 0; \ - int mosaicWait = 0; \ - if (background->mosaic) { \ - int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \ - y -= (inY % mosaicV) * background->dmy; \ - x -= (inY % mosaicV) * background->dmx; \ - mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \ - mosaicWait = renderer->start % (mosaicH + 1); \ - } \ - int32_t localX; \ - int32_t localY; \ - \ - int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \ - flags |= FLAG_TARGET_2 * background->target2; \ - int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \ - objwinFlags |= flags; \ - flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ - if (renderer->blda == 0x10 && renderer->bldb == 0) { \ - flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ - } \ - int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \ - color_t* palette = renderer->normalPalette; \ - if (variant) { \ - palette = renderer->variantPalette; \ - } \ - UNUSED(palette); \ - PREPARE_OBJWIN; - -#define BACKGROUND_BITMAP_ITERATE(W, H) \ - x += background->dx; \ - y += background->dy; \ - \ - if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \ - continue; \ - } else { \ - localX = x; \ - localY = y; \ - } - -static void _drawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - int sizeAdjusted = 0x8000 << background->size; - - BACKGROUND_BITMAP_INIT; - - uint32_t screenBase = background->screenBase; - uint32_t charBase = background->charBase; - uint8_t mapData; - uint8_t tileData = 0; - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - x += background->dx; - y += background->dy; - - if (!mosaicWait) { - if (background->overflow) { - localX = x & (sizeAdjusted - 1); - localY = y & (sizeAdjusted - 1); - } else if ((x | y) & ~(sizeAdjusted - 1)) { - continue; - } else { - localX = x; - localY = y; - } - mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; - tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; - - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (tileData && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint32_t color = renderer->normalPalette[0]; - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - - if (!mosaicWait) { - LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram); -#ifndef COLOR_16_BIT - unsigned color32; - color32 = 0; - color32 |= (color << 3) & 0xF8; - color32 |= (color << 6) & 0xF800; - color32 |= (color << 9) & 0xF80000; - color = color32; -#elif COLOR_5_6_5 - uint16_t color16 = 0; - color16 |= (color & 0x001F) << 11; - color16 |= (color & 0x03E0) << 1; - color16 |= (color & 0x7C00) >> 10; - color = color16; -#endif - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint16_t color = renderer->normalPalette[0]; - uint32_t offset = 0; - if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { - offset = 0xA000; - } - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - - if (!mosaicWait) { - color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS]; - - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (color && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current); - } - } - } -} - -static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { - BACKGROUND_BITMAP_INIT; - - uint32_t color = renderer->normalPalette[0]; - uint32_t offset = 0; - if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) { - offset = 0xA000; - } - - int outX; - uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - BACKGROUND_BITMAP_ITERATE(160, 128); - - if (!mosaicWait) { - LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram); -#ifndef COLOR_16_BIT - unsigned color32 = 0; - color32 |= (color << 9) & 0xF80000; - color32 |= (color << 3) & 0xF8; - color32 |= (color << 6) & 0xF800; - color = color32; -#elif COLOR_5_6_5 - uint16_t color16 = 0; - color16 |= (color & 0x001F) << 11; - color16 |= (color & 0x03E0) << 1; - color16 |= (color & 0x7C00) >> 10; - color = color16; -#endif - mosaicWait = mosaicH; - } else { - --mosaicWait; - } - - uint32_t current = *pixel; - if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) { - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - if (!variant) { - _compositeBlendObjwin(renderer, pixel, color | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_BRIGHTEN) { - _compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current); - } else if (renderer->blendEffect == BLEND_DARKEN) { - _compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current); - } - } - } -} - -#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \ - SPRITE_YBASE_ ## DEPTH(inY); \ - unsigned tileData; \ - for (; outX < condition; ++outX, inX += xOffset) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - SPRITE_XBASE_ ## DEPTH(inX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \ - } - -#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \ - SPRITE_YBASE_ ## DEPTH(inY); \ - unsigned tileData; \ - if (outX % mosaicH) { \ - if (!inX && xOffset > 0) { \ - inX = mosaicH - (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } else if (inX == width - xOffset) { \ - inX = mosaicH + (outX % mosaicH); \ - outX += mosaicH - (outX % mosaicH); \ - } \ - } \ - for (; outX < condition; ++outX, inX += xOffset) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - int localX = inX - xOffset * (outX % mosaicH); \ - if (localX < 0 || localX > width - 1) { \ - continue; \ - } \ - SPRITE_XBASE_ ## DEPTH(localX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ - } - -#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ - unsigned tileData; \ - for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ - if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ - continue; \ - } \ - xAccum += mat.a; \ - yAccum += mat.c; \ - int localX = (xAccum >> 8) + (width >> 1); \ - int localY = (yAccum >> 8) + (height >> 1); \ - \ - if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ - continue; \ - } \ - \ - SPRITE_YBASE_ ## DEPTH(localY); \ - SPRITE_XBASE_ ## DEPTH(localX); \ - SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \ - } - -#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2); -#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4; - -#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ - current = renderer->spriteLayer[outX]; \ - if ((current & FLAG_ORDER_MASK) > flags) { \ - if (tileData) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ - } else if (current != FLAG_UNWRITTEN) { \ - renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ - } \ - } - -#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ - if (tileData) { \ - renderer->row[outX] |= FLAG_OBJWIN; \ - } - -#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); -#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; - -#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ - current = renderer->spriteLayer[outX]; \ - if ((current & FLAG_ORDER_MASK) > flags) { \ - if (tileData) { \ - renderer->spriteLayer[outX] = palette[tileData] | flags; \ - } else if (current != FLAG_UNWRITTEN) { \ - renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ - } \ - } - -#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \ - LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ - tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ - if (tileData) { \ - renderer->row[outX] |= FLAG_OBJWIN; \ - } - -static int _preprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) { - int width = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2]; - int height = _objSizes[GBAObjAttributesAGetShape(sprite->a) * 8 + GBAObjAttributesBGetSize(sprite->b) * 2 + 1]; - int start = renderer->start; - int end = renderer->end; - uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; - flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT); - flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN); - int32_t x = GBAObjAttributesBGetX(sprite->b) << 23; - x >>= 23; - uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; - unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; - int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { - int target2 = renderer->target2Bd << 4; - target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); - target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); - target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); - target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if (GBAObjAttributesCGetPriority(sprite->c) < target2) { - variant = 0; - } - } - color_t* palette = &renderer->normalPalette[0x100]; - if (variant) { - palette = &renderer->variantPalette[0x100]; - } - - int inY = y - (int) GBAObjAttributesAGetY(sprite->a); - - uint32_t current; - if (GBAObjAttributesAIsTransformed(sprite->a)) { - int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); - int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); - struct GBAOAMMatrix mat; - LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a); - LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b); - LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c); - LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d); - - if (inY < 0) { - inY += 256; - } - int outX = x >= start ? x : start; - int inX = outX - x; - int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1)); - int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1)); - - if (!GBAObjAttributesAIs256Color(sprite->a)) { - palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; - if (flags & FLAG_OBJWIN) { - SPRITE_TRANSFORMED_LOOP(16, OBJWIN); - } else { - SPRITE_TRANSFORMED_LOOP(16, NORMAL); - } - } else { - if (flags & FLAG_OBJWIN) { - SPRITE_TRANSFORMED_LOOP(256, OBJWIN); - } else { - SPRITE_TRANSFORMED_LOOP(256, NORMAL); - } - } - } else { - int outX = x >= start ? x : start; - int condition = x + width; - int mosaicH = 1; - if (GBAObjAttributesAIsMosaic(sprite->a)) { - mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1; - if (condition % mosaicH) { - condition += mosaicH - (condition % mosaicH); - } - } - if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) { - inY += 256; - } - if (GBAObjAttributesBIsVFlip(sprite->b)) { - inY = height - inY - 1; - } - if (end < condition) { - condition = end; - } - int inX = outX - x; - int xOffset = 1; - if (GBAObjAttributesBIsHFlip(sprite->b)) { - inX = width - inX - 1; - xOffset = -1; - } - if (!GBAObjAttributesAIs256Color(sprite->a)) { - palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; - if (flags & FLAG_OBJWIN) { - SPRITE_NORMAL_LOOP(16, OBJWIN); - } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(16, NORMAL); - } else { - SPRITE_NORMAL_LOOP(16, NORMAL); - } - } else { - if (flags & FLAG_OBJWIN) { - SPRITE_NORMAL_LOOP(256, OBJWIN); - } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(256, NORMAL); - } else { - SPRITE_NORMAL_LOOP(256, NORMAL); - } - } - } - return 1; -} - -static void _postprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) { - int x; - uint32_t* pixel = &renderer->row[renderer->start]; - uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj; - - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); - bool objwinDisable = false; - bool objwinOnly = false; - if (objwinSlowPath) { - objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed); - objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed); - if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { - return; - } - - if (objwinDisable) { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } else if (objwinOnly) { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } else { - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendObjwin(renderer, pixel, color | flags, current); - } - } - return; - } - } else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) { - return; - } - for (x = renderer->start; x < renderer->end; ++x, ++pixel) { - uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN; - uint32_t current = *pixel; - if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) { - _compositeBlendNoObjwin(renderer, pixel, color | flags, current); - } - } -} - static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) { int i; if (renderer->blendEffect == BLEND_BRIGHTEN) {

@@ -1943,131 +763,3 @@ renderer->variantPalette[i] = renderer->normalPalette[i];

} } } - -static inline unsigned _brighten(unsigned color, int y) { - unsigned c = 0; - unsigned a; -#ifdef COLOR_16_BIT - a = color & 0x1F; - c |= (a + ((0x1F - a) * y) / 16) & 0x1F; - -#ifdef COLOR_5_6_5 - a = color & 0x7C0; - c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0; - - a = color & 0xF800; - c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; -#else - a = color & 0x3E0; - c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0; - - a = color & 0x7C00; - c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00; -#endif -#else - a = color & 0xF8; - c |= (a + ((0xF8 - a) * y) / 16) & 0xF8; - - a = color & 0xF800; - c |= (a + ((0xF800 - a) * y) / 16) & 0xF800; - - a = color & 0xF80000; - c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000; -#endif - return c; -} - -static inline unsigned _darken(unsigned color, int y) { - unsigned c = 0; - unsigned a; -#ifdef COLOR_16_BIT - a = color & 0x1F; - c |= (a - (a * y) / 16) & 0x1F; - -#ifdef COLOR_5_6_5 - a = color & 0x7C0; - c |= (a - (a * y) / 16) & 0x7C0; - - a = color & 0xF800; - c |= (a - (a * y) / 16) & 0xF800; -#else - a = color & 0x3E0; - c |= (a - (a * y) / 16) & 0x3E0; - - a = color & 0x7C00; - c |= (a - (a * y) / 16) & 0x7C00; -#endif -#else - a = color & 0xF8; - c |= (a - (a * y) / 16) & 0xF8; - - a = color & 0xF800; - c |= (a - (a * y) / 16) & 0xF800; - - a = color & 0xF80000; - c |= (a - (a * y) / 16) & 0xF80000; -#endif - return c; -} - -static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) { - unsigned c = 0; - unsigned a, b; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - a = colorA & 0xF81F; - b = colorB & 0xF81F; - a |= (colorA & 0x7C0) << 16; - b |= (colorB & 0x7C0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x08000000) { - c = (c & ~0x0FC00000) | 0x07C00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0xF81F) | ((c >> 16) & 0x07C0); -#else - a = colorA & 0x7C1F; - b = colorB & 0x7C1F; - a |= (colorA & 0x3E0) << 16; - b |= (colorB & 0x3E0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x04000000) { - c = (c & ~0x07E00000) | 0x03E00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); -#endif -#else - a = colorA & 0xF8; - b = colorB & 0xF8; - c |= ((a * weightA + b * weightB) / 16) & 0x1F8; - if (c & 0x00000100) { - c = 0x000000F8; - } - - a = colorA & 0xF800; - b = colorB & 0xF800; - c |= ((a * weightA + b * weightB) / 16) & 0x1F800; - if (c & 0x00010000) { - c = (c & 0x000000F8) | 0x0000F800; - } - - a = colorA & 0xF80000; - b = colorB & 0xF80000; - c |= ((a * weightA + b * weightB) / 16) & 0x1F80000; - if (c & 0x01000000) { - c = (c & 0x0000F8F8) | 0x00F80000; - } -#endif - return c; -}
M src/gba/savedata.csrc/gba/savedata.c

@@ -14,6 +14,11 @@

#include <errno.h> #include <fcntl.h> +// Some testing was done here... +// Erase cycles can vary greatly. +// Some games may vary anywhere between about 2000 cycles to up to 30000 cycles. (Observed on a Macronix (09C2) chip). +// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip). +// An average estimation is as follows. #define FLASH_SETTLE_CYCLES 18000 static void _flashSwitchBank(struct GBASavedata* savedata, int bank);

@@ -234,7 +239,9 @@ }

} } if (savedata->dust > 0 && (address >> 12) == savedata->settling) { - --savedata->dust; + // Give some overhead for waitstates and the comparison + // This estimation can probably be improved + savedata->dust -= 10; return 0x5F; } return savedata->currentBank[address];
M src/gba/serialize.csrc/gba/serialize.c

@@ -61,67 +61,71 @@ gba->rr->stateSaved(gba->rr, state);

} } -void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { +bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { + bool error = false; if (state->versionMagic != GBA_SAVESTATE_MAGIC) { GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate"); - return; + error = true; } if (state->biosChecksum != gba->biosChecksum) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS"); if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) { - return; + error = true; } } if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game"); - return; + error = true; } else if (!gba->memory.rom && state->id != 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded"); - return; + error = true; } if (state->romCrc32 != gba->romCrc32) { GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game"); } if (state->cpu.cycles < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); - return; + error = true; } if (state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); - return; + error = true; } if (state->video.nextHblank - state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); - return; + error = true; } if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); - return; + error = true; } if (state->audio.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); - return; + error = true; } if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative"); - return; + error = true; } if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative"); - return; + error = true; } if (state->audio.ch3.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative"); - return; + error = true; } if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative"); - return; + error = true; } int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET); if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) { GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM"); - return; + error = true; + } + if (error) { + return false; } memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs)); gba->cpu->cpsr = state->cpu.cpsr;

@@ -166,6 +170,7 @@

if (gba->rr) { gba->rr->stateLoaded(gba->rr, state); } + return true; } struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) {

@@ -218,8 +223,7 @@ }

struct GBASerializedState state; uLongf len = sizeof(state); uncompress((Bytef*) &state, &len, chunk->data, chunk->size); - GBADeserialize(png_get_user_chunk_ptr(png), &state); - return 1; + return GBADeserialize(png_get_user_chunk_ptr(png), &state); } static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {

@@ -254,6 +258,8 @@ bool success = GBASaveStateNamed(threadContext->gba, vf, screenshot);

vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot); + } else { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot); } return success; }

@@ -268,6 +274,8 @@ bool success = GBALoadStateNamed(threadContext->gba, vf);

vf->close(vf); if (success) { GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot); + } else { + GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to load", slot); } return success; }

@@ -304,9 +312,9 @@ struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);

if (!state) { return false; } - GBADeserialize(gba, state); + bool success = GBADeserialize(gba, state); vf->unmap(vf, state, sizeof(struct GBASerializedState)); - return true; + return success; } struct GBASerializedState* GBAAllocateState(void) {
M src/gba/serialize.hsrc/gba/serialize.h

@@ -321,7 +321,7 @@ struct VDir;

struct GBAThread; void GBASerialize(struct GBA* gba, struct GBASerializedState* state); -void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); +bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot); bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
M src/gba/supervisor/thread.csrc/gba/supervisor/thread.c

@@ -23,6 +23,8 @@ #include "platform/commandline.h"

#include <signal.h> +static void _loadGameDir(struct GBAThread* threadContext); + static const float _defaultFPSTarget = 60.f; #ifndef DISABLE_THREADING

@@ -352,18 +354,7 @@ if (args->dirmode) {

threadContext->gameDir = VDirOpen(args->fname); threadContext->stateDir = threadContext->gameDir; } else { - threadContext->rom = VFileOpen(args->fname, O_RDONLY); - threadContext->gameDir = 0; -#if USE_LIBZIP - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpenZip(args->fname, 0); - } -#endif -#if USE_LZMA - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpen7z(args->fname, 0); - } -#endif + GBAThreadLoadROM(threadContext, args->fname); } threadContext->fname = args->fname; threadContext->patch = VFileOpen(args->patch, O_RDONLY);

@@ -398,25 +389,7 @@ threadContext->rom = 0;

} if (threadContext->gameDir) { - threadContext->gameDir->rewind(threadContext->gameDir); - struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir); - while (dirent) { - struct Patch patchTemp; - struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY); - if (!vf) { - dirent = threadContext->gameDir->listNext(threadContext->gameDir); - continue; - } - if (!threadContext->rom && GBAIsROM(vf)) { - threadContext->rom = vf; - } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) { - threadContext->patch = vf; - } else { - vf->close(vf); - } - dirent = threadContext->gameDir->listNext(threadContext->gameDir); - } - + _loadGameDir(threadContext); } if (!threadContext->rom && !bootBios) {

@@ -676,6 +649,67 @@ }

MutexUnlock(&threadContext->stateMutex); GBASyncSetVideoSync(&threadContext->sync, frameOn); +} + +void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) { + threadContext->rom = VFileOpen(fname, O_RDONLY); + threadContext->gameDir = 0; +#if USE_LIBZIP + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpenZip(fname, 0); + } +#endif +#if USE_LZMA + if (!threadContext->gameDir) { + threadContext->gameDir = VDirOpen7z(fname, 0); + } +#endif +} + +static void _loadGameDir(struct GBAThread* threadContext) { + threadContext->gameDir->rewind(threadContext->gameDir); + struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir); + while (dirent) { + struct Patch patchTemp; + struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY); + if (!vf) { + dirent = threadContext->gameDir->listNext(threadContext->gameDir); + continue; + } + if (!threadContext->rom && GBAIsROM(vf)) { + threadContext->rom = vf; + } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) { + threadContext->patch = vf; + } else { + vf->close(vf); + } + dirent = threadContext->gameDir->listNext(threadContext->gameDir); + } +} + +void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname) { + GBAUnloadROM(threadContext->gba); + + if (threadContext->rom) { + threadContext->rom->close(threadContext->rom); + threadContext->rom = 0; + } + + if (threadContext->save) { + threadContext->save->close(threadContext->save); + threadContext->save = 0; + } + + GBAThreadLoadROM(threadContext, fname); + if(threadContext->gameDir) { + _loadGameDir(threadContext); + } + + threadContext->fname = fname; + threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR); + + GBARaiseIRQ(threadContext->gba, IRQ_GAMEPAK); + GBALoadROM(threadContext->gba, threadContext->rom, threadContext->save, threadContext->fname); } #ifdef USE_PTHREADS
M src/gba/supervisor/thread.hsrc/gba/supervisor/thread.h

@@ -126,6 +126,9 @@ void GBAThreadTogglePause(struct GBAThread* threadContext);

void GBAThreadPauseFromThread(struct GBAThread* threadContext); struct GBAThread* GBAThreadGetContext(void); +void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname); +void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname); + #ifdef USE_PNG void GBAThreadTakeScreenshot(struct GBAThread* threadContext); #endif
M src/gba/video.csrc/gba/video.c

@@ -23,6 +23,25 @@ static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);

static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer); static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels); +const int GBAVideoObjSizes[16][2] = { + { 8, 8 }, + { 16, 16 }, + { 32, 32 }, + { 64, 64 }, + { 16, 8 }, + { 32, 8 }, + { 32, 16 }, + { 64, 32 }, + { 8, 16 }, + { 8, 32 }, + { 16, 32 }, + { 32, 64 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, +}; + static struct GBAVideoRenderer dummyRenderer = { .init = GBAVideoDummyRendererInit, .reset = GBAVideoDummyRendererReset,
M src/gba/video.hsrc/gba/video.h

@@ -215,4 +215,6 @@ struct GBASerializedState;

void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state); void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state); +extern const int GBAVideoObjSizes[16][2]; + #endif
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -42,6 +42,9 @@ QApplication::setApplicationName(projectName);

QApplication::setApplicationVersion(projectVersion); Window* w = new Window(&m_configController); + connect(w, &Window::destroyed, [this]() { + m_windows[0] = nullptr; + }); m_windows[0] = w; #ifndef Q_OS_MAC

@@ -79,7 +82,11 @@ if (m_multiplayer.attached() >= MAX_GBAS) {

return nullptr; } Window* w = new Window(&m_configController, m_multiplayer.attached()); - m_windows[m_multiplayer.attached()] = w; + int windowId = m_multiplayer.attached(); + connect(w, &Window::destroyed, [this, windowId]() { + m_windows[windowId] = nullptr; + }); + m_windows[windowId] = w; w->setAttribute(Qt::WA_DeleteOnClose); #ifndef Q_OS_MAC w->show();
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -282,17 +282,7 @@ if (m_dirmode) {

m_threadContext.gameDir = VDirOpen(m_threadContext.fname); m_threadContext.stateDir = m_threadContext.gameDir; } else { - m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY); -#if USE_LIBZIP - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0); - } -#endif -#if USE_LZMA - if (!m_threadContext.gameDir) { - m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0); - } -#endif + GBAThreadLoadROM(&m_threadContext, m_threadContext.fname); } }

@@ -323,6 +313,28 @@ if (m_gameOpen) {

closeGame(); openGame(); } +} + +void GameController::yankPak() { + if (!m_gameOpen) { + return; + } + threadInterrupt(); + GBAYankROM(m_threadContext.gba); + threadContinue(); +} + + +void GameController::replaceGame(const QString& path) { + if (!m_gameOpen) { + return; + } + + m_fname = path; + threadInterrupt(); + m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData()); + GBAThreadReplaceROM(&m_threadContext, m_threadContext.fname); + threadContinue(); } void GameController::loadPatch(const QString& path) {

@@ -561,9 +573,10 @@ m_stateSlot = slot;

} GBARunOnThread(&m_threadContext, [](GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - GBALoadState(context, context->stateDir, controller->m_stateSlot); - controller->stateLoaded(context); - controller->frameAvailable(controller->m_drawContext); + if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) { + controller->stateLoaded(context); + controller->frameAvailable(controller->m_drawContext); + } }); }
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -97,6 +97,8 @@

public slots: void loadGame(const QString& path, bool dirmode = false); void loadBIOS(const QString& path); + void yankPak(); + void replaceGame(const QString& path); void setSkipBIOS(bool); void setUseBIOS(bool); void loadPatch(const QString& path);
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -68,6 +68,7 @@ , m_playerId(playerId)

{ setFocusPolicy(Qt::StrongFocus); setAcceptDrops(true); + setAttribute(Qt::WA_DeleteOnClose); m_controller = new GameController(this); m_controller->setInputController(&m_inputController); m_controller->setOverrides(m_config->overrides());

@@ -226,6 +227,24 @@ QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' ')));

QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); if (!filename.isEmpty()) { m_controller->loadGame(filename); + } +} + +void Window::replaceROM() { + QStringList formats{ + "*.gba", +#ifdef USE_LIBZIP + "*.zip", +#endif +#ifdef USE_LZMA + "*.7z", +#endif + "*.rom", + "*.bin"}; + QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' '))); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); + if (!filename.isEmpty()) { + m_controller->replaceGame(filename); } }

@@ -614,6 +633,8 @@ addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS");

addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM"); + m_mruMenu = fileMenu->addMenu(tr("Recent")); fileMenu->addSeparator();

@@ -697,6 +718,11 @@ QAction* shutdown = new QAction(tr("Sh&utdown"), emulationMenu);

connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame())); m_gameActions.append(shutdown); addControlledAction(emulationMenu, shutdown, "shutdown"); + + QAction* yank = new QAction(tr("Yank game pak"), emulationMenu); + connect(yank, SIGNAL(triggered()), m_controller, SLOT(yankPak())); + m_gameActions.append(yank); + addControlledAction(emulationMenu, yank, "yank"); emulationMenu->addSeparator(); QAction* pause = new QAction(tr("&Pause"), emulationMenu);
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -65,6 +65,8 @@ void toggleFullScreen();

void loadConfig(); void saveConfig(); + void replaceROM(); + void importSharkport(); void exportSharkport();
M src/util/vfs.csrc/util/vfs.c

@@ -5,6 +5,36 @@ * 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 "vfs.h" +struct VFile* VFileOpen(const char* path, int flags) { +#ifdef USE_VFS_FILE + const char* chflags; + switch (flags & O_ACCMODE) { + case O_WRONLY: + if (flags & O_APPEND) { + chflags = "ab"; + } else { + chflags = "wb"; + } + break; + case O_RDWR: + if (flags & O_APPEND) { + chflags = "a+b"; + } else if (flags & O_TRUNC) { + chflags = "w+b"; + } else { + chflags = "r+b"; + } + break; + case O_RDONLY: + chflags = "rb"; + break; + } + return VFileFOpen(path, chflags); +#else + return VFileOpenFD(path, flags); +#endif +} + ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { ssize_t bytesRead = 0; while (bytesRead < size - 1) {

@@ -50,3 +80,31 @@ STORE_16LE(lehword, 0, hword);

} return r; } + +struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + struct VFile* vf; + if (!dir) { + if (!realPath) { + return 0; + } + char* dotPoint = strrchr(realPath, '.'); + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + if (dotPoint > strrchr(realPath, '/')) { + int len = dotPoint - realPath; + strncpy(path, realPath, len); + path[len] = 0; + strncat(path + len, suffix, PATH_MAX - len - 1); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); + } + vf = VFileOpen(path, mode); + } else { + snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); + vf = dir->openFile(dir, path, mode); + } + return vf; +}
M src/util/vfs.hsrc/util/vfs.h

@@ -53,6 +53,8 @@ struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode);

}; struct VFile* VFileOpen(const char* path, int flags); + +struct VFile* VFileOpenFD(const char* path, int flags); struct VFile* VFileFOpen(const char* path, const char* mode); struct VFile* VFileFromFD(int fd); struct VFile* VFileFromMemory(void* mem, size_t size);
M src/util/vfs/vfs-dirent.csrc/util/vfs/vfs-dirent.c

@@ -190,31 +190,3 @@ return vdede->ent->d_name;

} return 0; } - -struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - struct VFile* vf; - if (!dir) { - if (!realPath) { - return 0; - } - char* dotPoint = strrchr(realPath, '.'); - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - if (dotPoint > strrchr(realPath, '/')) { - int len = dotPoint - realPath; - strncpy(path, realPath, len); - path[len] = 0; - strncat(path + len, suffix, PATH_MAX - len - 1); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix); - } - vf = VFileOpen(path, mode); - } else { - snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix); - vf = dir->openFile(dir, path, mode); - } - return vf; -}
M src/util/vfs/vfs-fd.csrc/util/vfs/vfs-fd.c

@@ -29,7 +29,7 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size);

static void _vfdTruncate(struct VFile* vf, size_t size); static ssize_t _vfdSize(struct VFile* vf); -struct VFile* VFileOpen(const char* path, int flags) { +struct VFile* VFileOpenFD(const char* path, int flags) { if (!path) { return 0; }
M src/util/vfs/vfs-file.csrc/util/vfs/vfs-file.c

@@ -7,6 +7,7 @@ #include "util/vfs.h"

#include "util/memory.h" +#include <errno.h> #include <stdio.h> struct VFileFILE {

@@ -28,6 +29,9 @@ if (!path && !mode) {

return 0; } FILE* file = fopen(path, mode); + if (!file && errno == ENOENT && strcmp(mode, "r+b") == 0) { + file = fopen(path, "w+b"); + } return VFileFromFILE(file); }