This commit is contained in:
2025-10-12 00:03:01 +02:00
parent f04b026d46
commit b55feb68f8
2 changed files with 112 additions and 48 deletions

View File

@@ -568,6 +568,7 @@ union cart_rtc
} reg;
uint8_t bytes[5];
};
extern "C" uint8_t gb_rom_read(struct gb_s*, const uint_fast32_t);
/**
* Emulator context.
@@ -577,15 +578,6 @@ union cart_rtc
*/
struct gb_s
{
/**
* Return byte from ROM at given address.
*
* \param gb_s emulator context
* \param addr address
* \return byte at address in ROM
*/
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t addr);
/**
* Return byte from cart RAM at given address.
*
@@ -796,17 +788,17 @@ uint8_t __gb_read(struct gb_s *gb, uint16_t addr)
case 0x1:
case 0x2:
case 0x3:
return gb->gb_rom_read(gb, addr);
return gb_rom_read(gb, addr);
case 0x4:
case 0x5:
case 0x6:
case 0x7:
if(gb->mbc == 1 && gb->cart_mode_select)
return gb->gb_rom_read(gb,
return gb_rom_read(gb,
addr + ((gb->selected_rom_bank & 0x1F) - 1) * ROM_BANK_SIZE);
else
return gb->gb_rom_read(gb, addr + (gb->selected_rom_bank - 1) * ROM_BANK_SIZE);
return gb_rom_read(gb, addr + (gb->selected_rom_bank - 1) * ROM_BANK_SIZE);
case 0x8:
case 0x9:
@@ -3542,7 +3534,7 @@ int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size)
/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */
0x00, 0x800, 0x2000, 0x8000, 0x20000, 0x10000
};
uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location);
uint8_t ram_size_code = gb_rom_read(gb, ram_size_location);
/* MBC2 always has 512 half-bytes of cart RAM.
* This assumes that only the lower nibble of each byte is used; the
@@ -3570,7 +3562,7 @@ uint_fast32_t gb_get_save_size(struct gb_s *gb)
/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */
0x00, 0x800, 0x2000, 0x8000, 0x20000, 0x10000
};
uint8_t ram_size_code = gb->gb_rom_read(gb, ram_size_location);
uint8_t ram_size_code = gb_rom_read(gb, ram_size_location);
/* MBC2 always has 512 half-bytes of cart RAM.
* This assumes that only the lower nibble of each byte is used; the
@@ -3603,7 +3595,7 @@ uint8_t gb_colour_hash(struct gb_s *gb)
uint16_t i;
for(i = ROM_TITLE_START_ADDR; i <= ROM_TITLE_END_ADDR; i++)
x += gb->gb_rom_read(gb, i);
x += gb_rom_read(gb, i);
return x;
}
@@ -3626,7 +3618,7 @@ void gb_reset(struct gb_s *gb)
if(gb->gb_bootrom_read == NULL)
{
uint8_t hdr_chk;
hdr_chk = gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC) != 0;
hdr_chk = gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC) != 0;
gb->cpu_reg.a = 0x01;
gb->cpu_reg.f.f_bits.z = 1;
@@ -3692,7 +3684,6 @@ void gb_reset(struct gb_s *gb)
}
enum gb_init_error_e gb_init(struct gb_s *gb,
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t),
uint8_t (*gb_cart_ram_read)(struct gb_s*, const uint_fast32_t),
void (*gb_cart_ram_write)(struct gb_s*, const uint_fast32_t, const uint8_t),
void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t),
@@ -3731,7 +3722,6 @@ enum gb_init_error_e gb_init(struct gb_s *gb,
* some early homebrew ROMs supposedly may use this value. */
const uint8_t num_ram_banks[] = { 0, 1, 1, 4, 16, 8 };
gb->gb_rom_read = gb_rom_read;
gb->gb_cart_ram_read = gb_cart_ram_read;
gb->gb_cart_ram_write = gb_cart_ram_write;
gb->gb_error = gb_error;
@@ -3751,24 +3741,24 @@ enum gb_init_error_e gb_init(struct gb_s *gb,
uint16_t i;
for(i = 0x0134; i <= 0x014C; i++)
x = x - gb->gb_rom_read(gb, i) - 1;
x = x - gb_rom_read(gb, i) - 1;
if(x != gb->gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC))
if(x != gb_rom_read(gb, ROM_HEADER_CHECKSUM_LOC))
return GB_INIT_INVALID_CHECKSUM;
}
/* Check if cartridge type is supported, and set MBC type. */
{
const uint8_t mbc_value = gb->gb_rom_read(gb, mbc_location);
const uint8_t mbc_value = gb_rom_read(gb, mbc_location);
if(mbc_value > sizeof(cart_mbc) - 1 ||
(gb->mbc = cart_mbc[mbc_value]) == -1)
return GB_INIT_CARTRIDGE_UNSUPPORTED;
}
gb->num_rom_banks_mask = num_rom_banks_mask[gb->gb_rom_read(gb, bank_count_location)] - 1;
gb->cart_ram = cart_ram[gb->gb_rom_read(gb, mbc_location)];
gb->num_ram_banks = num_ram_banks[gb->gb_rom_read(gb, ram_size_location)];
gb->num_rom_banks_mask = num_rom_banks_mask[gb_rom_read(gb, bank_count_location)] - 1;
gb->cart_ram = cart_ram[gb_rom_read(gb, mbc_location)];
gb->num_ram_banks = num_ram_banks[gb_rom_read(gb, ram_size_location)];
/* If the ROM says that it support RAM, but has 0 RAM banks, then
* disable RAM reads from the cartridge. */
@@ -3804,7 +3794,7 @@ const char* gb_get_rom_name(struct gb_s* gb, char *title_str)
for(; title_loc <= title_end; title_loc++)
{
const char title_char = gb->gb_rom_read(gb, title_loc);
const char title_char = gb_rom_read(gb, title_loc);
if(title_char >= ' ' && title_char <= '_')
{
@@ -3886,7 +3876,6 @@ void gb_set_rtc(struct gb_s *gb, const struct tm * const time)
* \returns 0 on success or an enum that describes the error.
*/
enum gb_init_error_e gb_init(struct gb_s *gb,
uint8_t (*gb_rom_read)(struct gb_s*, const uint_fast32_t),
uint8_t (*gb_cart_ram_read)(struct gb_s*, const uint_fast32_t),
void (*gb_cart_ram_write)(struct gb_s*, const uint_fast32_t, const uint8_t),
void (*gb_error)(struct gb_s*, const enum gb_error_e, const uint16_t),

View File

@@ -36,6 +36,47 @@
#define GB_PERF_ONLY(...)
#endif
static constexpr uint8_t kPattern2x2[4] = {0, 2, 3, 1}; // [ (y&1)<<1 | (x&1) ]
// exact predicate per your shouldPixelBeOn
static constexpr uint8_t on_bit(uint8_t v, uint8_t threshold) {
if (v >= 3)
return 1;
if (v == 0)
return 0;
return static_cast<uint8_t>(v > threshold);
}
// Build 4 output bits (MSB = first pixel) from 4 packed 2-bit pixels.
// packed4 layout: p0 | p1<<2 | p2<<4 | p3<<6
static constexpr uint8_t makeNibble(uint8_t packed4, int xParity, int yParity) {
uint8_t out = 0;
int xp = xParity;
for (int i = 0; i < 4; ++i) {
const uint8_t v = (packed4 >> (i * 2)) & 0x3;
const uint8_t th = kPattern2x2[(yParity << 1) | (xp & 1)];
out = static_cast<uint8_t>((out << 1) | on_bit(v, th));
xp ^= 1; // toggle x parity each pixel
}
return out;
}
using LUT256 = std::array<uint8_t, 256>;
using LUT2x256 = std::array<LUT256, 2>;
using LUTFull = std::array<LUT2x256, 2>;
static constexpr LUTFull buildNibbleLUT() {
LUTFull L{};
for (int yp = 0; yp < 2; ++yp)
for (int xp = 0; xp < 2; ++xp)
for (int p = 0; p < 256; ++p)
L[yp][xp][p] = makeNibble(static_cast<uint8_t>(p), xp, yp);
return L;
}
inline constexpr LUTFull kNibbleLUT = buildNibbleLUT();
namespace apps {
namespace {
@@ -813,8 +854,8 @@ private:
}
std::memset(&gb, 0, sizeof(gb));
const auto initResult = gb_init(&gb, &GameboyApp::romRead, &GameboyApp::cartRamRead, &GameboyApp::cartRamWrite,
&GameboyApp::errorCallback, this);
const auto initResult =
gb_init(&gb, &GameboyApp::cartRamRead, &GameboyApp::cartRamWrite, &GameboyApp::errorCallback, this);
if (initResult != GB_INIT_NO_ERROR) {
setStatus(initErrorToString(initResult));
romData.clear();
@@ -1125,53 +1166,83 @@ private:
}
static void drawLineOriginal(GameboyApp& self, const uint8_t pixels[160], int srcLine) {
Framebuffer& fb = self.framebuffer;
const int dstY = kOriginalOffsetY + srcLine;
const int baseX = kOriginalOffsetX;
Framebuffer& fb = self.framebuffer;
const int dstY = kOriginalOffsetY + srcLine;
const int baseX = kOriginalOffsetX;
const int yParity = dstY & 1;
CARDBOY_CHECK((baseX % 8) == 0);
CARDBOY_CHECK((LCD_WIDTH % 8) == 0); // 160 is fine
int x = 0;
while (x < LCD_WIDTH) {
uint8_t pack = 0;
for (int i = 0; i < 8; ++i, ++x) {
const bool on = shouldPixelBeOn(pixels[x], baseX + x, dstY);
pack = static_cast<uint8_t>((pack << 1) | (on ? 1 : 0));
}
fb.drawBits8(baseX + x - 8, dstY, pack);
// Build two 4-pixel packs (2 bits per pixel → 8-bit pack)
uint8_t p4a = pixels[x + 0] | (pixels[x + 1] << 2) | (pixels[x + 2] << 4) | (pixels[x + 3] << 6);
uint8_t p4b = pixels[x + 4] | (pixels[x + 5] << 2) | (pixels[x + 6] << 4) | (pixels[x + 7] << 6);
const int xParity = (baseX + x) & 1; // start parity for this byte
const uint8_t n0 = kNibbleLUT[yParity][xParity][p4a];
const uint8_t n1 = kNibbleLUT[yParity][xParity][p4b]; // same parity after 4 pixels
const uint8_t pack = static_cast<uint8_t>((n0 << 4) | (n1 & 0x0F));
fb.drawBits8(baseX + x, dstY, pack);
x += 8;
}
}
static void drawLineFullHeight(GameboyApp& self, const uint8_t pixels[160], int srcLine) {
Framebuffer& fb = self.framebuffer;
int yStart = kFullHeightRowBounds[static_cast<std::size_t>(srcLine)];
int yEnd = kFullHeightRowBounds[static_cast<std::size_t>(srcLine) + 1];
const int yStart = kFullHeightRowBounds[static_cast<std::size_t>(srcLine)];
const int yEnd = kFullHeightRowBounds[static_cast<std::size_t>(srcLine) + 1];
CARDBOY_CHECK(yEnd > yStart);
CARDBOY_CHECK((kFullHeightColumnBounds[0] % 8) == 0);
for (int dstY = yStart; dstY < yEnd; ++dstY) {
uint8_t pack = 0;
int bitsCollected = 0;
std::array<uint8_t, 8> vals{}; // collected 2-bit pixel values for this output byte
int collected = 0;
const int yParity = dstY & 1;
for (int x = 0; x < LCD_WIDTH; ++x) {
const int colStart = kFullHeightColumnBounds[static_cast<std::size_t>(x)];
const int colEnd = kFullHeightColumnBounds[static_cast<std::size_t>(x) + 1];
CARDBOY_CHECK(colEnd > colStart);
// expand this source pixel across its horizontal span
const uint8_t v = static_cast<uint8_t>(pixels[x]);
for (int dstX = colStart; dstX < colEnd; ++dstX) {
const bool on = shouldPixelBeOn(pixels[x], dstX, dstY);
pack = static_cast<uint8_t>((pack << 1) | (on ? 1 : 0));
++bitsCollected;
if (bitsCollected == 8) {
vals[static_cast<std::size_t>(collected)] = v;
++collected;
if (collected == 8) {
const int byteStart = dstX - 7;
CARDBOY_CHECK((byteStart % 8) == 0);
const int xParity = byteStart & 1;
// build two 4-pixel packs from the 8 collected values (mask to 2 bits)
const uint8_t p4a = (vals[0]) | ((vals[1]) << 2) | ((vals[2]) << 4) | ((vals[3]) << 6);
const uint8_t p4b = (vals[4]) | ((vals[5]) << 2) | ((vals[6]) << 4) | ((vals[7]) << 6);
// two LUT hits → two nibbles → one byte
const uint8_t n0 = kNibbleLUT[yParity][xParity][p4a];
const uint8_t n1 = kNibbleLUT[yParity][xParity][p4b];
const uint8_t pack = static_cast<uint8_t>((n0 << 4) | (n1 & 0x0F));
fb.drawBits8(byteStart, dstY, pack);
pack = 0;
bitsCollected = 0;
collected = 0; // reset for next byte
}
}
}
CARDBOY_CHECK(bitsCollected == 0);
CARDBOY_CHECK(collected == 0); // must end on byte boundary
}
}
public:
static uint8_t romRead(struct gb_s* gb, const uint_fast32_t addr) {
auto* self = fromGb(gb);
CARDBOY_CHECK_CODE(if (!self) return 0xFF;
@@ -1181,6 +1252,7 @@ private:
return self->romDataView[static_cast<std::size_t>(addr)];
}
private:
static uint8_t cartRamRead(struct gb_s* gb, const uint_fast32_t addr) {
auto* self = fromGb(gb);
if (!self)
@@ -1262,6 +1334,9 @@ private:
}
};
extern "C" __attribute__((always_inline)) uint8_t gb_rom_read(struct gb_s* gb, const uint_fast32_t addr) {
return GameboyApp::romRead(gb, addr);
}
class GameboyAppFactory final : public cardboy::sdk::IAppFactory {
public:
const char* name() const override { return kGameboyAppName; }