Files
ox/src/nostalgia/core/gba/gfx.cpp
T
2023-06-03 20:40:26 -05:00

240 lines
8.0 KiB
C++

/*
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <ox/fs/fs.hpp>
#include <ox/mc/mc.hpp>
#include <ox/std/array.hpp>
#include <teagba/addresses.hpp>
#include <teagba/gfx.hpp>
#include <teagba/registers.hpp>
#include <keel/media.hpp>
#include <turbine/turbine.hpp>
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/gfx.hpp>
namespace nostalgia::core {
struct BgCbbData {
unsigned bpp = 4;
};
static ox::Array<BgCbbData, 4> g_cbbData;
constexpr auto GbaTileColumns = 32;
constexpr auto GbaTileRows = 32;
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;
BgCbbData *cbbData = nullptr;
ox::FileAddress defaultPalette;
GbaPaletteTarget pal;
volatile uint16_t *tileMap = nullptr;
};
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaPaletteTarget> auto *t) noexcept {
io->template setTypeInfo<Palette>();
const auto colorHandler = [t](std::size_t i, const Color16 *c) {
t->palette[i] = *c;
return OxError(0);
};
return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
}
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t) noexcept {
io->template setTypeInfo<CompactTileSheet>();
oxReturnError(io->field("bpp", &t->cbbData->bpp));
oxReturnError(io->field("defaultPalette", &t->defaultPalette));
uint16_t intermediate = 0;
const auto handleTileMap = [t, &intermediate](std::size_t i, const 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*, const InitParams&) noexcept {
for (auto bgCtl = &REG_BG0CTL; bgCtl <= &REG_BG3CTL; bgCtl += 2) {
teagba::bgSetSbb(bgCtl, 28);
}
return {};
}
void shutdownGfx(Context*) noexcept {
}
uint8_t bgStatus(Context*) noexcept {
return (REG_DISPCTL >> 8) & 0b1111;
}
void setBgStatus(Context*, uint32_t status) noexcept {
constexpr auto BgStatus = 8;
REG_DISPCTL = (REG_DISPCTL & ~0b111100000000u) | status << BgStatus;
}
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);
}
// Do NOT rely on Context in the GBA version of this function.
ox::Error initConsole(Context *ctx) noexcept {
constexpr ox::FileAddress TilesheetAddr("/TileSheets/Charset.ng");
constexpr ox::FileAddress PaletteAddr("/Palettes/Charset.npal");
setBgStatus(ctx, 0b0001);
if (!ctx) {
ctx = new(ox_alloca(sizeof(Context))) Context;
oxRequire(rom, keel::loadRom());
ox::FileStore32 fs(rom, 32 * ox::units::MB);
auto romFs = new(ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
oxRequireM(tctx, turbine::init(ox::UPtr<ox::FileSystem>(romFs), ""));
ctx->turbineCtx = tctx.release();
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
setBgCbb(ctx, 0, 0);
return {};
} else {
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr));
setBgCbb(ctx, 0, 0);
return {};
}
}
void setBgCbb(Context*, unsigned bgIdx, unsigned cbb) noexcept {
auto &bgCtl = regBgCtl(bgIdx);
const auto &cbbData = g_cbbData[cbb];
teagba::bgSetBpp(&bgCtl, cbbData.bpp);
teagba::bgSetCbb(&bgCtl, cbb);
}
ox::Error loadBgTileSheet(Context *ctx,
unsigned cbb,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
GbaTileMapTarget target;
target.pal.palette = MEM_BG_PALETTE;
target.cbbData = &g_cbbData[cbb];
target.tileMap = MEM_BG_TILES[cbb].data();
oxReturnError(ox::readMC(ts, tsStat.size, &target));
// load external palette if available
if (paletteAddr) {
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
}
// update bpp of all bgs with the updated cbb
const auto bpp = g_cbbData[cbb].bpp;
teagba::iterateBgCtl([bpp, cbb](auto bgCtl) {
if (teagba::bgCbb(bgCtl) == cbb) {
teagba::bgSetBpp(bgCtl, bpp);
}
});
return {};
}
ox::Error loadSpriteTileSheet(Context *ctx,
const ox::FileAddress &tilesheetAddr,
const ox::FileAddress &paletteAddr) noexcept {
oxRequire(tsStat, ctx->rom().stat(tilesheetAddr));
oxRequire(ts, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(tilesheetAddr));
GbaTileMapTarget target;
target.pal.palette = MEM_SPRITE_PALETTE;
target.tileMap = MEM_SPRITE_TILES;
oxReturnError(ox::readMC(ts, tsStat.size, &target));
// load external palette if available
if (paletteAddr) {
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target.pal));
}
return {};
}
ox::Error loadBgPalette(Context *ctx, unsigned, const ox::FileAddress &paletteAddr) noexcept {
GbaPaletteTarget target;
target.palette = MEM_BG_PALETTE;
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target));
return {};
}
ox::Error loadSpritePalette(Context *ctx, unsigned cbb, const ox::FileAddress &paletteAddr) noexcept {
GbaPaletteTarget target;
target.palette = &MEM_SPRITE_PALETTE[cbb];
oxRequire(palStat, ctx->rom().stat(paletteAddr));
oxRequire(pal, static_cast<ox::MemFS*>(&ctx->rom())->directAccess(paletteAddr));
oxReturnError(ox::readMC(pal, palStat.size, &target));
return {};
}
void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept {
const auto col = static_cast<unsigned>(column);
for (auto i = 0u; i < str.bytes(); i++) {
const auto c = charMap[static_cast<unsigned>(str[i])];
setTile(ctx, 0, static_cast<int>(col + i), row, static_cast<uint8_t>(c));
}
}
void setTile(Context*, unsigned bgIdx, int column, int row, uint8_t tile) noexcept {
const auto tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
MEM_BG_MAP[bgIdx][tileIdx] = tile;
}
// Do NOT use Context in the GBA version of this function.
void clearTileLayer(Context*, unsigned bgIdx) noexcept {
memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
}
[[maybe_unused]]
void hideSprite(Context*, unsigned idx) noexcept {
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
teagba::GbaSpriteAttrUpdate oa;
oa.attr0 = 2 << 8;
oa.idx = idx;
teagba::addSpriteUpdate(oa);
}
void setSprite(Context*,
unsigned idx,
int x,
int y,
unsigned tileIdx,
unsigned spriteShape,
unsigned spriteSize,
unsigned flipX) noexcept {
//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
teagba::GbaSpriteAttrUpdate oa;
oa.attr0 = static_cast<uint16_t>(y & ox::onMask<uint8_t>(0b111'1111))
| (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;
teagba::addSpriteUpdate(oa);
}
}