296 lines
9.6 KiB
C++
296 lines
9.6 KiB
C++
/*
|
|
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#include <ox/fs/fs.hpp>
|
|
#include <ox/mc/mc.hpp>
|
|
|
|
#include <nostalgia/core/media.hpp>
|
|
#include <nostalgia/core/gfx.hpp>
|
|
|
|
#include "addresses.hpp"
|
|
#include "bios.hpp"
|
|
#include "gfx.hpp"
|
|
#include "irq.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 = Palette::TypeName;
|
|
static constexpr auto TypeVersion = Palette::TypeVersion;
|
|
volatile uint16_t *palette = nullptr;
|
|
};
|
|
|
|
struct GbaTileMapTarget {
|
|
static constexpr auto TypeName = CompactTileSheet::TypeName;
|
|
static constexpr auto TypeVersion = CompactTileSheet::TypeVersion;
|
|
volatile uint16_t *bgCtl = nullptr;
|
|
ox::FileAddress defaultPalette;
|
|
GbaPaletteTarget pal;
|
|
volatile uint16_t *tileMap = nullptr;
|
|
};
|
|
|
|
template<typename T>
|
|
constexpr ox::Error modelRead(T *io, GbaPaletteTarget *t) noexcept {
|
|
io->template setTypeInfo<Palette>();
|
|
const auto colorHandler = [t](std::size_t i, Color16 *c) {
|
|
t->palette[i] = *c;
|
|
return OxError(0);
|
|
};
|
|
return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr ox::Error modelRead(T *io, GbaTileMapTarget *t) noexcept {
|
|
io->template setTypeInfo<CompactTileSheet>();
|
|
uint8_t bpp;
|
|
oxReturnError(io->field("bpp", &bpp));
|
|
constexpr auto Bpp8 = 1 << 7;
|
|
if (t->bgCtl) {
|
|
*t->bgCtl = (28 << 8) | 1;
|
|
if (bpp == 4) {
|
|
*t->bgCtl = *t->bgCtl | ((*t->bgCtl | Bpp8) ^ Bpp8); // set to use 4 bits per pixel
|
|
} else {
|
|
*t->bgCtl = *t->bgCtl | Bpp8; // set to use 8 bits per pixel
|
|
}
|
|
}
|
|
oxReturnError(io->field("defaultPalette", &t->defaultPalette));
|
|
uint16_t intermediate = 0;
|
|
const auto handleTileMap = [t, &intermediate](std::size_t i, uint8_t *tile) {
|
|
if (i & 1) { // i is odd
|
|
intermediate |= static_cast<uint16_t>(*tile) << 8;
|
|
t->tileMap[i / 2] = intermediate;
|
|
} else { // i is even
|
|
intermediate = *tile & 0x00ff;
|
|
}
|
|
return OxError(0);
|
|
};
|
|
return io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
|
}
|
|
|
|
ox::Error initGfx(Context*) noexcept {
|
|
REG_DISPCTL = DispCtl_Mode0
|
|
| DispCtl_SpriteMap1D
|
|
| DispCtl_Obj;
|
|
// tell display to trigger vblank interrupts
|
|
REG_DISPSTAT = REG_DISPSTAT | DispStat_irq_vblank;
|
|
// enable vblank interrupt
|
|
REG_IE = REG_IE | Int_vblank;
|
|
return OxError(0);
|
|
}
|
|
|
|
ox::Error shutdownGfx(Context*) noexcept {
|
|
return OxError(0);
|
|
}
|
|
|
|
void setWindowTitle(Context*, const char*) noexcept {
|
|
}
|
|
|
|
void focusWindow(Context*) noexcept {
|
|
}
|
|
|
|
int getScreenWidth(Context*) noexcept {
|
|
return 240;
|
|
}
|
|
|
|
int getScreenHeight(Context*) noexcept {
|
|
return 160;
|
|
}
|
|
|
|
geo::Size getScreenSize(Context*) noexcept {
|
|
return {240, 160};
|
|
}
|
|
|
|
uint8_t bgStatus(Context*) noexcept {
|
|
return (REG_DISPCTL >> 8) & 0b1111;
|
|
}
|
|
|
|
void setBgStatus(Context*, uint32_t status) noexcept {
|
|
constexpr auto Bg0Status = 8;
|
|
REG_DISPCTL = (REG_DISPCTL & ~0b111100000000u) | status << Bg0Status;
|
|
}
|
|
|
|
bool bgStatus(Context*, unsigned bg) noexcept {
|
|
return (REG_DISPCTL >> (8 + bg)) & 1;
|
|
}
|
|
|
|
void setBgStatus(Context*, unsigned bg, bool status) noexcept {
|
|
constexpr auto Bg0Status = 8;
|
|
const auto mask = static_cast<uint32_t>(status) << (Bg0Status + bg);
|
|
REG_DISPCTL = REG_DISPCTL | ((REG_DISPCTL & ~mask) | mask);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr volatile uint16_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) noexcept {
|
|
constexpr auto TilesheetAddr = "/TileSheets/Charset.ng";
|
|
constexpr auto PaletteAddr = "/Palettes/Charset.npal";
|
|
setBgStatus(ctx, 0b0001);
|
|
if (!ctx) {
|
|
ctx = new (ox_alloca(sizeof(Context))) Context();
|
|
oxRequire(rom, loadRom());
|
|
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
|
auto romFs = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
|
new (&ctx->rom) ox::UniquePtr<ox::FileSystem>(romFs);
|
|
}
|
|
return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
|
|
}
|
|
|
|
ox::Error loadBgTileSheet(Context *ctx,
|
|
int section,
|
|
const ox::FileAddress &tilesheetAddr,
|
|
const ox::FileAddress &paletteAddr) noexcept {
|
|
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
|
|
oxRequire(ts, ctx->rom->directAccess(tilesheetAddr));
|
|
GbaTileMapTarget target;
|
|
target.pal.palette = &MEM_BG_PALETTE[section];
|
|
target.bgCtl = &bgCtl(section);
|
|
target.tileMap = &reinterpret_cast<uint16_t*>(MEM_BG_TILES)[section * 512];
|
|
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
|
// load external palette if available
|
|
if (paletteAddr) {
|
|
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
|
oxRequire(pal, ctx->rom->directAccess(paletteAddr));
|
|
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
|
}
|
|
return OxError(0);
|
|
}
|
|
|
|
ox::Error loadSpriteTileSheet(Context *ctx,
|
|
int section,
|
|
const ox::FileAddress &tilesheetAddr,
|
|
const ox::FileAddress &paletteAddr) noexcept {
|
|
oxRequire(tsStat, ctx->rom->stat(tilesheetAddr));
|
|
oxRequire(ts, ctx->rom->directAccess(tilesheetAddr));
|
|
GbaTileMapTarget target;
|
|
target.pal.palette = &MEM_SPRITE_PALETTE[section];
|
|
// Is this needed? Should this be written to an equivalent sprite value?
|
|
// target.bgCtl = &bgCtl(section);
|
|
target.tileMap = &reinterpret_cast<uint16_t*>(MEM_SPRITE_TILES)[section * 512];
|
|
oxReturnError(ox::readMC(ts, tsStat.size, &target));
|
|
// load external palette if available
|
|
if (paletteAddr) {
|
|
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
|
oxRequire(pal, ctx->rom->directAccess(paletteAddr));
|
|
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
|
|
}
|
|
return OxError(0);
|
|
}
|
|
|
|
ox::Error loadBgPalette(Context *ctx, int section, const ox::FileAddress &paletteAddr) noexcept {
|
|
GbaPaletteTarget target;
|
|
target.palette = &MEM_BG_PALETTE[section];
|
|
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
|
oxRequire(pal, ctx->rom->directAccess(paletteAddr));
|
|
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
|
return OxError(0);
|
|
}
|
|
|
|
ox::Error loadSpritePalette(Context *ctx, int section, const ox::FileAddress &paletteAddr) noexcept {
|
|
GbaPaletteTarget target;
|
|
target.palette = &MEM_SPRITE_PALETTE[section];
|
|
oxRequire(palStat, ctx->rom->stat(paletteAddr));
|
|
oxRequire(pal, ctx->rom->directAccess(paletteAddr));
|
|
oxReturnError(ox::readMC(pal, palStat.size, &target));
|
|
return OxError(0);
|
|
}
|
|
|
|
// Do NOT use Context in the GBA version of this function.
|
|
void puts(Context *ctx, int column, int row, const char *str) noexcept {
|
|
for (int i = 0; str[i]; i++) {
|
|
setTile(ctx, 0, column + i, row, static_cast<uint8_t>(charMap[static_cast<int>(str[i])]));
|
|
}
|
|
}
|
|
|
|
void setTile(Context*, int layer, int column, int row, uint8_t tile) noexcept {
|
|
MEM_BG_MAP[layer][row * GbaTileColumns + column] = tile;
|
|
}
|
|
|
|
// Do NOT use Context in the GBA version of this function.
|
|
void clearTileLayer(Context*, int layer) noexcept {
|
|
memset(&MEM_BG_MAP[layer], 0, GbaTileRows * GbaTileColumns);
|
|
}
|
|
|
|
[[maybe_unused]]
|
|
void hideSprite(Context*, unsigned idx) noexcept {
|
|
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
|
GbaSpriteAttrUpdate oa;
|
|
oa.attr0 = 2 << 8;
|
|
oa.idx = idx;
|
|
// block until g_spriteUpdates is less than buffer len
|
|
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
|
|
nostalgia_core_vblankintrwait();
|
|
}
|
|
if constexpr(config::GbaEventLoopTimerBased) {
|
|
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
|
|
g_spriteBuffer[g_spriteUpdates] = oa;
|
|
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
|
|
} else {
|
|
const auto ie = REG_IE; // disable vblank interrupt handler
|
|
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
|
|
g_spriteBuffer[g_spriteUpdates] = oa;
|
|
REG_IE = ie; // enable vblank interrupt handler
|
|
}
|
|
g_spriteUpdates = g_spriteUpdates + 1;
|
|
}
|
|
|
|
void setSprite(Context*,
|
|
unsigned idx,
|
|
unsigned x,
|
|
unsigned y,
|
|
unsigned tileIdx,
|
|
unsigned spriteShape,
|
|
unsigned spriteSize,
|
|
unsigned flipX) noexcept {
|
|
oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
|
|
GbaSpriteAttrUpdate oa;
|
|
oa.attr0 = static_cast<uint16_t>(y & ox::onMask<uint8_t>(7))
|
|
| (static_cast<uint16_t>(1) << 10) // enable alpha
|
|
| (static_cast<uint16_t>(spriteShape) << 14);
|
|
oa.attr1 = (static_cast<uint16_t>(x) & ox::onMask<uint8_t>(8))
|
|
| (static_cast<uint16_t>(flipX) << 12)
|
|
| (static_cast<uint16_t>(spriteSize) << 14);
|
|
oa.attr2 = static_cast<uint16_t>(tileIdx & ox::onMask<uint16_t>(8));
|
|
oa.idx = idx;
|
|
// block until g_spriteUpdates is less than buffer len
|
|
if (g_spriteUpdates >= config::GbaSpriteBufferLen) [[unlikely]] {
|
|
nostalgia_core_vblankintrwait();
|
|
}
|
|
if constexpr(config::GbaEventLoopTimerBased) {
|
|
REG_IE = REG_IE & ~Int_vblank; // disable vblank interrupt handler
|
|
g_spriteBuffer[g_spriteUpdates] = oa;
|
|
REG_IE = REG_IE | Int_vblank; // enable vblank interrupt handler
|
|
} else {
|
|
const auto ie = REG_IE; // disable vblank interrupt handler
|
|
REG_IE = REG_IE & static_cast<uint16_t>(~Int_vblank); // disable vblank interrupt handler
|
|
g_spriteBuffer[g_spriteUpdates] = oa;
|
|
REG_IE = ie; // enable vblank interrupt handler
|
|
}
|
|
g_spriteUpdates = g_spriteUpdates + 1;
|
|
}
|
|
|
|
}
|