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/sdk/app_framework.hpp"
|
||||
#include "cardboy/sdk/app_system.hpp"
|
||||
#include "cardboy/sdk/display_spec.hpp"
|
||||
#include "cardboy/sdk/services.hpp"
|
||||
#include "cardboy/utils/utils.hpp"
|
||||
|
||||
@@ -116,7 +117,6 @@ public:
|
||||
statusMessage.clear();
|
||||
resetFpsStats();
|
||||
scaleMode = ScaleMode::Original;
|
||||
geometryDirty = true;
|
||||
ensureFilesystemReady();
|
||||
refreshRomList();
|
||||
mode = Mode::Browse;
|
||||
@@ -180,9 +180,7 @@ public:
|
||||
}
|
||||
|
||||
|
||||
GB_PERF_ONLY(const uint64_t geometryStartUs = nowMicros();)
|
||||
ensureRenderGeometry();
|
||||
GB_PERF_ONLY(perf.geometryUs = nowMicros() - geometryStartUs;)
|
||||
GB_PERF_ONLY(perf.geometryUs = 0;)
|
||||
|
||||
GB_PERF_ONLY(const uint64_t runStartUs = nowMicros();)
|
||||
gb_run_frame(&gb);
|
||||
@@ -447,20 +445,34 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
struct RenderGeometry {
|
||||
float scaleX = 1.0f;
|
||||
float scaleY = 1.0f;
|
||||
int scaledWidth = LCD_WIDTH;
|
||||
int scaledHeight = LCD_HEIGHT;
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
int leftMargin = 0;
|
||||
int rightMargin = 0;
|
||||
std::array<int, LCD_HEIGHT> lineYStart{};
|
||||
std::array<int, LCD_HEIGHT> lineYEnd{};
|
||||
std::array<int, LCD_WIDTH> colXStart{};
|
||||
std::array<int, LCD_WIDTH> colXEnd{};
|
||||
};
|
||||
static constexpr int kOriginalOffsetX = (cardboy::sdk::kDisplayWidth - LCD_WIDTH) / 2;
|
||||
static constexpr int kOriginalOffsetY = (cardboy::sdk::kDisplayHeight - LCD_HEIGHT) / 2;
|
||||
static constexpr int kFullHeightScaledWidth =
|
||||
(((LCD_WIDTH * cardboy::sdk::kDisplayHeight + LCD_HEIGHT / 2) / LCD_HEIGHT) + 7) & ~7;
|
||||
static constexpr int kFullHeightOffsetX = (((cardboy::sdk::kDisplayWidth - kFullHeightScaledWidth) / 2) / 8) * 8;
|
||||
|
||||
static_assert(kFullHeightScaledWidth % 8 == 0);
|
||||
static_assert(kFullHeightOffsetX % 8 == 0);
|
||||
static_assert(kOriginalOffsetX % 8 == 0);
|
||||
static_assert(kFullHeightOffsetX + kFullHeightScaledWidth <= cardboy::sdk::kDisplayWidth);
|
||||
|
||||
inline static constexpr std::array<int, LCD_WIDTH + 1> kFullHeightColumnBounds = []() constexpr {
|
||||
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;
|
||||
Framebuffer& framebuffer;
|
||||
@@ -473,8 +485,6 @@ private:
|
||||
|
||||
Mode mode = Mode::Browse;
|
||||
ScaleMode scaleMode = ScaleMode::Original;
|
||||
bool geometryDirty = true;
|
||||
RenderGeometry geometry{};
|
||||
std::vector<RomEntry> roms;
|
||||
std::size_t selectedIndex = 0;
|
||||
bool browserDirty = true;
|
||||
@@ -657,86 +667,10 @@ private:
|
||||
scaleMode = ScaleMode::FullHeight;
|
||||
else
|
||||
scaleMode = ScaleMode::Original;
|
||||
geometryDirty = true;
|
||||
frameDirty = true;
|
||||
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) {
|
||||
if (input.select && !prevInput.select) {
|
||||
refreshRomList();
|
||||
@@ -912,7 +846,6 @@ private:
|
||||
gbReady = true;
|
||||
mode = Mode::Running;
|
||||
frameDirty = true;
|
||||
geometryDirty = true;
|
||||
activeRomName = rom.name.empty() ? "Game" : rom.name;
|
||||
|
||||
std::string statusText = "Running " + activeRomName;
|
||||
@@ -931,7 +864,6 @@ private:
|
||||
cartRam.clear();
|
||||
activeRomName.clear();
|
||||
activeRomSavePath.clear();
|
||||
geometryDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -945,7 +877,6 @@ private:
|
||||
cartRam.clear();
|
||||
activeRomName.clear();
|
||||
activeRomSavePath.clear();
|
||||
geometryDirty = true;
|
||||
std::memset(&gb, 0, sizeof(gb));
|
||||
mode = Mode::Browse;
|
||||
browserDirty = true;
|
||||
@@ -992,8 +923,6 @@ private:
|
||||
return;
|
||||
frameDirty = false;
|
||||
|
||||
ensureRenderGeometry();
|
||||
|
||||
++fpsFrameCounter;
|
||||
++totalFrameCounter;
|
||||
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";
|
||||
|
||||
if (scaleMode == ScaleMode::FullHeight) {
|
||||
const auto& geom = geometry;
|
||||
const int textScale = 1;
|
||||
const int rotatedWidth = font16x8::kGlyphHeight * textScale;
|
||||
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 fpsLineGap = 4;
|
||||
@@ -1202,6 +1124,54 @@ private:
|
||||
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) {
|
||||
auto* self = fromGb(gb);
|
||||
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],
|
||||
const uint_fast8_t line) {
|
||||
auto* self = fromGb(gb);
|
||||
if (!self || line >= LCD_HEIGHT)
|
||||
return;
|
||||
CARDBOY_CHECK(self && line < LCD_HEIGHT);
|
||||
|
||||
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;
|
||||
|
||||
Framebuffer& fb = self->framebuffer;
|
||||
fb.frameReady();
|
||||
|
||||
if (geom.scaledWidth == LCD_WIDTH && geom.scaledHeight == LCD_HEIGHT) {
|
||||
const int dstY = yStart;
|
||||
const int dstXBase = geom.offsetX;
|
||||
for (int x = 0; x < LCD_WIDTH; ++x) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (self->scaleMode == ScaleMode::FullHeight)
|
||||
drawLineFullHeight(*self, pixels, static_cast<int>(line));
|
||||
else
|
||||
drawLineOriginal(*self, pixels, static_cast<int>(line));
|
||||
}
|
||||
|
||||
static const char* initErrorToString(enum gb_init_error_e err) {
|
||||
|
||||
Reference in New Issue
Block a user