/* * Copyright 2016 - 2020 gary@drinkingtea.net * * 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 #include #include #include #include #include "addresses.hpp" #include "bios.hpp" #include "irq.hpp" #include "gfx.hpp" namespace nostalgia::core { constexpr auto GbaTileColumns = 32; constexpr auto GbaTileRows = 32; constexpr uint16_t DispStat_irq_vblank = 1 << 3; constexpr uint16_t DispStat_irq_hblank = 1 << 4; constexpr uint16_t DispStat_irq_vcount = 1 << 5; struct GbaPaletteTarget { static constexpr auto TypeName = NostalgiaPalette::TypeName; static constexpr auto Fields = NostalgiaPalette::Fields; static constexpr auto TypeVersion = NostalgiaPalette::TypeVersion; volatile uint16_t *palette = nullptr; }; struct GbaTileMapTarget { static constexpr auto TypeName = NostalgiaGraphic::TypeName; static constexpr auto Fields = NostalgiaGraphic::Fields; static constexpr auto TypeVersion = NostalgiaGraphic::TypeVersion; volatile uint32_t *bgCtl = nullptr; ox::FileAddress defaultPalette; GbaPaletteTarget pal; volatile uint16_t *tileMap = nullptr; }; template ox::Error modelRead(T *io, GbaPaletteTarget *t) { io->template setTypeInfo(); auto colorHandler = [t](std::size_t i, Color16 *c) { t->palette[i] = *c; return OxError(0); }; return io->template field("colors", colorHandler); } template ox::Error modelRead(T *io, GbaTileMapTarget *t) { io->template setTypeInfo(); uint8_t bpp; int dummy; oxReturnError(io->field("bpp", &bpp)); oxReturnError(io->field("rows", &dummy)); oxReturnError(io->field("columns", &dummy)); constexpr auto Bpp8 = 1 << 7; *t->bgCtl = (28 << 8) | 1; if (bpp == 4) { *t->bgCtl |= (*t->bgCtl | Bpp8) ^ Bpp8; // set to use 4 bits per pixel } else { *t->bgCtl |= Bpp8; // set to use 8 bits per pixel } oxReturnError(io->field("defaultPalette", &t->defaultPalette)); oxReturnError(io->field("pal", &t->pal)); uint16_t intermediate = 0; auto handleTileMap = [t, &intermediate](std::size_t i, uint8_t *tile) { if (i & 1) { // i is odd intermediate |= static_cast(*tile) << 8; t->tileMap[i / 2] = intermediate; } else { // i is even intermediate = *tile & 0x00ff; } return OxError(0); }; return io->template field("tileMap", handleTileMap); } ox::Error initGfx(Context*) { /* Sprite Mode ----\ */ /* ---\| */ /* Background 0 -\|| */ /* Objects -----\||| */ /* |||| */ REG_DISPCTL = 0x1101; // tell display to trigger vblank interrupts REG_DISPSTAT |= DispStat_irq_vblank; // enable vblank interrupt REG_IE |= Int_vblank; return OxError(0); } ox::Error shutdownGfx(Context*) { return OxError(0); } [[nodiscard]] constexpr volatile uint32_t &bgCtl(int bg) noexcept { switch (bg) { case 0: return REG_BG0CTL; case 1: return REG_BG1CTL; case 2: return REG_BG2CTL; case 3: return REG_BG3CTL; default: oxPanic(OxError(1), "Looking up non-existent register"); return REG_BG0CTL; } } // Do NOT rely on Context in the GBA version of this function. ox::Error initConsole(Context *ctx) { constexpr auto TilesheetAddr = "/TileSheets/Charset.ng"; constexpr auto PaletteAddr = "/Palettes/Charset.npal"; if (!ctx) { ctx = new (ox_alloca(sizeof(Context))) Context(); auto rom = loadRom(); if (!rom) { return OxError(1); } ox::FileStore32 fs(rom, 32 * ox::units::MB); ctx->rom = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs); } return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr); } ox::Error loadBgTileSheet(Context *ctx, int section, ox::FileAddress tilesheetAddr, ox::FileAddress paletteAddr) { auto [tsStat, tsStatErr] = ctx->rom->stat(tilesheetAddr); oxReturnError(tsStatErr); auto [ts, tserr] = ctx->rom->read(tilesheetAddr); oxReturnError(tserr); GbaTileMapTarget target; target.pal.palette = &MEM_BG_PALETTE[section]; target.bgCtl = &bgCtl(section); target.tileMap = &ox::bit_cast(MEM_BG_TILES)[section * 512]; oxReturnError(ox::readMC(ts, tsStat.size, &target)); // load external palette if available if (paletteAddr) { auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr); oxReturnError(palStatErr); auto [pal, palErr] = ctx->rom->read(paletteAddr); oxReturnError(palErr); oxReturnError(ox::readMC(pal, palStat.size, &target.pal)); } return OxError(0); } ox::Error loadSpriteTileSheet(Context *ctx, int section, ox::FileAddress tilesheetAddr, ox::FileAddress paletteAddr) { auto [tsStat, tsStatErr] = ctx->rom->stat(tilesheetAddr); oxReturnError(tsStatErr); auto [ts, tserr] = ctx->rom->read(tilesheetAddr); oxReturnError(tserr); GbaTileMapTarget target; target.pal.palette = &MEM_SPRITE_PALETTE[section]; target.bgCtl = &bgCtl(section); target.tileMap = &ox::bit_cast(MEM_SPRITE_TILES)[section * 512]; oxReturnError(ox::readMC(ts, tsStat.size, &target)); // load external palette if available if (paletteAddr) { auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr); oxReturnError(palStatErr); auto [pal, palErr] = ctx->rom->read(paletteAddr); oxReturnError(palErr); oxReturnError(ox::readMC(pal, palStat.size, &target.pal)); } return OxError(0); } ox::Error loadBgPalette(Context *ctx, int section, ox::FileAddress paletteAddr) { GbaPaletteTarget target; target.palette = &MEM_BG_PALETTE[section]; auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr); oxReturnError(palStatErr); auto [pal, palErr] = ctx->rom->read(paletteAddr); oxReturnError(palErr); oxReturnError(ox::readMC(pal, palStat.size, &target)); return OxError(0); } ox::Error loadSpritePalette(Context *ctx, int section, ox::FileAddress paletteAddr) { GbaPaletteTarget target; target.palette = &MEM_SPRITE_PALETTE[section]; auto [palStat, palStatErr] = ctx->rom->stat(paletteAddr); oxReturnError(palStatErr); auto [pal, palErr] = ctx->rom->read(paletteAddr); oxReturnError(palErr); oxReturnError(ox::readMC(pal, palStat.size, &target)); return OxError(0); } // Do NOT use Context in the GBA version of this function. void setTile(Context*, int layer, int column, int row, uint8_t tile) { MEM_BG_MAP[layer][row * GbaTileColumns + column] = tile; } // Do NOT use Context in the GBA version of this function. void clearTileLayer(Context*, int layer) { memset(&MEM_BG_MAP[layer], 0, GbaTileRows * GbaTileColumns); } void setSprite(unsigned idx, unsigned x, unsigned y, unsigned tileIdx) { GbaSpriteAttrUpdate oa; oa.attr0 = static_cast(y & ox::onMask(7)) | (static_cast(1) << 10); // enable alpha oa.attr1 = static_cast(x) & ox::onMask(8); oa.attr2 = static_cast(tileIdx & ox::onMask(8)); oa.idx = idx; // block until g_spriteUpdates is less than buffer len if (g_spriteUpdates >= config::GbaSpriteBufferLen) { nostalgia_core_vblankintrwait(); } if constexpr(config::GbaEventLoopTimerBased) { REG_IE &= ~Int_vblank; // disable vblank interrupt handler g_spriteBuffer[g_spriteUpdates++] = oa; REG_IE |= Int_vblank; // enable vblank interrupt handler } else { auto ie = REG_IE; // disable vblank interrupt handler REG_IE &= ~Int_vblank; // disable vblank interrupt handler g_spriteBuffer[g_spriteUpdates++] = oa; REG_IE = ie; // enable vblank interrupt handler } } }