mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
a little faster gameboy
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
#include "cardboy/gfx/font16x8.hpp"
|
#include "cardboy/gfx/font16x8.hpp"
|
||||||
#include "cardboy/sdk/app_framework.hpp"
|
#include "cardboy/sdk/app_framework.hpp"
|
||||||
#include "cardboy/sdk/app_system.hpp"
|
#include "cardboy/sdk/app_system.hpp"
|
||||||
|
#include "cardboy/sdk/display_spec.hpp"
|
||||||
#include "cardboy/sdk/services.hpp"
|
#include "cardboy/sdk/services.hpp"
|
||||||
#include "cardboy/utils/utils.hpp"
|
#include "cardboy/utils/utils.hpp"
|
||||||
|
|
||||||
@@ -115,8 +116,7 @@ public:
|
|||||||
prevInput = {};
|
prevInput = {};
|
||||||
statusMessage.clear();
|
statusMessage.clear();
|
||||||
resetFpsStats();
|
resetFpsStats();
|
||||||
scaleMode = ScaleMode::Original;
|
scaleMode = ScaleMode::Original;
|
||||||
geometryDirty = true;
|
|
||||||
ensureFilesystemReady();
|
ensureFilesystemReady();
|
||||||
refreshRomList();
|
refreshRomList();
|
||||||
mode = Mode::Browse;
|
mode = Mode::Browse;
|
||||||
@@ -180,9 +180,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GB_PERF_ONLY(const uint64_t geometryStartUs = nowMicros();)
|
GB_PERF_ONLY(perf.geometryUs = 0;)
|
||||||
ensureRenderGeometry();
|
|
||||||
GB_PERF_ONLY(perf.geometryUs = nowMicros() - geometryStartUs;)
|
|
||||||
|
|
||||||
GB_PERF_ONLY(const uint64_t runStartUs = nowMicros();)
|
GB_PERF_ONLY(const uint64_t runStartUs = nowMicros();)
|
||||||
gb_run_frame(&gb);
|
gb_run_frame(&gb);
|
||||||
@@ -447,20 +445,34 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RenderGeometry {
|
static constexpr int kOriginalOffsetX = (cardboy::sdk::kDisplayWidth - LCD_WIDTH) / 2;
|
||||||
float scaleX = 1.0f;
|
static constexpr int kOriginalOffsetY = (cardboy::sdk::kDisplayHeight - LCD_HEIGHT) / 2;
|
||||||
float scaleY = 1.0f;
|
static constexpr int kFullHeightScaledWidth =
|
||||||
int scaledWidth = LCD_WIDTH;
|
(((LCD_WIDTH * cardboy::sdk::kDisplayHeight + LCD_HEIGHT / 2) / LCD_HEIGHT) + 7) & ~7;
|
||||||
int scaledHeight = LCD_HEIGHT;
|
static constexpr int kFullHeightOffsetX = (((cardboy::sdk::kDisplayWidth - kFullHeightScaledWidth) / 2) / 8) * 8;
|
||||||
int offsetX = 0;
|
|
||||||
int offsetY = 0;
|
static_assert(kFullHeightScaledWidth % 8 == 0);
|
||||||
int leftMargin = 0;
|
static_assert(kFullHeightOffsetX % 8 == 0);
|
||||||
int rightMargin = 0;
|
static_assert(kOriginalOffsetX % 8 == 0);
|
||||||
std::array<int, LCD_HEIGHT> lineYStart{};
|
static_assert(kFullHeightOffsetX + kFullHeightScaledWidth <= cardboy::sdk::kDisplayWidth);
|
||||||
std::array<int, LCD_HEIGHT> lineYEnd{};
|
|
||||||
std::array<int, LCD_WIDTH> colXStart{};
|
inline static constexpr std::array<int, LCD_WIDTH + 1> kFullHeightColumnBounds = []() constexpr {
|
||||||
std::array<int, LCD_WIDTH> colXEnd{};
|
std::array<int, LCD_WIDTH + 1> bounds{};
|
||||||
};
|
for (int x = 0; x <= LCD_WIDTH; ++x)
|
||||||
|
bounds[static_cast<std::size_t>(x)] = kFullHeightOffsetX + (kFullHeightScaledWidth * x) / LCD_WIDTH;
|
||||||
|
return bounds;
|
||||||
|
}();
|
||||||
|
|
||||||
|
inline static constexpr std::array<int, LCD_HEIGHT + 1> kFullHeightRowBounds = []() constexpr {
|
||||||
|
std::array<int, LCD_HEIGHT + 1> bounds{};
|
||||||
|
for (int y = 0; y <= LCD_HEIGHT; ++y)
|
||||||
|
bounds[static_cast<std::size_t>(y)] = (cardboy::sdk::kDisplayHeight * y) / LCD_HEIGHT;
|
||||||
|
return bounds;
|
||||||
|
}();
|
||||||
|
|
||||||
|
static_assert(kFullHeightColumnBounds[0] % 8 == 0);
|
||||||
|
static_assert(kFullHeightColumnBounds[LCD_WIDTH] - kFullHeightColumnBounds[0] == kFullHeightScaledWidth);
|
||||||
|
static_assert(kFullHeightRowBounds[LCD_HEIGHT] == cardboy::sdk::kDisplayHeight);
|
||||||
|
|
||||||
AppContext& context;
|
AppContext& context;
|
||||||
Framebuffer& framebuffer;
|
Framebuffer& framebuffer;
|
||||||
@@ -471,10 +483,8 @@ private:
|
|||||||
int64_t frameDelayCarryUs = 0;
|
int64_t frameDelayCarryUs = 0;
|
||||||
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
static constexpr uint32_t kTargetFrameUs = 1000000 / 60; // ~16.6 ms
|
||||||
|
|
||||||
Mode mode = Mode::Browse;
|
Mode mode = Mode::Browse;
|
||||||
ScaleMode scaleMode = ScaleMode::Original;
|
ScaleMode scaleMode = ScaleMode::Original;
|
||||||
bool geometryDirty = true;
|
|
||||||
RenderGeometry geometry{};
|
|
||||||
std::vector<RomEntry> roms;
|
std::vector<RomEntry> roms;
|
||||||
std::size_t selectedIndex = 0;
|
std::size_t selectedIndex = 0;
|
||||||
bool browserDirty = true;
|
bool browserDirty = true;
|
||||||
@@ -657,86 +667,10 @@ private:
|
|||||||
scaleMode = ScaleMode::FullHeight;
|
scaleMode = ScaleMode::FullHeight;
|
||||||
else
|
else
|
||||||
scaleMode = ScaleMode::Original;
|
scaleMode = ScaleMode::Original;
|
||||||
geometryDirty = true;
|
frameDirty = true;
|
||||||
frameDirty = true;
|
|
||||||
setStatus(scaleMode == ScaleMode::FullHeight ? "Scale: Full height" : "Scale: Original");
|
setStatus(scaleMode == ScaleMode::FullHeight ? "Scale: Full height" : "Scale: Original");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ensureRenderGeometry() {
|
|
||||||
if (!geometryDirty)
|
|
||||||
return;
|
|
||||||
geometryDirty = false;
|
|
||||||
|
|
||||||
auto& geom = geometry;
|
|
||||||
const int fbWidth = framebuffer.width();
|
|
||||||
const int fbHeight = framebuffer.height();
|
|
||||||
const auto resetGeom = [&]() {
|
|
||||||
geom.scaleX = 1.0f;
|
|
||||||
geom.scaleY = 1.0f;
|
|
||||||
geom.scaledWidth = 0;
|
|
||||||
geom.scaledHeight = 0;
|
|
||||||
geom.offsetX = 0;
|
|
||||||
geom.offsetY = 0;
|
|
||||||
geom.leftMargin = 0;
|
|
||||||
geom.rightMargin = 0;
|
|
||||||
std::fill(geom.lineYStart.begin(), geom.lineYStart.end(), 0);
|
|
||||||
std::fill(geom.lineYEnd.begin(), geom.lineYEnd.end(), 0);
|
|
||||||
std::fill(geom.colXStart.begin(), geom.colXStart.end(), 0);
|
|
||||||
std::fill(geom.colXEnd.begin(), geom.colXEnd.end(), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fbWidth <= 0 || fbHeight <= 0) {
|
|
||||||
resetGeom();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int scaledWidth;
|
|
||||||
int scaledHeight;
|
|
||||||
if (scaleMode == ScaleMode::FullHeight) {
|
|
||||||
int targetHeight = fbHeight;
|
|
||||||
int targetWidth = static_cast<int>((static_cast<int64_t>(LCD_WIDTH) * targetHeight + LCD_HEIGHT / 2) /
|
|
||||||
std::max(1, LCD_HEIGHT));
|
|
||||||
if (targetWidth > fbWidth) {
|
|
||||||
targetWidth = fbWidth;
|
|
||||||
targetHeight = static_cast<int>((static_cast<int64_t>(LCD_HEIGHT) * targetWidth + LCD_WIDTH / 2) /
|
|
||||||
std::max(1, LCD_WIDTH));
|
|
||||||
}
|
|
||||||
scaledWidth = std::clamp(targetWidth, 1, fbWidth);
|
|
||||||
scaledHeight = std::clamp(targetHeight, 1, fbHeight);
|
|
||||||
} else {
|
|
||||||
scaledWidth = std::clamp(fbWidth, 1, LCD_WIDTH);
|
|
||||||
scaledHeight = std::clamp(fbHeight, 1, LCD_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
geom.scaledWidth = scaledWidth;
|
|
||||||
geom.scaledHeight = scaledHeight;
|
|
||||||
geom.offsetX = std::max(0, (fbWidth - scaledWidth) / 2);
|
|
||||||
geom.offsetY = std::max(0, (fbHeight - scaledHeight) / 2);
|
|
||||||
geom.leftMargin = geom.offsetX;
|
|
||||||
geom.rightMargin = std::max(0, fbWidth - (geom.offsetX + scaledWidth));
|
|
||||||
geom.scaleX = static_cast<float>(scaledWidth) / static_cast<float>(LCD_WIDTH);
|
|
||||||
geom.scaleY = static_cast<float>(scaledHeight) / static_cast<float>(LCD_HEIGHT);
|
|
||||||
|
|
||||||
for (int srcLine = 0; srcLine < LCD_HEIGHT; ++srcLine) {
|
|
||||||
int start = geom.offsetY + static_cast<int>((static_cast<int64_t>(scaledHeight) * srcLine) / LCD_HEIGHT);
|
|
||||||
int end =
|
|
||||||
geom.offsetY + static_cast<int>((static_cast<int64_t>(scaledHeight) * (srcLine + 1)) / LCD_HEIGHT);
|
|
||||||
start = std::clamp(start, 0, fbHeight);
|
|
||||||
end = std::clamp(end, start, fbHeight);
|
|
||||||
geom.lineYStart[srcLine] = start;
|
|
||||||
geom.lineYEnd[srcLine] = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int srcCol = 0; srcCol < LCD_WIDTH; ++srcCol) {
|
|
||||||
int start = geom.offsetX + static_cast<int>((static_cast<int64_t>(scaledWidth) * srcCol) / LCD_WIDTH);
|
|
||||||
int end = geom.offsetX + static_cast<int>((static_cast<int64_t>(scaledWidth) * (srcCol + 1)) / LCD_WIDTH);
|
|
||||||
start = std::clamp(start, 0, fbWidth);
|
|
||||||
end = std::clamp(end, start, fbWidth);
|
|
||||||
geom.colXStart[srcCol] = start;
|
|
||||||
geom.colXEnd[srcCol] = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleBrowserInput(const InputState& input) {
|
void handleBrowserInput(const InputState& input) {
|
||||||
if (input.select && !prevInput.select) {
|
if (input.select && !prevInput.select) {
|
||||||
refreshRomList();
|
refreshRomList();
|
||||||
@@ -912,7 +846,6 @@ private:
|
|||||||
gbReady = true;
|
gbReady = true;
|
||||||
mode = Mode::Running;
|
mode = Mode::Running;
|
||||||
frameDirty = true;
|
frameDirty = true;
|
||||||
geometryDirty = true;
|
|
||||||
activeRomName = rom.name.empty() ? "Game" : rom.name;
|
activeRomName = rom.name.empty() ? "Game" : rom.name;
|
||||||
|
|
||||||
std::string statusText = "Running " + activeRomName;
|
std::string statusText = "Running " + activeRomName;
|
||||||
@@ -931,7 +864,6 @@ private:
|
|||||||
cartRam.clear();
|
cartRam.clear();
|
||||||
activeRomName.clear();
|
activeRomName.clear();
|
||||||
activeRomSavePath.clear();
|
activeRomSavePath.clear();
|
||||||
geometryDirty = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +877,6 @@ private:
|
|||||||
cartRam.clear();
|
cartRam.clear();
|
||||||
activeRomName.clear();
|
activeRomName.clear();
|
||||||
activeRomSavePath.clear();
|
activeRomSavePath.clear();
|
||||||
geometryDirty = true;
|
|
||||||
std::memset(&gb, 0, sizeof(gb));
|
std::memset(&gb, 0, sizeof(gb));
|
||||||
mode = Mode::Browse;
|
mode = Mode::Browse;
|
||||||
browserDirty = true;
|
browserDirty = true;
|
||||||
@@ -992,8 +923,6 @@ private:
|
|||||||
return;
|
return;
|
||||||
frameDirty = false;
|
frameDirty = false;
|
||||||
|
|
||||||
ensureRenderGeometry();
|
|
||||||
|
|
||||||
++fpsFrameCounter;
|
++fpsFrameCounter;
|
||||||
++totalFrameCounter;
|
++totalFrameCounter;
|
||||||
const uint32_t nowMs = context.clock.millis();
|
const uint32_t nowMs = context.clock.millis();
|
||||||
@@ -1015,16 +944,9 @@ private:
|
|||||||
const std::string scaleHint = (scaleMode == ScaleMode::FullHeight) ? "START+B NORMAL" : "START+B SCALE";
|
const std::string scaleHint = (scaleMode == ScaleMode::FullHeight) ? "START+B NORMAL" : "START+B SCALE";
|
||||||
|
|
||||||
if (scaleMode == ScaleMode::FullHeight) {
|
if (scaleMode == ScaleMode::FullHeight) {
|
||||||
const auto& geom = geometry;
|
const int textScale = 1;
|
||||||
const int textScale = 1;
|
const int screenHeight = framebuffer.height();
|
||||||
const int rotatedWidth = font16x8::kGlyphHeight * textScale;
|
const int screenWidth = framebuffer.width();
|
||||||
const int screenHeight = framebuffer.height();
|
|
||||||
const int screenWidth = framebuffer.width();
|
|
||||||
const int leftMargin = std::max(0, geom.leftMargin);
|
|
||||||
const int rightMargin = std::max(0, geom.rightMargin);
|
|
||||||
const int maxLeftX = std::max(0, screenWidth - rotatedWidth);
|
|
||||||
const int maxRightXBase = std::max(0, screenWidth - rotatedWidth);
|
|
||||||
|
|
||||||
|
|
||||||
const int horizontalPadding = 8;
|
const int horizontalPadding = 8;
|
||||||
const int fpsLineGap = 4;
|
const int fpsLineGap = 4;
|
||||||
@@ -1202,6 +1124,54 @@ private:
|
|||||||
return value > threshold;
|
return value > threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
CARDBOY_CHECK((baseX % 8) == 0);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
CARDBOY_CHECK(yEnd > yStart);
|
||||||
|
CARDBOY_CHECK((kFullHeightColumnBounds[0] % 8) == 0);
|
||||||
|
|
||||||
|
for (int dstY = yStart; dstY < yEnd; ++dstY) {
|
||||||
|
uint8_t pack = 0;
|
||||||
|
int bitsCollected = 0;
|
||||||
|
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);
|
||||||
|
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) {
|
||||||
|
const int byteStart = dstX - 7;
|
||||||
|
CARDBOY_CHECK((byteStart % 8) == 0);
|
||||||
|
fb.drawBits8(byteStart, dstY, pack);
|
||||||
|
pack = 0;
|
||||||
|
bitsCollected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CARDBOY_CHECK(bitsCollected == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t romRead(struct gb_s* gb, const uint_fast32_t addr) {
|
static uint8_t romRead(struct gb_s* gb, const uint_fast32_t addr) {
|
||||||
auto* self = fromGb(gb);
|
auto* self = fromGb(gb);
|
||||||
CARDBOY_CHECK_CODE(if (!self) return 0xFF;
|
CARDBOY_CHECK_CODE(if (!self) return 0xFF;
|
||||||
@@ -1246,49 +1216,19 @@ private:
|
|||||||
__attribute__((optimize("Ofast"))) static void lcdDrawLine(struct gb_s* gb, const uint8_t pixels[160],
|
__attribute__((optimize("Ofast"))) static void lcdDrawLine(struct gb_s* gb, const uint8_t pixels[160],
|
||||||
const uint_fast8_t line) {
|
const uint_fast8_t line) {
|
||||||
auto* self = fromGb(gb);
|
auto* self = fromGb(gb);
|
||||||
if (!self || line >= LCD_HEIGHT)
|
CARDBOY_CHECK(self && line < LCD_HEIGHT);
|
||||||
return;
|
|
||||||
|
|
||||||
ScopedCallbackTimer timer(self, PerfTracker::CallbackKind::LcdDraw);
|
ScopedCallbackTimer timer(self, PerfTracker::CallbackKind::LcdDraw);
|
||||||
|
|
||||||
self->ensureRenderGeometry();
|
|
||||||
const auto& geom = self->geometry;
|
|
||||||
if (geom.scaledWidth <= 0 || geom.scaledHeight <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int yStart = geom.lineYStart[line];
|
|
||||||
const int yEnd = geom.lineYEnd[line];
|
|
||||||
if (yStart >= yEnd)
|
|
||||||
return;
|
|
||||||
|
|
||||||
self->frameDirty = true;
|
self->frameDirty = true;
|
||||||
|
|
||||||
Framebuffer& fb = self->framebuffer;
|
Framebuffer& fb = self->framebuffer;
|
||||||
fb.frameReady();
|
fb.frameReady();
|
||||||
|
|
||||||
if (geom.scaledWidth == LCD_WIDTH && geom.scaledHeight == LCD_HEIGHT) {
|
if (self->scaleMode == ScaleMode::FullHeight)
|
||||||
const int dstY = yStart;
|
drawLineFullHeight(*self, pixels, static_cast<int>(line));
|
||||||
const int dstXBase = geom.offsetX;
|
else
|
||||||
for (int x = 0; x < LCD_WIDTH; ++x) {
|
drawLineOriginal(*self, pixels, static_cast<int>(line));
|
||||||
const bool on = shouldPixelBeOn(pixels[x], dstXBase + x, dstY);
|
|
||||||
fb.drawPixel(dstXBase + x, dstY, on);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& colStart = geom.colXStart;
|
|
||||||
const auto& colEnd = geom.colXEnd;
|
|
||||||
for (int x = 0; x < LCD_WIDTH; ++x) {
|
|
||||||
const int drawStart = colStart[x];
|
|
||||||
const int drawEnd = colEnd[x];
|
|
||||||
if (drawStart >= drawEnd)
|
|
||||||
continue;
|
|
||||||
for (int dstY = yStart; dstY < yEnd; ++dstY)
|
|
||||||
for (int dstX = drawStart; dstX < drawEnd; ++dstX) {
|
|
||||||
const bool on = shouldPixelBeOn(pixels[x], dstX, dstY);
|
|
||||||
fb.drawPixel(dstX, dstY, on);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* initErrorToString(enum gb_init_error_e err) {
|
static const char* initErrorToString(enum gb_init_error_e err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user