From ed0aca8f481d6bf1cd7c64e7d5c92181f608fa0f Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 20:41:46 +0300 Subject: [PATCH 01/31] Add initial software renderer implementation. Taken from https://github.com/iProgramMC/Butterscotch/tree/sw-renderer-old. --- CMakeLists.txt | 27 +- src/sw/debug_utils.c | 44 ++ src/sw/defines.h | 8 + src/sw/fb_convert.c | 104 +++ src/sw/fb_convert.h | 13 + src/sw/pixel_convert.h | 90 +++ src/sw/sw_renderer.c | 1630 ++++++++++++++++++++++++++++++++++++++++ src/sw/sw_renderer.h | 7 + src/utils.h | 5 + 9 files changed, 1920 insertions(+), 8 deletions(-) create mode 100755 src/sw/debug_utils.c create mode 100755 src/sw/defines.h create mode 100755 src/sw/fb_convert.c create mode 100755 src/sw/fb_convert.h create mode 100755 src/sw/pixel_convert.h create mode 100755 src/sw/sw_renderer.c create mode 100755 src/sw/sw_renderer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f2c18a3a..e3340df1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,15 +228,26 @@ if(PLATFORM STREQUAL "glfw") $/gamecontrollerdb.txt ) elseif(PLATFORM STREQUAL "sdl") - if (NOT ENABLE_LEGACY_GL) - message(FATAL_ERROR "SDL requires legacy GL!") + option(ENABLE_SW_RENDERER "Enable the software renderer (SDL 1.2 only)" ON) + + if (NOT ENABLE_LEGACY_GL AND NOT ENABLE_SW_RENDERER) + message(FATAL_ERROR "SDL requires legacy GL or the software renderer!") + endif() + + if(ENABLE_LEGACY_GL) + file(GLOB GL_SOURCES src/gl_legacy/*.c src/gl_common/*.c src/image/*.c) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_legacy) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common) + target_sources(butterscotch PRIVATE ${GL_SOURCES}) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) + elseif(ENABLE_SW_RENDERER) + file(GLOB SWR_SOURCES src/sw/*.c src/image/*.c) + add_compile_definitions(ENABLE_SW_RENDERER) + target_sources(butterscotch PRIVATE ${SWR_SOURCES}) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/sw) + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) endif() - file(GLOB GL_SOURCES src/gl_legacy/*.c src/gl_common/*.c src/image/*.c) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_legacy) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common) - target_sources(butterscotch PRIVATE ${GL_SOURCES}) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) # Butterscotch VM/interpreter profiler option(ENABLE_VM_GML_PROFILER "Enable Butterscotch VM/interpreter profiler" ON) diff --git a/src/sw/debug_utils.c b/src/sw/debug_utils.c new file mode 100755 index 00000000..604dbe1c --- /dev/null +++ b/src/sw/debug_utils.c @@ -0,0 +1,44 @@ +#include +#include + +int debug_fprintf(FILE* stream, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + if (stream == stderr) + { + static char buffer[4096]; + int len = vsnprintf(buffer, sizeof(buffer), fmt, args); + + OutputDebugStringA(buffer); + + FILE* stderr2 = fopen("log.txt", "a+"); + fwrite(buffer, 1, strlen(buffer), stderr2); + fclose(stderr2); + + va_end(args); + return len; + } + + int len = vfprintf(stream, fmt, args); + va_end(args); + return len; +} + +int debug_printf(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + static char buffer[4096]; + int len = vsnprintf(buffer, sizeof(buffer), fmt, args); + OutputDebugStringA(buffer); + + FILE* stderr2 = fopen("log.txt", "a+"); + fwrite(buffer, 1, strlen(buffer), stderr2); + fclose(stderr2); + + va_end(args); + return len; +} diff --git a/src/sw/defines.h b/src/sw/defines.h new file mode 100755 index 00000000..9160d57e --- /dev/null +++ b/src/sw/defines.h @@ -0,0 +1,8 @@ +#pragma once + +#define LIKELY(cond) __builtin_expect(!!(cond), 1) +#define UNLIKELY(cond) __builtin_expect(!!(cond), 0) + +#define UNUSED __attribute__ ((unused)) +#define FORCE_INLINE static inline __attribute__((always_inline)) + diff --git a/src/sw/fb_convert.c b/src/sw/fb_convert.c new file mode 100755 index 00000000..9b0ba9f7 --- /dev/null +++ b/src/sw/fb_convert.c @@ -0,0 +1,104 @@ +#include +#include +#include "fb_convert.h" +#include "pixel_convert.h" +#include "utils.h" + +FORCE_INLINE UNUSED +uint16_t argb8888_to_rgb1555(uint32_t xl) +{ + Pixel32ARGB x; + x.l = xl; + return (x.p.b >> 3) | ((x.p.g >> 3) << 5) | ((x.p.r >> 3) << 10); +} + +FORCE_INLINE UNUSED +uint8_t argb8888_to_rgb332(uint32_t xl) +{ + Pixel32ARGB x; + x.l = xl; + return (x.p.r >> 5) | ((x.p.g >> 5) << 3) | ((x.p.b >> 6) << 6); +} + +uint8_t* swrConvert32to24(uint8_t* dest, uint32_t* src, int width, int height) +{ + int size = width * height; + if (!dest) { + dest = safeCalloc(3, size); + } + + for (int i = 0, j = 0; i < size; i++) + { + Pixel32ARGB x; + x.l = src[i]; + + dest[j++] = x.p.b; + dest[j++] = x.p.g; + dest[j++] = x.p.r; + } + + return dest; +} + +uint16_t* swrConvert32to16(uint16_t* dest, uint32_t* src, int width, int height) +{ + int size = width * height; + if (!dest) { + dest = safeCalloc(sizeof(uint16_t), size); + } + + for (int i = 0; i < size; i++) + dest[i] = argb8888_to_rgb1555(src[i]); + + return dest; +} + +uint8_t* swrConvert32to8(uint8_t* dest, uint32_t* src, int width, int height) +{ + int size = width * height; + if (!dest) { + dest = safeCalloc(sizeof(uint8_t), size); + } + + for (int i = 0; i < size; i++) + dest[i] = argb8888_to_rgb332(src[i]); + + return dest; +} + +typedef struct +{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; +} +BITMAPINFO_8BIT; + +BITMAPINFO* swrSetup8BitBitmapInfo(int width, int height) +{ + static BITMAPINFO_8BIT* pBmi; + if (!pBmi) { + pBmi = safeCalloc(1, sizeof(BITMAPINFO_8BIT)); + + // set up palette + int i = 0; + for (int b = 0; b < 4; b++) + for (int g = 0; g < 8; g++) + for (int r = 0; r < 8; r++) + { + pBmi->bmiColors[i].rgbRed = (r << 5) + (r << 2) + (r >> 1); + pBmi->bmiColors[i].rgbGreen = (g << 5) + (g << 2) + (g >> 1); + pBmi->bmiColors[i].rgbBlue = (b << 6) + (b << 4) + (b << 2) + b; + pBmi->bmiColors[i].rgbReserved = 255; + i++; + } + } + + pBmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pBmi->bmiHeader.biWidth = width; + pBmi->bmiHeader.biHeight = -height; + pBmi->bmiHeader.biPlanes = 1; + pBmi->bmiHeader.biCompression = BI_RGB; + pBmi->bmiHeader.biBitCount = 8; + + return (BITMAPINFO*) pBmi; +} diff --git a/src/sw/fb_convert.h b/src/sw/fb_convert.h new file mode 100755 index 00000000..dad12c66 --- /dev/null +++ b/src/sw/fb_convert.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct tagBITMAPINFO BITMAPINFO; + +uint8_t* swrConvert32to24(uint8_t* dest, uint32_t* src, int width, int height); + +uint16_t* swrConvert32to16(uint16_t* dest, uint32_t* src, int width, int height); + +uint8_t* swrConvert32to8(uint8_t* dest, uint32_t* src, int width, int height); + +BITMAPINFO* swrSetup8BitBitmapInfo(int width, int height); diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h new file mode 100755 index 00000000..08d59201 --- /dev/null +++ b/src/sw/pixel_convert.h @@ -0,0 +1,90 @@ +#pragma once + +#include "defines.h" + +// CONFIG: Change the size of a pixel. +// +// 32-bit: 0xAARRGGBB +// 16-bit: 0b0RRRRRGGGGGBBBBB +// 8-bit: 0bBBGGGRRR +#define PIXEL_SIZE 32 +//#define PIXEL_SIZE 16 +//#define PIXEL_SIZE 8 + +#if PIXEL_SIZE == 32 +typedef uint32_t uintpixel_t; +#elif PIXEL_SIZE == 16 +typedef uint16_t uintpixel_t; +#elif PIXEL_SIZE == 8 +typedef uint8_t uintpixel_t; +#define PXL_TRANSPARENT (0b10101010) +#else +#error "Unknown pixel size!" +#endif + +typedef union +{ + struct { + uint8_t r, g, b, a; + } p; + uint32_t l; +} +Pixel32ABGR; + +typedef union +{ + struct { + uint8_t b, g, r, a; + } p; + uint32_t l; +} +Pixel32ARGB; + +FORCE_INLINE UNUSED +uint16_t abgr8888_to_rgb1555(uint32_t xl) +{ + Pixel32ABGR x; + x.l = xl; + return (x.p.b >> 3) | ((x.p.g >> 3) << 5) | ((x.p.r >> 3) << 10) | ((x.p.a >> 7) << 15); +} + +FORCE_INLINE UNUSED +uint8_t abgr8888_to_rgb332(uint32_t xl) +{ + Pixel32ABGR x; + x.l = xl; + +#if PIXEL_SIZE == 8 + //check if transparent + if (x.p.a < 128) + return PXL_TRANSPARENT; +#endif + + uint8_t pxl = (x.p.r >> 5) | ((x.p.g >> 5) << 3) | ((x.p.b >> 6) << 6); + +#if PIXEL_SIZE == 8 + //hacky fixup + if (pxl == PXL_TRANSPARENT) + pxl++; +#endif + + return pxl; +} + +FORCE_INLINE UNUSED +uintpixel_t swrConvertPixelTexture(uint32_t gmPixel) +{ +#if PIXEL_SIZE == 32 + return (gmPixel & 0xFF00FF00) | ((gmPixel & 0xFF) << 16) | ((gmPixel >> 16) & 0xFF); +#elif PIXEL_SIZE == 16 + return abgr8888_to_rgb1555(gmPixel); +#elif PIXEL_SIZE == 8 + return abgr8888_to_rgb332(gmPixel); +#endif +} + +#if PIXEL_SIZE == 8 +#define swrConvertPixel(x) swrConvertPixelTexture((x) | 0xFF000000) +#else +#define swrConvertPixel swrConvertPixelTexture +#endif diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c new file mode 100755 index 00000000..ee69ef4d --- /dev/null +++ b/src/sw/sw_renderer.c @@ -0,0 +1,1630 @@ +#include +#include +#include +#include +#include "defines.h" +#include "sw_renderer.h" +#include "text_utils.h" +#include "pixel_convert.h" +#include "image/image_decoder.h" + +#define UNIMP() do { fprintf(stderr, "NYI %s\n", __func__); } while (0) +//#define UNIMP() do { } while (0) +#define UNIMP2() do { } while (0) + +#ifndef M_PI +#define M_PI 3.1415926535897932384626 +#endif + +#define TEXTURE_LRU_LENGTH 16 + +typedef struct +{ + uintpixel_t* buffer; + uint16_t width, height; +} +SWTexture; + +typedef struct +{ + Renderer base; + + // Window Properties + uint16_t width; + uint16_t height; + // Framebuffer + uintpixel_t* fb; + uint16_t fbPitch; // in sizeof(uintpixel_t) units, NOT in bytes! + + SWTexture** textures; + uint32_t* textureIndexLRU; + uint32_t textureIndexLRUHead; + uint32_t textureIndexLRUTail; + size_t textureCount; + + bool viewActive; + int viewX, viewY, viewW, viewH; + int portX, portY, portW, portH; + int gameW, gameH, windowW, windowH; +} +SWRenderer; + +void Runner_setNextFrame(uintpixel_t* framebuffer, int width, int height); + +FORCE_INLINE int swrMin(int a, int b) { return a < b ? a : b; } +FORCE_INLINE int swrMax(int a, int b) { return a > b ? a : b; } +FORCE_INLINE int swrAbs(int x) { return x < 0 ? -x : x; } + +FORCE_INLINE bool opaque(uintpixel_t color) +{ +#if PIXEL_SIZE == 32 + return (color & 0xFF000000) != 0; +#elif PIXEL_SIZE == 16 + return (color & 0x8000) != 0; +#else + return (color != PXL_TRANSPARENT); +#endif +} + +typedef union +{ + struct { uint8_t b, g, r, a; } p; + uint32_t l; +} +Color32; + +FORCE_INLINE uintpixel_t tint(uintpixel_t tintColor, uintpixel_t color) +{ +#if PIXEL_SIZE == 32 + Color32 x, y; + + if ((tintColor & 0xFFFFFF) == 0xFFFFFF) + return color; + + x.l = color; + y.l = tintColor; + + x.p.b = (int)x.p.b * y.p.b / 255; + x.p.g = (int)x.p.g * y.p.g / 255; + x.p.r = (int)x.p.r * y.p.r / 255; + return x.l; +#elif PIXEL_SIZE == 16 + if ((tintColor & 0x7FFF) == 0x7FFF) + return color; + + int tcb = tintColor & 0x1F; + int tcg = (tintColor >> 5) & 0x1F; + int tcr = (tintColor >> 10) & 0x1F; + + int cb = color & 0x1F; + int cg = (color >> 5) & 0x1F; + int cr = (color >> 10) & 0x1F; + int ca = color & 0x8000; + + cb = (cb * tcb) / 32; + cg = (cg * tcg) / 32; + cr = (cr * tcr) / 32; + return ca | cb | (cg << 5) | (cr << 10); +#elif PIXEL_SIZE == 8 + // fast but hacky + if (tintColor == 0xFF || tintColor == PXL_TRANSPARENT) + return color; + + return color & tintColor; +#endif +} + +// NOTE: alpha is between 0 and 256, NOT between 0 and 255! +FORCE_INLINE void alphaBlend(uintpixel_t* dcolor, uintpixel_t scolor, int alpha) +{ +#if PIXEL_SIZE == 32 || PIXEL_SIZE == 16 + // it's so insignificant here nobody will notice if we just don't... + if (alpha < 3) + return; + + // it's so significant here we might as well fill in the whole color + if (alpha > 253) + { + *dcolor = scolor; + return; + } + + int inval = 256 - alpha; +#endif + +#if PIXEL_SIZE == 32 + Color32 dc, sc; + dc.l = *dcolor; + sc.l = scolor; + + dc.p.r = (dc.p.r * inval + sc.p.r * alpha) >> 8; + dc.p.g = (dc.p.g * inval + sc.p.g * alpha) >> 8; + dc.p.b = (dc.p.b * inval + sc.p.b * alpha) >> 8; + + *dcolor = dc.l; +#elif PIXEL_SIZE == 16 + int scb = scolor & 0x1F; + int scg = (scolor >> 5) & 0x1F; + int scr = (scolor >> 10) & 0x1F; + + uintpixel_t _dcolor = *dcolor; + int dcb = _dcolor & 0x1F; + int dcg = (_dcolor >> 5) & 0x1F; + int dcr = (_dcolor >> 10) & 0x1F; + int dca = _dcolor & 0x8000; + + dcr = (dcr * inval + scr * alpha) >> 8; + dcg = (dcg * inval + scg * alpha) >> 8; + dcb = (dcb * inval + scb * alpha) >> 8; + + *dcolor = dca | dcb | (dcg << 5) | (dcr << 10); +#else + if (alpha < 240) { + static int alphaApproximationThingy = 0; + alphaApproximationThingy += 1339; + if (alphaApproximationThingy > 601000) + alphaApproximationThingy = 0; + + //gotta love that RNG + if ((alphaApproximationThingy & 0xFF) >= alpha) + return; + } + + *dcolor = scolor; +#endif +} + +FORCE_INLINE int swrIntAlpha(float alphaf) +{ + return (int)(alphaf * 256); +} + +FORCE_INLINE bool swrMustRotate(float angleDeg) +{ + int angleDegInt = (int)(angleDeg * 4); + angleDegInt %= 360*4; + + if (angleDegInt > 180*4) + angleDegInt -= 360*4; + + return swrAbs(angleDegInt) >= 1; // 0.25 degrees +} + +FORCE_INLINE bool swrMustRotateTolerant(float angleDeg) +{ + int angleDegInt = (int)(angleDeg * 16); + angleDegInt %= 360*16; + + if (angleDegInt > 180*16) + angleDegInt -= 360*16; + + return swrAbs(angleDegInt) >= 1; // 1/16 of a degree +} + +FORCE_INLINE int swrFloor(float x) +{ + int i = (int) x; + return i - (x < (float) i); +} + +FORCE_INLINE int swrCeiling(float x) +{ + int i = (int) x; + return i + (x > (float) i); +} + +static SWTexture* createTexture(const uint8_t* srcBuffer, int width, int height) +{ + SWTexture* txt = safeCalloc(1, sizeof(SWTexture)); + txt->buffer = safeCalloc(width * height, sizeof(uintpixel_t)); + + const uint32_t* rgbaSrc = (const uint32_t*) srcBuffer; + + size_t sz = width * height; + for (size_t i = 0; i < sz; i++) + txt->buffer[i] = swrConvertPixelTexture(rgbaSrc[i]); + + txt->width = (uint16_t) width; + txt->height = (uint16_t) height; + + return txt; +} + +static bool swrAddTextureIndexToLRU(SWRenderer* swr, int textureIndex) +{ + uint32_t newIndex = (swr->textureIndexLRUHead + 1) % TEXTURE_LRU_LENGTH; + if (newIndex == swr->textureIndexLRUTail) { + // about to collide with tail from the other side -- nope. + return false; + } + + swr->textureIndexLRU[swr->textureIndexLRUHead] = textureIndex; + swr->textureIndexLRUHead = newIndex; + return true; +} + +static int swrTailTextureIndexLRU(SWRenderer* swr, bool remove) +{ + if (swr->textureIndexLRUHead == swr->textureIndexLRUTail) + return -1; + + uint32_t textureIndex = swr->textureIndexLRU[swr->textureIndexLRUTail]; + + if (remove) + swr->textureIndexLRUTail = (swr->textureIndexLRUTail + 1) % TEXTURE_LRU_LENGTH; + + return textureIndex; +} + +static void swrEvictTextureFromCache(SWRenderer* swr, int textureIndex) +{ + SWTexture* texture = swr->textures[textureIndex]; + swr->textures[textureIndex] = NULL; + + free(texture->buffer); + free(texture); +} + +// Lazily decodes and uploads a TXTR page on first access. +// Returns true if the texture is ready, false if it failed to decode. +static bool swrEnsureTextureIsLoaded(SWRenderer* swr, uint32_t pageId) +{ + if (swr->textures[pageId]) + return true; + + DataWin* dw = swr->base.dataWin; + Texture* txtr = &dw->txtr.textures[pageId]; + + int w, h; + bool gm2022_5 = DataWin_isVersionAtLeast(dw, 2022, 5, 0, 0); + + uint8_t* pixels = NULL; + + do + { + pixels = ImageDecoder_decodeToRgba(txtr->blobData, (size_t) txtr->blobSize, gm2022_5, &w, &h); + if (pixels) + break; + + fprintf(stderr, "swr: Failed to decode TXTR page %u. This is likely because we're out of memory, so evicting a texture.\n", pageId); + + int tail = swrTailTextureIndexLRU(swr, true); + if (tail == -1) { + fprintf(stderr, "swr: Looks like we can't fit this texture in memory at all. Bummer.\n"); + break; + } + + swrEvictTextureFromCache(swr, tail); + fprintf(stderr, "swr: Evicted texture %d, trying again.\n", tail); + } + while (!pixels); + + if (pixels == nullptr) { + fprintf(stderr, "swr: Failed to decode TXTR page %u.\n", pageId); + return false; + } + + swr->textures[pageId] = createTexture(pixels, w, h); + free(pixels); + + fprintf(stderr, "SWR: Loaded TXTR page %u (%dx%d)\n", pageId, w, h); + + // add it to the LRU + do + { + bool added = swrAddTextureIndexToLRU(swr, pageId); + if (added) + break; + + int tail = swrTailTextureIndexLRU(swr, true); + if (tail == -1) { + fprintf(stderr, "swr: Come on now.\n"); + assert(tail != -1); + } + + swrEvictTextureFromCache(swr, tail); + } + while (true); + + return true; +} + +static void SWRenderer_init(Renderer* renderer, DataWin* dataWin) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + renderer->dataWin = dataWin; + + //allocate frame buffer + swr->fb = safeCalloc(swr->width * swr->height, sizeof(uintpixel_t)); + swr->fbPitch = swr->width; + + //allocate texture buffer + swr->textureCount = dataWin->txtr.count; + swr->textures = safeCalloc(swr->textureCount, sizeof(SWTexture*)); + + //allocate texture LRU cache to allow for dynamic unloading of textures + swr->textureIndexLRU = safeCalloc(TEXTURE_LRU_LENGTH, sizeof(uint32_t)); + swr->textureIndexLRUHead = 0; + swr->textureIndexLRUTail = 0; + + fprintf(stderr, "SWRenderer initialized.\n"); +} + +static void SWRenderer_destroy(Renderer* renderer) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + (void) swr; + + fprintf(stderr, "SWRenderer destroyed.\n"); +} + +static void SWRenderer_beginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swr->gameW = gameW; + swr->gameH = gameH; + swr->windowW = windowW; + swr->windowH = windowH; +} + +// This used to be just one, "endFrame". Not sure what the different is. +static void SWRenderer_endFrameInit(Renderer* renderer) +{ + UNIMP(); + SWRenderer* swr = (SWRenderer*) renderer; + Runner_setNextFrame(swr->fb, swr->width, swr->height); +} + +static void SWRenderer_endFrameEnd(Renderer* renderer) +{ + UNIMP(); + SWRenderer* swr = (SWRenderer*) renderer; + Runner_setNextFrame(swr->fb, swr->width, swr->height); +} + +static void SWRenderer_beginView(Renderer* renderer, int32_t viewX, int32_t viewY, int32_t viewW, int32_t viewH, + int32_t portX, int32_t portY, int32_t portW, int32_t portH, float viewAngle) +{ + (void)renderer; (void)viewX; (void)viewY; (void)viewW; (void)viewH; + (void)portX; (void)portY; (void)portW; (void)portH; (void)viewAngle; + UNIMP2(); + + SWRenderer* swr = (SWRenderer*) renderer; + + float xratio = (float) swr->windowW / swr->gameW; + float yratio = (float) swr->windowH / swr->gameH; + + portX = (int)(portX * xratio); + portY = (int)(portY * yratio); + portW = (int)(portW * xratio); + portH = (int)(portH * yratio); + + swr->viewActive = true; + swr->viewX = viewX; + swr->viewY = viewY; + swr->viewW = viewW; + swr->viewH = viewH; + swr->portX = portX; + swr->portY = portY; + swr->portW = portW; + swr->portH = portH; +} + +static void SWRenderer_endView(Renderer* renderer) +{ + (void)renderer; + UNIMP2(); + + SWRenderer* swr = (SWRenderer*) renderer; + swr->viewActive = false; + + swr->portX = swr->viewX = 0; + swr->portY = swr->viewY = 0; + swr->portW = swr->viewW = swr->width; + swr->portH = swr->viewH = swr->height; +} + +static void SWRenderer_beginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, + int32_t portX, int32_t portY, int32_t portW, int32_t portH) +{ + (void)renderer; (void)guiW; (void)guiH; + (void)portX; (void)portY; (void)portW; (void)portH; + UNIMP2(); +} + +static void SWRenderer_endGUI(Renderer* renderer) +{ + (void)renderer; + UNIMP2(); +} + +static void swrTransformPosIfNeeded(SWRenderer* swr, float* dx, float* dy) +{ + if (!swr->viewActive) return; + + if (dx) { + float xscale = ((float)swr->portW / swr->viewW); + *dx -= swr->viewX; + *dx *= xscale; + *dx += swr->portX; + } + if (dy) { + float yscale = ((float)swr->portH / swr->viewH); + *dy -= swr->viewY; + *dy *= yscale; + *dy += swr->portY; + } +} + +static void swrTransformSizeIfNeeded(SWRenderer* swr, float* dx, float* dy) +{ + if (!swr->viewActive || !swr->viewW || !swr->viewH) return; + + if (dx) *dx *= ((float)swr->portW / swr->viewW); + if (dy) *dy *= ((float)swr->portH / swr->viewH); +} + +FORCE_INLINE void swrPlotPixel(Renderer* renderer, int x, int y, uintpixel_t color, int alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + if (x < 0 || y < 0) return; + if (x >= swr->width || y >= swr->height) return; + + alphaBlend(&swr->fb[y * swr->fbPitch + x], color, alpha); +} + +static void swrDrawHLineInt(Renderer* renderer, int dx, int dy, int dw, uintpixel_t color, int alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + if (dy < 0) return; + if (dy >= swr->height) return; + if (dx < 0) { dw += dx; dx = 0; } + if (dx + dw >= swr->width) dw = swr->width - dx; + if (dw <= 0) return; + + uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; + for (int i = 0; i < dw; i++) + alphaBlend(&line[i], color, alpha); +} + +static void swrDrawHLine(Renderer* renderer, float dx, float dy, float dw, uintpixel_t color, float alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + float thickness = 1; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &dw, &thickness); + + // TODO: use thickness + swrDrawHLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dw), color, swrIntAlpha(alpha)); +} + +static void swrDrawVLineInt(Renderer* renderer, int dx, int dy, int dh, uintpixel_t color, int alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + if (dx < 0) return; + if (dx >= swr->width) return; + if (dy < 0) { dh += dy; dy = 0; } + if (dy + dh >= swr->height) dh = swr->height - dy; + if (dh <= 0) return; + + for (int i = 0; i < dh; i++) + { + uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; + alphaBlend(&line[0], color, alpha); + } +} + +static void swrDrawVLine(Renderer* renderer, float dx, float dy, float dh, uintpixel_t color, float alpha) +{ + SWRenderer *swr = (SWRenderer*) renderer; + float thickness = 1; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &thickness, &dh); + + // TODO: use thickness + swrDrawVLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dh), color, swrIntAlpha(alpha)); +} + +static void swrDrawRectangle(Renderer* renderer, float x1, float y1, float x2, float y2, uintpixel_t color, float alpha) +{ + swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color, alpha); + swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color, alpha); + swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color, alpha); + swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color, alpha); +} + +static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, int width, uintpixel_t color, int alpha) +{ + if (x1 == x2) + { + swrDrawVLineInt(renderer, x1, swrMin(y1, y2), swrAbs(y1 - y2), color, alpha); + return; + } + if (y1 == y2) + { + swrDrawHLineInt(renderer, swrMin(x1, x2), y1, swrAbs(x1 - x2), color, alpha); + return; + } + + int dx = x2 - x1, dy = y2 - y1; + int dx1 = swrAbs(dx), dy1 = swrAbs(dy), xe, ye, x, y; + int px = 2 * dy1 - dx1, py = 2 * dx1 - dy1; + + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1, y = y1, xe = x2; + } + else + { + x = x2, y = y2, xe = x1; + } + + swrPlotPixel(renderer, x, y, color, alpha); + + for (int i = 0; x < xe; i++) + { + x++; + if (px < 0) + { + px += 2 * dy1; + } + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y++; else y--; + px += 2 * (dy1 - dx1); + } + + swrPlotPixel(renderer, x, y, color, alpha); + } + } + else + { + if (dy >= 0) + { + x = x1, y = y1, ye = y2; + } + else + { + x = x2, y = y2, ye = y1; + } + + swrPlotPixel(renderer, x, y, color, alpha); + + for (int i = 0; y < ye; i++) + { + y++; + if (py <= 0) + { + py += 2 * dx1; + } + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x++; else x--; + py += 2 * (dx1 - dy1); + } + + swrPlotPixel(renderer, x, y, color, alpha); + } + } +} + +static void swrDrawLine(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uintpixel_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + swrTransformSizeIfNeeded(swr, &width, NULL); + swrDrawLineInt(renderer, swrFloor(x1), swrFloor(y1), swrCeiling(x2), swrCeiling(y2), swrCeiling(width), color, swrIntAlpha(alpha)); +} + +static void swrDrawSpriteInternal( + Renderer* renderer, int dx, int dy, int dw, int dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uintpixel_t tintColor, int alpha +) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + bool flipX = false, flipY = false; + if (dw < 0) { dx += dw; dw = -dw; flipX = true; } + if (dh < 0) { dy += dh; dh = -dh; flipY = true; } + + //basic out of bound checks + if (dw == 0 || dh == 0) return; + if (sw == 0) sw = 1; + if (sh == 0) sh = 1; + if (dx + dw <= 0) return; + if (dy + dh <= 0) return; + if (dx >= swr->width) return; + if (dy >= swr->height) return; + + int odw = dw, odh = dh; + int osw = sw, osh = sh; + + int minx = swr->portX, miny = swr->portY, maxx = swr->portX + swr->portW, maxy = swr->portY + swr->portH; + + //out of bounds adjustment checks + int diffxl = 0, diffyl = 0, diffxu = 0, diffyu = 0; + if (dx < minx) { diffxl = minx - dx; dx = minx; dw -= diffxl; } + if (dy < miny) { diffyl = miny - dy; dy = miny; dh -= diffyl; } + if (dx + dw > maxx) { diffxu = dx + dw - maxx; dw -= diffxu; } + if (dy + dh > maxy) { diffyu = dy + dh - maxy; dh -= diffyu; } + + if (diffxl != 0 || diffyl != 0 || diffxu != 0 || diffyu != 0) + { + //adjust source coordinates too + diffxl = (int)((long)diffxl * osw / odw); + diffyl = (int)((long)diffyl * osh / odh); + diffxu = (int)((long)(diffxu + 1) * osw / odw); + diffyu = (int)((long)(diffyu + 1) * osh / odh); + sx += flipX ? diffxu : diffxl; + sy += flipY ? diffyu : diffyl; + sw -= diffxl + diffxu; + sh -= diffyl + diffyu; + if (sw <= 0 || sh <= 0) return; + } + + //clip the source coords into bounds too + if (sx < 0) { sw += sx; sx = 0; } + if (sy < 0) { sh += sy; sy = 0; } + if (sx + sw >= texture->width) { sw = texture->width - sx; } + if (sy + sh >= texture->height) { sh = texture->height - sy; } + + //okay, now we can finally get on with rendering + + int ixs = 0, oxs = 1, iys = 0, oys = 1; + if (flipX) ixs = dw - 1, oxs = -1; + if (flipY) iys = dh - 1, oys = -1; + + // tweak these if stuff doesn't look right + typedef int32_t fixedp_t; + const int fp_prec = 8; + + fixedp_t ystep = (sh == dh) ? (1 << fp_prec) : ((fixedp_t) osh << fp_prec) / odh; + fixedp_t xstep = (sw == dw) ? (1 << fp_prec) : ((fixedp_t) osw << fp_prec) / odw; + fixedp_t oxs2 = oxs * xstep; + fixedp_t oys2 = oys * ystep; + fixedp_t ixs2 = ixs * xstep; + fixedp_t iys2 = iys * ystep; + + if (sw == dw) + { + fixedp_t ys2 = (fixedp_t) iys2; + for (int y = 0, ys = iys; y < dh; y++, ys += oys, ys2 += oys2) + { + uintpixel_t* dstline; + const uintpixel_t* srcline; + dstline = &swr->fb[(dy + y) * swr->fbPitch + dx]; + if (dh == sh) + srcline = &texture->buffer[(sy + ys) * texture->width + sx]; + else + srcline = &texture->buffer[(sy + (int)(ys2 >> fp_prec)) * texture->width + sx]; + + for (int x = 0, xs = ixs; x < dw; x++, xs += oxs) + { + uintpixel_t pixel = srcline[xs]; + if (opaque(pixel)) + alphaBlend(&dstline[x], tint(tintColor, pixel), alpha); + } + } + } + else + { + fixedp_t ys2 = iys2; + for (int y = 0, ys = iys; y < dh; y++, ys += oys, ys2 += oys2) + { + uintpixel_t* dstline; + const uintpixel_t* srcline; + dstline = &swr->fb[(dy + y) * swr->fbPitch + dx]; + if (dh == sh) + srcline = &texture->buffer[(sy + ys) * texture->width + sx]; + else + srcline = &texture->buffer[(sy + (int)(ys2 >> fp_prec)) * texture->width + sx]; + + fixedp_t xs2 = ixs2; + for (int x = 0, xs = ixs; x < dw; x++, xs += oxs, xs2 += oxs2) + { + uintpixel_t pixel = srcline[(int)(xs2 >> fp_prec)]; + if (opaque(pixel)) + alphaBlend(&dstline[x], tint(tintColor, pixel), alpha); + } + } + } +} + +static void swrDrawSprite( + Renderer* renderer, float dx, float dy, float dw, float dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uint32_t tintColor, float alpha +) +{ + SWRenderer *swr = (SWRenderer*) renderer; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformSizeIfNeeded(swr, &dw, &dh); + + swrDrawSpriteInternal( + renderer, + swrFloor(dx), + swrFloor(dy), + swrCeiling(dw), + swrCeiling(dh), + texture, + sx, sy, + sw, sh, + swrConvertPixel(tintColor), + swrIntAlpha(alpha) + ); +} + +static void swrDrawSpriteRotatedInternal( + Renderer* renderer, int dx, int dy, int dw, int dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uintpixel_t tintColor, int alpha, + float angleDeg, + float pivotX, + float pivotY +) +{ + SWRenderer* swr = (SWRenderer*) renderer; + float angleRad = -angleDeg * M_PI / 180.0f; + + bool flipX = false, flipY = false; + if (dw < 0) { dw = -dw; dx -= dw; pivotX = dw - pivotX; flipX = true; } + if (dh < 0) { dh = -dh; dy -= dh; pivotY = dh - pivotY; flipY = true; } + + float cosA = cosf(angleRad); + float sinA = sinf(angleRad); + + float cnrx[4], cnry[4]; + cnrx[0] = cnrx[3] = dx; + cnry[0] = cnry[1] = dy; + cnrx[1] = cnrx[2] = dx + dw; + cnry[2] = cnry[3] = dy + dh; + + float pxa = pivotX + dx; + float pya = pivotY + dy; + + float minXf = FLT_MAX, minYf = FLT_MAX, maxXf = -FLT_MAX, maxYf = -FLT_MAX; + for (int i = 0; i < 4; i++) + { + float cxi = cnrx[i] - pxa; + float cyi = cnry[i] - pya; + float rx = cosA * cxi - sinA * cyi + pxa; + float ry = sinA * cxi + cosA * cyi + pya; + if (minXf > rx) minXf = rx; + if (maxXf < rx) maxXf = rx; + if (minYf > ry) minYf = ry; + if (maxYf < ry) maxYf = ry; + } + + // minX, minY, maxX, maxY now represent an AABB of pixels we should loop over + int minX = swrFloor(minXf); + int minY = swrFloor(minYf); + int maxX = swrCeiling(maxXf); + int maxY = swrCeiling(maxYf); + + // basic out-of-bound checks + if (maxX < 0) return; + if (maxY < 0) return; + if (minX >= swr->width) return; + if (minY >= swr->height) return; + + // however, we'll need to clip it against out of bounds first + int minXc = minX, minYc = minY, maxXc = maxX, maxYc = maxY; + int minx = swr->portX, miny = swr->portY, maxx = swr->portX + swr->portW, maxy = swr->portY + swr->portH; + + if (minXc < minx) minXc = minx; + if (minYc < miny) minYc = miny; + if (maxXc >= maxx) maxXc = maxx; + if (maxYc >= maxy) maxYc = maxy; + + // some final clip checks + if (minXc >= maxXc || minYc >= maxYc) return; + + int sox = flipX ? sw - 1 : 0; + int soy = flipY ? sh - 1 : 0; + int six = flipX ? -1 : 1; + int siy = flipY ? -1 : 1; + + float sw_dw = (float) sw / dw; + float sh_dh = (float) sh / dh; + + for (int cy = minYc; cy < maxYc; cy++) + { + uintpixel_t *dstline = &swr->fb[cy * swr->fbPitch]; + for (int cx = minXc; cx < maxXc; cx++) + { + // we need to determine the texture-space coordinate of cx/cy + float ox = (float) cx + 0.5f - pxa; + float oy = (float) cy + 0.5f - pya; + + // "undo" the rotation + float lx = cosA * ox + sinA * oy; + float ly = -sinA * ox + cosA * oy; + + // turn it into a texture-local coordinate + lx += pxa - dx; + ly += pya - dy; + + if (lx < 0 || ly < 0 || lx >= (float) dw || ly >= (float) dh) continue; + + lx = lx * sw_dw; + ly = ly * sh_dh; + + int tx = (int)(sox + lx * six); + int ty = (int)(soy + ly * siy); + + if (tx < 0) tx = 0; + if (ty < 0) ty = 0; + if (tx >= sw) tx = sw - 1; + if (ty >= sh) ty = sh - 1; + + tx += sx; + ty += sy; + + uintpixel_t src = texture->buffer[ty * texture->width + tx]; + + if (opaque(src)) + alphaBlend(&dstline[cx], tint(tintColor, src), alpha); + } + } +} + +static void swrDrawSpriteRotated( + Renderer* renderer, float dx, float dy, float dw, float dh, + SWTexture* texture, int sx, int sy, int sw, int sh, + uint32_t tintColor, float alpha, + float angleDeg, + float pivotX, + float pivotY +) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + swrTransformPosIfNeeded(swr, &dx, &dy); + swrTransformPosIfNeeded(swr, &pivotX, &pivotY); + swrTransformSizeIfNeeded(swr, &dw, &dh); + + swrDrawSpriteRotatedInternal( + renderer, + swrFloor(dx), + swrFloor(dy), + swrCeiling(dw), + swrCeiling(dh), + texture, + sx, sy, + sw, sh, + swrConvertPixel(tintColor), + swrIntAlpha(alpha), + angleDeg, + pivotX, + pivotY + ); +} + +static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x, float y, + float originX, float originY, float xscale, float yscale, + float angleDeg, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) return; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + int sx = tpag->sourceX; + int sy = tpag->sourceY; + int sw = tpag->sourceWidth; + int sh = tpag->sourceHeight; + + float dx = (float)(tpag->targetX - originX); + float dy = (float)(tpag->targetY - originY); + int dw = (int)(xscale * sw); + int dh = (int)(yscale * sh); + dx *= xscale; + dy *= yscale; + dx += x; + dy += y; + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(swrMustRotate(angleDeg))) + { + float pivotX = x - dx; + float pivotY = y - dy; + swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX, pivotY); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } +} + +static void SWRenderer_drawSpritePart(Renderer* renderer, int32_t tpagIndex, + int32_t srcOffX, int32_t srcOffY, int32_t srcW, int32_t srcH, + float x, float y, float xscale, float yscale, float angleDeg, + float pivotX, float pivotY, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) return; + + bool flipX = false, flipY = false; + if (xscale < 0) flipX = true, xscale = -xscale; + if (yscale < 0) flipY = true, yscale = -yscale; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + int sx = tpag->sourceX + srcOffX; + int sy = tpag->sourceY + srcOffY; + int sw = srcW; + int sh = srcH; + + float dx = x; + float dy = y; + int dw = swrCeiling(xscale * sw); + int dh = swrCeiling(yscale * sh); + if (flipX) dx -= dw; + if (flipY) dy -= dh; + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(swrMustRotate(angleDeg))) + { + swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX * dw, pivotY * dh); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } +} + +static void SWRenderer_drawSpritePos(Renderer* renderer, int32_t tpagIndex, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, float alpha) +{ + (void)renderer; (void)tpagIndex; + (void)x1; (void)y1; (void)x2; (void)y2; + (void)x3; (void)y3; (void)x4; (void)y4; (void)alpha; + UNIMP(); +} + +static void SWRenderer_drawRectangle(Renderer* renderer, float x1, float y1, float x2, float y2, + uint32_t color, float alpha, bool outline) +{ + uintpixel_t pxcolor = swrConvertPixel(color); + + SWRenderer* swr = (SWRenderer*) renderer; + + if (outline) + { + swrDrawRectangle(renderer, x1, y1, x2, y2, pxcolor, alpha); + } + else + { + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + + int alphaInt = swrIntAlpha(alpha); + int x1i = swrFloor(x1), x2i = swrCeiling(x2), y1i = swrFloor(y1), y2i = swrCeiling(y2); + int xd = x2i - x1i; + int yd = y2i - y1i; + if (xd <= 0 || yd <= 0) return; + + for (int y = 0; y <= yd; y++) { + swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor, alphaInt); + } + } +} + +static void SWRenderer_drawRectangleColor(Renderer* renderer, float x1, float y1, float x2, float y2, + uint32_t color1, uint32_t color2, uint32_t color3, uint32_t color4, + float alpha, bool outline) +{ + (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; + (void)color1; (void)color2; (void)color3; (void)color4; (void)alpha; (void)outline; + UNIMP(); +} + +static void SWRenderer_drawLine(Renderer* renderer, float x1, float y1, float x2, float y2, + float width, uint32_t color, float alpha) +{ + (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; + (void)width; (void)color; (void)alpha; + + swrDrawLine(renderer, x1, y1, x2, y2, width, swrConvertPixel(color), alpha); +} + +static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, + float x3, float y3, bool outline) +{ + (void)outline; + + uintpixel_t drawColorCvt = swrConvertPixel(renderer->drawColor); + swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, renderer->drawAlpha); +} + +static void SWRenderer_drawLineColor(Renderer* renderer, float x1, float y1, float x2, float y2, + float width, uint32_t color1, uint32_t color2, float alpha) +{ + (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; + (void)width; (void)color1; (void)color2; (void)alpha; + UNIMP(); +} + +typedef struct +{ + Font* font; + TexturePageItem* fontTpag; // single TPAG for regular fonts (NULL for sprite fonts) + int fontTpagIndex; + int fontPageId; + Sprite* spriteFontSprite; // source sprite for sprite fonts (NULL for regular fonts) +} +SwrFontState; + +static bool swrResolveFontState(SWRenderer* swr, DataWin* dw, Font* font, SwrFontState* state) +{ + state->font = font; + state->fontTpag = NULL; + state->fontTpagIndex = 0; + state->spriteFontSprite = NULL; + + if (font->isSpriteFont) + { + state->spriteFontSprite = &dw->sprt.sprites[font->spriteIndex]; + } + else + { + state->fontTpagIndex = font->tpagIndex; + if (state->fontTpagIndex < 0) return false; + + state->fontTpag = &dw->tpag.items[state->fontTpagIndex]; + int16_t pageId = state->fontTpag->texturePageId; + if (0 > pageId || (uint32_t) pageId >= swr->textureCount) return false; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return false; + + state->fontPageId = pageId; + } + + return true; +} + +static bool swrResolveGlyph( + SWRenderer* swr, DataWin* dw, SwrFontState* state, FontGlyph* glyph, float cursorX, float cursorY, + int* tpagIndex, int* pageId, int* sx, int* sy, int* sw, int* sh, float* dx, float* dy +) +{ + Font* font = state->font; + if (font->isSpriteFont && state->spriteFontSprite != NULL) + { + Sprite* sprite = state->spriteFontSprite; + int32_t glyphIndex = (int32_t) (glyph - font->glyphs); + if (0 > glyphIndex || glyphIndex >= (int32_t) sprite->textureCount) return false; + + int32_t tpagIdx = sprite->tpagIndices[glyphIndex]; + if (0 > tpagIdx) return false; + + TexturePageItem* glyphTpag = &dw->tpag.items[tpagIdx]; + int16_t pid = glyphTpag->texturePageId; + if (0 > pid || (uint32_t) pid >= swr->textureCount) return false; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pid)) return false; + + *tpagIndex = tpagIdx; + *pageId = glyphTpag->texturePageId; + + *sx = glyphTpag->sourceX; + *sy = glyphTpag->sourceY; + *sw = glyphTpag->sourceWidth; + *sh = glyphTpag->sourceHeight; + + *dx = cursorX + glyph->offset; + *dy = cursorY + glyphTpag->targetY - sprite->originY; + } + else + { + *tpagIndex = state->fontTpagIndex; + *pageId = state->fontPageId; + + *sx = state->fontTpag->sourceX + glyph->sourceX; + *sy = state->fontTpag->sourceY + glyph->sourceY; + *sw = glyph->sourceWidth; + *sh = glyph->sourceHeight; + + *dx = cursorX + glyph->offset; + *dy = cursorY; + } + + return true; +} + +static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, float xscale, float yscale, UNUSED float angleDeg, int32_t color, UNUSED float alpha) +{ + Renderer* renderer = &swr->base; + DataWin* dwin = renderer->dataWin; + + int32_t fontIndex = renderer->drawFont; + if (0 > fontIndex || dwin->font.count <= (uint32_t) fontIndex) return; + + Font* font = &dwin->font.fonts[fontIndex]; + + SwrFontState fontState; + if (!swrResolveFontState(swr, dwin, font, &fontState)) return; + + // TODO: do we need to mirror the way the text scrolls too?! + float cosA = 1.0f, sinA = 0.0f, angleRad = 0.0f; + bool mustRotate = swrMustRotateTolerant(angleDeg); + if (UNLIKELY(mustRotate)) + { + angleRad = -angleDeg * M_PI / 180.0f; + cosA = cosf(angleRad); + sinA = sinf(angleRad); + } + + int textLen = (int) strlen(text); + int lineCount = TextUtils_countLines(text, textLen); + float lineStride = TextUtils_lineStride(font); + + // Vertical alignment offset + float totalHeight = (float) lineCount * lineStride; + float valignOffset = 0; + if (renderer->drawValign == 1) valignOffset = -totalHeight / 2.0f; + else if (renderer->drawValign == 2) valignOffset = -totalHeight; + + xscale *= font->scaleX; + yscale *= font->scaleY; + + // Iterate through lines. HTML5 subtracts ascenderOffset from the per-line y offset + // (see yyFont.GR_Text_Draw), shifting glyphs up so the baseline aligns with the drawn y. + float cursorY = valignOffset - (float) font->ascenderOffset; + int32_t lineStart = 0; + + for (int32_t lineIdx = 0; lineCount > lineIdx; lineIdx++) { + // Find end of current line + int32_t lineEnd = lineStart; + while (textLen > lineEnd && !TextUtils_isNewlineChar(text[lineEnd])) { + lineEnd++; + } + int32_t lineLen = lineEnd - lineStart; + + // Horizontal alignment offset for this line + float lineWidth = TextUtils_measureLineWidth(font, text + lineStart, lineLen); + float halignOffset = 0; + if (renderer->drawHalign == 1) halignOffset = -lineWidth / 2.0f; + else if (renderer->drawHalign == 2) halignOffset = -lineWidth; + + float cursorX = halignOffset; + + // Render each glyph in the line - decode each codepoint once and carry it forward as next iteration's ch (also used for kerning) + int32_t pos = 0; + uint16_t ch = 0; + bool hasCh = false; + if (lineLen > pos) { + ch = TextUtils_decodeUtf8(text + lineStart, lineLen, &pos); + hasCh = true; + } + + while (hasCh) { + FontGlyph* glyph = TextUtils_findGlyph(font, ch); + + uint16_t nextCh = 0; + bool hasNext = lineLen > pos; + if (hasNext) nextCh = TextUtils_decodeUtf8(text + lineStart, lineLen, &pos); + + if (glyph != nullptr) { + bool drewSuccessfully = false; + if (glyph->sourceWidth != 0 && glyph->sourceHeight != 0) { + int fontTpagIndex = 0, pageId = 0; + int sx, sy, sw, sh, dw, dh; + float dx, dy; + if (swrResolveGlyph(swr, dwin, &fontState, glyph, cursorX, cursorY, + &fontTpagIndex, &pageId, &sx, &sy, &sw, &sh, &dx, &dy)) + { + dx *= xscale; dx += x; + dy *= xscale; dy += y; + dw = swrCeiling(xscale * glyph->sourceWidth); + dh = swrCeiling(yscale * glyph->sourceHeight); + + // TODO: at 640x480, for some reason, without this fixup the + // letters in the "Name the fallen human." screen don't shake + dx = round(dx * 2) / 2; + dy = round(dy * 2) / 2; + + SWTexture* texture = swr->textures[pageId]; + + if (UNLIKELY(mustRotate)) + { + float ndx = cosA * dx - sinA * dy; + float ndy = sinA * dx + cosA * dy; + swrDrawSpriteRotated(renderer, ndx, ndy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, 0.0f, 0.0f); + } + else + { + swrDrawSprite(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha); + } + + drewSuccessfully = true; + } + } + + cursorX += glyph->shift; + if (drewSuccessfully && hasNext) { + cursorX += TextUtils_getKerningOffset(glyph, nextCh); + } + } + + ch = nextCh; + hasCh = hasNext; + } + + cursorY += lineStride; + // Skip past the newline, treating \r\n and \n\r as single breaks + if (textLen > lineEnd) { + lineStart = TextUtils_skipNewline(text, lineEnd, textLen); + } else { + lineStart = lineEnd; + } + } +} + +static void SWRenderer_drawText(Renderer* renderer, const char* text, float x, float y, + float xscale, float yscale, float angleDeg) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, renderer->drawColor, renderer->drawAlpha); +} + +static void SWRenderer_drawTextColor(Renderer* renderer, const char* text, float x, float y, + float xscale, float yscale, float angleDeg, + int32_t c1, int32_t c2, int32_t c3, int32_t c4, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha); +} + +static void SWRenderer_flush(Renderer* renderer) +{ + (void)renderer; + UNIMP(); +} + +static void SWRenderer_clearScreen(Renderer* renderer, uint32_t color, float alpha) +{ + (void)renderer; (void)color; (void)alpha; + UNIMP(); +} + +static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t surfaceID, + int32_t x, int32_t y, int32_t w, int32_t h, + bool removeback, bool smooth, + int32_t xorig, int32_t yorig) +{ + (void)renderer; (void)surfaceID; (void)x; (void)y; (void)w; (void)h; + (void)removeback; (void)smooth; (void)xorig; (void)yorig; + UNIMP(); + return 0; +} + +static void SWRenderer_deleteSprite(Renderer* renderer, int32_t spriteIndex) +{ + UNIMP(); + (void)renderer; (void)spriteIndex; +} + +static void SWRenderer_gpuSetBlendMode(Renderer* renderer, int32_t mode) +{ + UNIMP(); + (void)renderer; (void)mode; +} + +static void SWRenderer_gpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor) +{ + UNIMP(); + (void)renderer; (void)sfactor; (void)dfactor; +} + +static void SWRenderer_gpuSetBlendEnable(Renderer* renderer, bool enable) +{ + UNIMP(); + (void)renderer; (void)enable; +} + +static void SWRenderer_gpuSetAlphaTestEnable(Renderer* renderer, bool enable) +{ + UNIMP(); + (void)renderer; (void)enable; +} + +static void SWRenderer_gpuSetAlphaTestRef(Renderer* renderer, uint8_t ref) +{ + UNIMP(); + (void)renderer; (void)ref; +} + +static void SWRenderer_gpuSetColorWriteEnable(Renderer* renderer, bool red, bool green, bool blue, bool alpha) +{ + UNIMP(); + (void)renderer; (void)red; (void)green; (void)blue; (void)alpha; +} + +static void SWRenderer_gpuGetColorWriteEnable(Renderer* renderer, bool* red, bool* green, bool* blue, bool* alpha) +{ + UNIMP(); + *red = false; + *green = false; + *blue = false; + *alpha = false; + (void)renderer; (void)red; (void)green; (void)blue; (void)alpha; +} + +static bool SWRenderer_gpuGetBlendEnable(Renderer* renderer) +{ + UNIMP(); + (void)renderer; + return false; +} + +static void SWRenderer_gpuSetFog(Renderer* renderer, bool enable, uint32_t color) +{ + UNIMP(); + (void)renderer; (void)enable; (void)color; +} + +static void SWRenderer_drawTiled(Renderer* renderer, int32_t tpagIndex, + float originX, float originY, float x, float y, + float xscale, float yscale, bool tileX, bool tileY, + float roomW, float roomH, uint32_t color, float alpha) +{ + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; + + if (0 > tpagIndex || dwin->tpag.count <= (uint32_t) tpagIndex) return; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + float axScale = fabsf(xscale); + float ayScale = fabsf(yscale); + float tileW = (float) tpag->boundingWidth * axScale; + float tileH = (float) tpag->boundingHeight * ayScale; + if (0 >= tileW || 0 >= tileH) return; + + float startX, endX, startY, endY; + if (tileX) { + startX = fmodf(x - originX * axScale, tileW); + if (startX > 0) startX -= tileW; + endX = roomW; + } else { + startX = x - originX * axScale; + endX = startX + tileW; + } + if (tileY) { + startY = fmodf(y - originY * ayScale, tileH); + if (startY > 0) startY -= tileH; + endY = roomH; + } else { + startY = y - originY * ayScale; + endY = startY + tileH; + } + + int sx = tpag->sourceX; + int sy = tpag->sourceY; + int sw = tpag->sourceWidth; + int sh = tpag->sourceHeight; + + int localX0 = tpag->targetX - originX; + int localY0 = tpag->targetY - originY; + int localX1 = localX0 + tpag->sourceWidth; + int localY1 = localY0 + tpag->sourceHeight; + int sx0 = xscale * localX0; + int sy0 = yscale * localY0; + int sx1 = xscale * localX1; + int sy1 = yscale * localY1; + + for (int dy = startY; endY > dy; dy += tileH) { + int cy = dy + (int)(originY * ayScale); + int vy0 = cy + sy0; + int vy1 = cy + sy1; + int dh = vy1 - vy0; + + for (int dx = startX; endX > dx; dx += tileW) { + int cx = dx + (int)(originX * axScale); + int vx0 = cx + sx0; + int vx1 = cx + sx1; + int dw = vx1 - vx0; + + swrDrawSprite(renderer, vx0, vy0, dw, dh, swr->textures[pageId], sx, sy, sw, sh, color, alpha); + } + } +} + +static int32_t SWRenderer_createSurface(Renderer* renderer, int32_t width, int32_t height) +{ + UNIMP(); + (void)renderer; (void)width; (void)height; + return 0; +} + +static bool SWRenderer_surfaceExists(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + return false; +} + +static bool SWRenderer_setRenderTarget(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + return false; +} + +static float SWRenderer_getSurfaceWidth(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + return 0.0f; +} + +static float SWRenderer_getSurfaceHeight(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + return 0.0f; +} + +static void SWRenderer_drawSurface(Renderer* renderer, int32_t surfaceID, + int32_t srcLeft, int32_t srcTop, int32_t srcWidth, int32_t srcHeight, + float x, float y, float xscale, float yscale, float angleDeg, + uint32_t color, float alpha) +{ + UNIMP(); + (void)renderer; (void)surfaceID; + (void)srcLeft; (void)srcTop; (void)srcWidth; (void)srcHeight; + (void)x; (void)y; (void)xscale; (void)yscale; (void)angleDeg; + (void)color; (void)alpha; +} + +static void SWRenderer_surfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width, int32_t height) +{ + UNIMP(); + (void)renderer; (void)surfaceID; (void)width; (void)height; +} + +static void SWRenderer_surfaceFree(Renderer* renderer, int32_t surfaceID) +{ + UNIMP(); + (void)renderer; (void)surfaceID; +} + +static void SWRenderer_surfaceCopy(Renderer* renderer, + int32_t DestSurfaceID, int32_t DestX, int32_t DestY, + int32_t SrcSurfaceID, int32_t SrcX, int32_t SrcY, + int32_t SrcW, int32_t SrcH, bool part) +{ + UNIMP(); + (void)renderer; + (void)DestSurfaceID; (void)DestX; (void)DestY; + (void)SrcSurfaceID; (void)SrcX; (void)SrcY; + (void)SrcW; (void)SrcH; (void)part; +} + +static bool SWRenderer_surfaceGetPixels(Renderer* renderer, int32_t surfaceID, uint8_t* outRGBA) +{ + UNIMP(); + (void)renderer; (void)surfaceID; (void)outRGBA; + return false; +} + +static void SWRenderer_drawTiledPart(Renderer* renderer, int32_t tpagIndex, + int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, + float dstX, float dstY, float dstW, float dstH, + uint32_t color, float alpha) +{ + UNIMP(); + (void)renderer; (void)tpagIndex; + (void)srcX; (void)srcY; (void)srcW; (void)srcH; + (void)dstX; (void)dstY; (void)dstW; (void)dstH; + (void)color; (void)alpha; +} + +static RendererVtable swrVtable = +{ + .init = SWRenderer_init, + .destroy = SWRenderer_destroy, + .beginFrame = SWRenderer_beginFrame, + .endFrameInit = SWRenderer_endFrameInit, + .endFrameEnd = SWRenderer_endFrameEnd, + .beginView = SWRenderer_beginView, + .endView = SWRenderer_endView, + .beginGUI = SWRenderer_beginGUI, + .endGUI = SWRenderer_endGUI, + .drawSprite = SWRenderer_drawSprite, + .drawSpritePart = SWRenderer_drawSpritePart, + .drawSpritePos = SWRenderer_drawSpritePos, + .drawRectangle = SWRenderer_drawRectangle, + .drawRectangleColor = SWRenderer_drawRectangleColor, + .drawLine = SWRenderer_drawLine, + .drawTriangle = SWRenderer_drawTriangle, + .drawLineColor = SWRenderer_drawLineColor, + .drawText = SWRenderer_drawText, + .drawTextColor = SWRenderer_drawTextColor, + .flush = SWRenderer_flush, + .clearScreen = SWRenderer_clearScreen, + .createSpriteFromSurface = SWRenderer_createSpriteFromSurface, + .deleteSprite = SWRenderer_deleteSprite, + .gpuSetBlendMode = SWRenderer_gpuSetBlendMode, + .gpuSetBlendModeExt = SWRenderer_gpuSetBlendModeExt, + .gpuSetBlendEnable = SWRenderer_gpuSetBlendEnable, + .gpuSetAlphaTestEnable = SWRenderer_gpuSetAlphaTestEnable, + .gpuSetAlphaTestRef = SWRenderer_gpuSetAlphaTestRef, + .gpuSetColorWriteEnable = SWRenderer_gpuSetColorWriteEnable, + .gpuGetColorWriteEnable = SWRenderer_gpuGetColorWriteEnable, + .gpuGetBlendEnable = SWRenderer_gpuGetBlendEnable, + .gpuSetFog = SWRenderer_gpuSetFog, + .drawTile = NULL, + .drawTiled = SWRenderer_drawTiled, + .createSurface = SWRenderer_createSurface, + .surfaceExists = SWRenderer_surfaceExists, + .setRenderTarget = SWRenderer_setRenderTarget, + .getSurfaceWidth = SWRenderer_getSurfaceWidth, + .getSurfaceHeight = SWRenderer_getSurfaceHeight, + .drawSurface = SWRenderer_drawSurface, + .surfaceResize = SWRenderer_surfaceResize, + .surfaceFree = SWRenderer_surfaceFree, + .surfaceCopy = SWRenderer_surfaceCopy, + .surfaceGetPixels = SWRenderer_surfaceGetPixels, + .drawTiledPart = SWRenderer_drawTiledPart, +}; + +void SWRenderer_clearFrameBuffer(Renderer* renderer, uint32_t color) +{ + SWRenderer* swr = (SWRenderer*) renderer; + + uintpixel_t pxcolor = swrConvertPixel(color); + + size_t fbSize = swr->fbPitch; + fbSize *= swr->height; + for (size_t i = 0; i < fbSize; i++) + { + swr->fb[i] = pxcolor; + } +} + +Renderer* SWRenderer_create(int windowWidth, int windowHeight) +{ + SWRenderer* swr = safeCalloc(1, sizeof(SWRenderer)); + swr->base.vtable = &swrVtable; + swr->base.drawColor = 0xFFFFFF; + swr->base.drawAlpha = 1.0f; + swr->base.drawFont = -1; + swr->base.drawHalign = 0; + swr->base.drawValign = 0; + swr->base.circlePrecision = 24; + + swr->width = windowWidth; + swr->height = windowHeight; + + return (Renderer*) swr; +} diff --git a/src/sw/sw_renderer.h b/src/sw/sw_renderer.h new file mode 100755 index 00000000..f8ca7226 --- /dev/null +++ b/src/sw/sw_renderer.h @@ -0,0 +1,7 @@ +#pragma once + +#include "renderer.h" + +Renderer* SWRenderer_create(int windowWidth, int windowHeight); + +void SWRenderer_clearFrameBuffer(Renderer* renderer, uint32_t color); diff --git a/src/utils.h b/src/utils.h index ef12f208..24e78731 100644 --- a/src/utils.h +++ b/src/utils.h @@ -25,6 +25,7 @@ do { \ if (!(condition)) { \ fprintf(stderr, "Requirement failed at %s:%d\n", __FILE__, __LINE__); \ + fflush(stderr); \ abort(); \ } \ } while (0) @@ -33,6 +34,7 @@ do { \ if (!(condition)) { \ fprintf(stderr, "Requirement failed at %s:%d: %s\n", __FILE__, __LINE__, message); \ +fflush(stderr); \ abort(); \ } \ } while (0) @@ -41,6 +43,7 @@ abort(); \ do { \ if (!(condition)) { \ fprintf(stderr, "Requirement failed at %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ +fflush(stderr); \ abort(); \ } \ } while (0) @@ -49,6 +52,7 @@ abort(); \ typeof(ptr) _val = (ptr); \ if (_val == NULL) { \ fprintf(stderr, "%s:%d: requireNotNull failed: '%s'\n", __FILE__, __LINE__, #ptr); \ +fflush(stderr); \ abort(); \ } \ _val; \ @@ -58,6 +62,7 @@ _val; \ typeof(ptr) _val = (ptr); \ if (_val == NULL) { \ fprintf(stderr, "%s:%d: requireNotNull failed: %s\n", __FILE__, __LINE__, (msg)); \ +fflush(stderr); \ abort(); \ } \ _val; \ From b813cff02580863d9a6a912b16f2d7404b2d2eee Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 20:45:55 +0300 Subject: [PATCH 02/31] Remove redundant src/image inclusion. --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3340df1..fed01ae2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,21 +233,20 @@ elseif(PLATFORM STREQUAL "sdl") if (NOT ENABLE_LEGACY_GL AND NOT ENABLE_SW_RENDERER) message(FATAL_ERROR "SDL requires legacy GL or the software renderer!") endif() - + if(ENABLE_LEGACY_GL) file(GLOB GL_SOURCES src/gl_legacy/*.c src/gl_common/*.c src/image/*.c) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_legacy) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl_common) target_sources(butterscotch PRIVATE ${GL_SOURCES}) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/gl) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) elseif(ENABLE_SW_RENDERER) file(GLOB SWR_SOURCES src/sw/*.c src/image/*.c) add_compile_definitions(ENABLE_SW_RENDERER) target_sources(butterscotch PRIVATE ${SWR_SOURCES}) target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/sw) - target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) endif() + target_include_directories(butterscotch PRIVATE ${CMAKE_SOURCE_DIR}/src/image) # Butterscotch VM/interpreter profiler option(ENABLE_VM_GML_PROFILER "Enable Butterscotch VM/interpreter profiler" ON) From c6791e7b990d0852a3cbc92fe75d84c8aa8edeab Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 20:48:18 +0300 Subject: [PATCH 03/31] Add build rules for Makefile --- Makefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 4a5d91f7..04c391bd 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,15 @@ ifndef DISABLE_BC17 DEFINES += -DENABLE_BC17 endif +ifndef DISABLE_SW_RENDERER +ifeq ($(PLATFORM),sdl) +DEFINES += -DENABLE_SW_RENDERER +SRCS += $(wildcard src/swr/*.c) +INCLUDES += -Isrc/swr +HEADERS += $(wildcard src/swr/*.h) +endif +endif + # GNU make doesn't have a way to do OR in conditionals, stupid language for clowns ifndef DISABLE_LEGACY_GL ENABLE_GL := 1 From 8bb4bc1c84d4694c18b6ed9fdf8e0d28e1f05bb9 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 20:50:48 +0300 Subject: [PATCH 04/31] Fix oopsie --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 04c391bd..3cd48e0a 100644 --- a/Makefile +++ b/Makefile @@ -42,9 +42,9 @@ endif ifndef DISABLE_SW_RENDERER ifeq ($(PLATFORM),sdl) DEFINES += -DENABLE_SW_RENDERER -SRCS += $(wildcard src/swr/*.c) +SRCS += $(wildcard src/sw/*.c) INCLUDES += -Isrc/swr -HEADERS += $(wildcard src/swr/*.h) +HEADERS += $(wildcard src/sw/*.h) endif endif From 0fe12739fe0cdd1d8498c78dc83354a72d94f4fc Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 20:54:41 +0300 Subject: [PATCH 05/31] * endFrameInit/endFrameEnd no longer print "NYI" --- src/sw/sw_renderer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index ee69ef4d..a021e36f 100755 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -372,14 +372,12 @@ static void SWRenderer_beginFrame(Renderer* renderer, int32_t gameW, int32_t gam // This used to be just one, "endFrame". Not sure what the different is. static void SWRenderer_endFrameInit(Renderer* renderer) { - UNIMP(); SWRenderer* swr = (SWRenderer*) renderer; Runner_setNextFrame(swr->fb, swr->width, swr->height); } static void SWRenderer_endFrameEnd(Renderer* renderer) { - UNIMP(); SWRenderer* swr = (SWRenderer*) renderer; Runner_setNextFrame(swr->fb, swr->width, swr->height); } From 01b731252fe43de72cf4bd7636db165fc510a1e4 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Sun, 17 May 2026 13:58:09 -0400 Subject: [PATCH 06/31] fix build --- Makefile | 2 +- src/sw/debug_utils.c | 44 ------------------ src/sw/fb_convert.c | 104 ------------------------------------------- src/sw/fb_convert.h | 13 ------ 4 files changed, 1 insertion(+), 162 deletions(-) delete mode 100755 src/sw/debug_utils.c delete mode 100755 src/sw/fb_convert.c delete mode 100755 src/sw/fb_convert.h diff --git a/Makefile b/Makefile index 3cd48e0a..b954645f 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ ifndef DISABLE_SW_RENDERER ifeq ($(PLATFORM),sdl) DEFINES += -DENABLE_SW_RENDERER SRCS += $(wildcard src/sw/*.c) -INCLUDES += -Isrc/swr +INCLUDES += -Isrc/sw HEADERS += $(wildcard src/sw/*.h) endif endif diff --git a/src/sw/debug_utils.c b/src/sw/debug_utils.c deleted file mode 100755 index 604dbe1c..00000000 --- a/src/sw/debug_utils.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -int debug_fprintf(FILE* stream, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - - if (stream == stderr) - { - static char buffer[4096]; - int len = vsnprintf(buffer, sizeof(buffer), fmt, args); - - OutputDebugStringA(buffer); - - FILE* stderr2 = fopen("log.txt", "a+"); - fwrite(buffer, 1, strlen(buffer), stderr2); - fclose(stderr2); - - va_end(args); - return len; - } - - int len = vfprintf(stream, fmt, args); - va_end(args); - return len; -} - -int debug_printf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - - static char buffer[4096]; - int len = vsnprintf(buffer, sizeof(buffer), fmt, args); - OutputDebugStringA(buffer); - - FILE* stderr2 = fopen("log.txt", "a+"); - fwrite(buffer, 1, strlen(buffer), stderr2); - fclose(stderr2); - - va_end(args); - return len; -} diff --git a/src/sw/fb_convert.c b/src/sw/fb_convert.c deleted file mode 100755 index 9b0ba9f7..00000000 --- a/src/sw/fb_convert.c +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include -#include "fb_convert.h" -#include "pixel_convert.h" -#include "utils.h" - -FORCE_INLINE UNUSED -uint16_t argb8888_to_rgb1555(uint32_t xl) -{ - Pixel32ARGB x; - x.l = xl; - return (x.p.b >> 3) | ((x.p.g >> 3) << 5) | ((x.p.r >> 3) << 10); -} - -FORCE_INLINE UNUSED -uint8_t argb8888_to_rgb332(uint32_t xl) -{ - Pixel32ARGB x; - x.l = xl; - return (x.p.r >> 5) | ((x.p.g >> 5) << 3) | ((x.p.b >> 6) << 6); -} - -uint8_t* swrConvert32to24(uint8_t* dest, uint32_t* src, int width, int height) -{ - int size = width * height; - if (!dest) { - dest = safeCalloc(3, size); - } - - for (int i = 0, j = 0; i < size; i++) - { - Pixel32ARGB x; - x.l = src[i]; - - dest[j++] = x.p.b; - dest[j++] = x.p.g; - dest[j++] = x.p.r; - } - - return dest; -} - -uint16_t* swrConvert32to16(uint16_t* dest, uint32_t* src, int width, int height) -{ - int size = width * height; - if (!dest) { - dest = safeCalloc(sizeof(uint16_t), size); - } - - for (int i = 0; i < size; i++) - dest[i] = argb8888_to_rgb1555(src[i]); - - return dest; -} - -uint8_t* swrConvert32to8(uint8_t* dest, uint32_t* src, int width, int height) -{ - int size = width * height; - if (!dest) { - dest = safeCalloc(sizeof(uint8_t), size); - } - - for (int i = 0; i < size; i++) - dest[i] = argb8888_to_rgb332(src[i]); - - return dest; -} - -typedef struct -{ - BITMAPINFOHEADER bmiHeader; - RGBQUAD bmiColors[256]; -} -BITMAPINFO_8BIT; - -BITMAPINFO* swrSetup8BitBitmapInfo(int width, int height) -{ - static BITMAPINFO_8BIT* pBmi; - if (!pBmi) { - pBmi = safeCalloc(1, sizeof(BITMAPINFO_8BIT)); - - // set up palette - int i = 0; - for (int b = 0; b < 4; b++) - for (int g = 0; g < 8; g++) - for (int r = 0; r < 8; r++) - { - pBmi->bmiColors[i].rgbRed = (r << 5) + (r << 2) + (r >> 1); - pBmi->bmiColors[i].rgbGreen = (g << 5) + (g << 2) + (g >> 1); - pBmi->bmiColors[i].rgbBlue = (b << 6) + (b << 4) + (b << 2) + b; - pBmi->bmiColors[i].rgbReserved = 255; - i++; - } - } - - pBmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - pBmi->bmiHeader.biWidth = width; - pBmi->bmiHeader.biHeight = -height; - pBmi->bmiHeader.biPlanes = 1; - pBmi->bmiHeader.biCompression = BI_RGB; - pBmi->bmiHeader.biBitCount = 8; - - return (BITMAPINFO*) pBmi; -} diff --git a/src/sw/fb_convert.h b/src/sw/fb_convert.h deleted file mode 100755 index dad12c66..00000000 --- a/src/sw/fb_convert.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -typedef struct tagBITMAPINFO BITMAPINFO; - -uint8_t* swrConvert32to24(uint8_t* dest, uint32_t* src, int width, int height); - -uint16_t* swrConvert32to16(uint16_t* dest, uint32_t* src, int width, int height); - -uint8_t* swrConvert32to8(uint8_t* dest, uint32_t* src, int width, int height); - -BITMAPINFO* swrSetup8BitBitmapInfo(int width, int height); From 9a017401921690c29fb1e789a605cec9b60a2549 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 21:10:06 +0300 Subject: [PATCH 07/31] Prepare for surface work --- src/sdl/main.c | 2 +- src/sw/sw_renderer.c | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/sdl/main.c b/src/sdl/main.c index 2828bcb7..d76c42a7 100644 --- a/src/sdl/main.c +++ b/src/sdl/main.c @@ -1216,7 +1216,7 @@ int main(int argc, char* argv[]) { if (!args.headless && runner->currentRoom->speed > 0) { static bool fastForwardActive = false; static bool fastForwardTabPrev = false; - bool fastForwardTabNow = false; + bool fastForwardTabNow = RunnerKeyboard_checkPressed(runner->keyboard, '\t'); if (args.fastForwardSpeed > 0.0 && fastForwardTabNow && !fastForwardTabPrev) { fastForwardActive = !fastForwardActive; lastFrameTime = (SDL_GetTicks()/1000.0f); diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index a021e36f..7d398e64 100755 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -18,6 +18,8 @@ #define TEXTURE_LRU_LENGTH 16 +#define SURFACE_MAX_COUNT 16 + typedef struct { uintpixel_t* buffer; @@ -36,12 +38,21 @@ typedef struct uintpixel_t* fb; uint16_t fbPitch; // in sizeof(uintpixel_t) units, NOT in bytes! + uintpixel_t* mainFb; + uint16_t mainWidth; + uint16_t mainHeight; + uint16_t mainPitch; + bool drawingToSurface; + SWTexture** textures; uint32_t* textureIndexLRU; uint32_t textureIndexLRUHead; uint32_t textureIndexLRUTail; size_t textureCount; + SWTexture** surfaces; + size_t surfaceCount; + bool viewActive; int viewX, viewY, viewW, viewH; int portX, portY, portW, portH; @@ -339,6 +350,10 @@ static void SWRenderer_init(Renderer* renderer, DataWin* dataWin) swr->fb = safeCalloc(swr->width * swr->height, sizeof(uintpixel_t)); swr->fbPitch = swr->width; + //allocate surface buffer + swr->surfaces = safeCalloc(SURFACE_MAX_COUNT, sizeof(SWTexture*)); + swr->surfaceCount = SURFACE_MAX_COUNT; + //allocate texture buffer swr->textureCount = dataWin->txtr.count; swr->textures = safeCalloc(swr->textureCount, sizeof(SWTexture*)); @@ -367,18 +382,21 @@ static void SWRenderer_beginFrame(Renderer* renderer, int32_t gameW, int32_t gam swr->gameH = gameH; swr->windowW = windowW; swr->windowH = windowH; + swr->drawingToSurface = false; } // This used to be just one, "endFrame". Not sure what the different is. static void SWRenderer_endFrameInit(Renderer* renderer) { SWRenderer* swr = (SWRenderer*) renderer; + assert(!swr->drawingToSurface); Runner_setNextFrame(swr->fb, swr->width, swr->height); } static void SWRenderer_endFrameEnd(Renderer* renderer) { SWRenderer* swr = (SWRenderer*) renderer; + assert(!swr->drawingToSurface); Runner_setNextFrame(swr->fb, swr->width, swr->height); } @@ -391,8 +409,17 @@ static void SWRenderer_beginView(Renderer* renderer, int32_t viewX, int32_t view SWRenderer* swr = (SWRenderer*) renderer; - float xratio = (float) swr->windowW / swr->gameW; - float yratio = (float) swr->windowH / swr->gameH; + float xratio, yratio; + + if (swr->drawingToSurface) { + UNIMP(); + xratio = 1.0f; + yratio = 1.0f; + } + else { + xratio = (float) swr->windowW / swr->gameW; + yratio = (float) swr->windowH / swr->gameH; + } portX = (int)(portX * xratio); portY = (int)(portY * yratio); From a443be3c5ccc3d58d96a29bb541e2823eb6b0c57 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sun, 17 May 2026 21:15:10 +0300 Subject: [PATCH 08/31] Relocate a function --- src/sw/sw_renderer.c | 174 +++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 7d398e64..f039ed87 100755 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1326,33 +1326,86 @@ static void SWRenderer_drawTextColor(Renderer* renderer, const char* text, float swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha); } -static void SWRenderer_flush(Renderer* renderer) +static void SWRenderer_drawTiled(Renderer* renderer, int32_t tpagIndex, + float originX, float originY, float x, float y, + float xscale, float yscale, bool tileX, bool tileY, + float roomW, float roomH, uint32_t color, float alpha) { - (void)renderer; - UNIMP(); -} + SWRenderer* swr = (SWRenderer*) renderer; + DataWin* dwin = renderer->dataWin; -static void SWRenderer_clearScreen(Renderer* renderer, uint32_t color, float alpha) -{ - (void)renderer; (void)color; (void)alpha; - UNIMP(); + if (0 > tpagIndex || dwin->tpag.count <= (uint32_t) tpagIndex) return; + + TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; + int16_t pageId = tpag->texturePageId; + if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + + float axScale = fabsf(xscale); + float ayScale = fabsf(yscale); + float tileW = (float) tpag->boundingWidth * axScale; + float tileH = (float) tpag->boundingHeight * ayScale; + if (0 >= tileW || 0 >= tileH) return; + + float startX, endX, startY, endY; + if (tileX) { + startX = fmodf(x - originX * axScale, tileW); + if (startX > 0) startX -= tileW; + endX = roomW; + } else { + startX = x - originX * axScale; + endX = startX + tileW; + } + if (tileY) { + startY = fmodf(y - originY * ayScale, tileH); + if (startY > 0) startY -= tileH; + endY = roomH; + } else { + startY = y - originY * ayScale; + endY = startY + tileH; + } + + int sx = tpag->sourceX; + int sy = tpag->sourceY; + int sw = tpag->sourceWidth; + int sh = tpag->sourceHeight; + + int localX0 = tpag->targetX - originX; + int localY0 = tpag->targetY - originY; + int localX1 = localX0 + tpag->sourceWidth; + int localY1 = localY0 + tpag->sourceHeight; + int sx0 = xscale * localX0; + int sy0 = yscale * localY0; + int sx1 = xscale * localX1; + int sy1 = yscale * localY1; + + for (int dy = startY; endY > dy; dy += tileH) { + int cy = dy + (int)(originY * ayScale); + int vy0 = cy + sy0; + int vy1 = cy + sy1; + int dh = vy1 - vy0; + + for (int dx = startX; endX > dx; dx += tileW) { + int cx = dx + (int)(originX * axScale); + int vx0 = cx + sx0; + int vx1 = cx + sx1; + int dw = vx1 - vx0; + + swrDrawSprite(renderer, vx0, vy0, dw, dh, swr->textures[pageId], sx, sy, sw, sh, color, alpha); + } + } } -static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t surfaceID, - int32_t x, int32_t y, int32_t w, int32_t h, - bool removeback, bool smooth, - int32_t xorig, int32_t yorig) +static void SWRenderer_flush(Renderer* renderer) { - (void)renderer; (void)surfaceID; (void)x; (void)y; (void)w; (void)h; - (void)removeback; (void)smooth; (void)xorig; (void)yorig; + (void)renderer; UNIMP(); - return 0; } -static void SWRenderer_deleteSprite(Renderer* renderer, int32_t spriteIndex) +static void SWRenderer_clearScreen(Renderer* renderer, uint32_t color, float alpha) { + (void)renderer; (void)color; (void)alpha; UNIMP(); - (void)renderer; (void)spriteIndex; } static void SWRenderer_gpuSetBlendMode(Renderer* renderer, int32_t mode) @@ -1414,76 +1467,6 @@ static void SWRenderer_gpuSetFog(Renderer* renderer, bool enable, uint32_t color (void)renderer; (void)enable; (void)color; } -static void SWRenderer_drawTiled(Renderer* renderer, int32_t tpagIndex, - float originX, float originY, float x, float y, - float xscale, float yscale, bool tileX, bool tileY, - float roomW, float roomH, uint32_t color, float alpha) -{ - SWRenderer* swr = (SWRenderer*) renderer; - DataWin* dwin = renderer->dataWin; - - if (0 > tpagIndex || dwin->tpag.count <= (uint32_t) tpagIndex) return; - - TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; - int16_t pageId = tpag->texturePageId; - if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; - if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; - - float axScale = fabsf(xscale); - float ayScale = fabsf(yscale); - float tileW = (float) tpag->boundingWidth * axScale; - float tileH = (float) tpag->boundingHeight * ayScale; - if (0 >= tileW || 0 >= tileH) return; - - float startX, endX, startY, endY; - if (tileX) { - startX = fmodf(x - originX * axScale, tileW); - if (startX > 0) startX -= tileW; - endX = roomW; - } else { - startX = x - originX * axScale; - endX = startX + tileW; - } - if (tileY) { - startY = fmodf(y - originY * ayScale, tileH); - if (startY > 0) startY -= tileH; - endY = roomH; - } else { - startY = y - originY * ayScale; - endY = startY + tileH; - } - - int sx = tpag->sourceX; - int sy = tpag->sourceY; - int sw = tpag->sourceWidth; - int sh = tpag->sourceHeight; - - int localX0 = tpag->targetX - originX; - int localY0 = tpag->targetY - originY; - int localX1 = localX0 + tpag->sourceWidth; - int localY1 = localY0 + tpag->sourceHeight; - int sx0 = xscale * localX0; - int sy0 = yscale * localY0; - int sx1 = xscale * localX1; - int sy1 = yscale * localY1; - - for (int dy = startY; endY > dy; dy += tileH) { - int cy = dy + (int)(originY * ayScale); - int vy0 = cy + sy0; - int vy1 = cy + sy1; - int dh = vy1 - vy0; - - for (int dx = startX; endX > dx; dx += tileW) { - int cx = dx + (int)(originX * axScale); - int vx0 = cx + sx0; - int vx1 = cx + sx1; - int dw = vx1 - vx0; - - swrDrawSprite(renderer, vx0, vy0, dw, dh, swr->textures[pageId], sx, sy, sw, sh, color, alpha); - } - } -} - static int32_t SWRenderer_createSurface(Renderer* renderer, int32_t width, int32_t height) { UNIMP(); @@ -1562,6 +1545,23 @@ static bool SWRenderer_surfaceGetPixels(Renderer* renderer, int32_t surfaceID, u return false; } +static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t surfaceID, + int32_t x, int32_t y, int32_t w, int32_t h, + bool removeback, bool smooth, + int32_t xorig, int32_t yorig) +{ + (void)renderer; (void)surfaceID; (void)x; (void)y; (void)w; (void)h; + (void)removeback; (void)smooth; (void)xorig; (void)yorig; + UNIMP(); + return 0; +} + +static void SWRenderer_deleteSprite(Renderer* renderer, int32_t spriteIndex) +{ + UNIMP(); + (void)renderer; (void)spriteIndex; +} + static void SWRenderer_drawTiledPart(Renderer* renderer, int32_t tpagIndex, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, float dstX, float dstY, float dstW, float dstH, From 0e1ccaceace0c6a3b1b38d0cec33d6e785edbc2c Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Sun, 17 May 2026 18:14:11 -0400 Subject: [PATCH 09/31] chmod -x --- src/sw/defines.h | 0 src/sw/pixel_convert.h | 0 src/sw/sw_renderer.c | 0 src/sw/sw_renderer.h | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/sw/defines.h mode change 100755 => 100644 src/sw/pixel_convert.h mode change 100755 => 100644 src/sw/sw_renderer.c mode change 100755 => 100644 src/sw/sw_renderer.h diff --git a/src/sw/defines.h b/src/sw/defines.h old mode 100755 new mode 100644 diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h old mode 100755 new mode 100644 diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c old mode 100755 new mode 100644 diff --git a/src/sw/sw_renderer.h b/src/sw/sw_renderer.h old mode 100755 new mode 100644 From c2a71bd73078c53207c019aae33de7dc53644fca Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Sun, 17 May 2026 19:21:58 -0400 Subject: [PATCH 10/31] no binary litterals in C99 --- src/sw/pixel_convert.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h index 08d59201..b2853c7e 100644 --- a/src/sw/pixel_convert.h +++ b/src/sw/pixel_convert.h @@ -17,7 +17,7 @@ typedef uint32_t uintpixel_t; typedef uint16_t uintpixel_t; #elif PIXEL_SIZE == 8 typedef uint8_t uintpixel_t; -#define PXL_TRANSPARENT (0b10101010) +#define PXL_TRANSPARENT (0xAA) #else #error "Unknown pixel size!" #endif From d7249d7eabe67daa1e8c899ca4f64b54dbc648c9 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Sun, 17 May 2026 19:58:56 -0400 Subject: [PATCH 11/31] likely macro compat --- src/sw/defines.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sw/defines.h b/src/sw/defines.h index 9160d57e..a0377fc9 100644 --- a/src/sw/defines.h +++ b/src/sw/defines.h @@ -1,7 +1,12 @@ #pragma once +#if defined(__GNUC__) && (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)) #define LIKELY(cond) __builtin_expect(!!(cond), 1) #define UNLIKELY(cond) __builtin_expect(!!(cond), 0) +#else +#define LIKELY(cond) (cond) +#define UNLIKELY(cond) (cond) +#endif #define UNUSED __attribute__ ((unused)) #define FORCE_INLINE static inline __attribute__((always_inline)) From 1032617b2c2dd5f27883923b422b11dc9e7072e9 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 18 May 2026 19:41:36 +0300 Subject: [PATCH 12/31] Attempt to fix a big-endian issue by byte-swapping the color channel masks in SDL. --- src/sdl/main.c | 14 ++++++++++---- src/sw/defines.h | 11 +++++++++++ src/sw/pixel_convert.h | 12 +++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/sdl/main.c b/src/sdl/main.c index d76c42a7..37833856 100644 --- a/src/sdl/main.c +++ b/src/sdl/main.c @@ -615,10 +615,16 @@ void Runner_setNextFrame(uint32_t* framebuffer, int width, int height) height, 32, width * 4, - 0xff0000, - 0x00ff00, - 0x0000ff, - 0 +#ifdef IS_BIG_ENDIAN + 0x0000ff00, // Rmask + 0x00ff0000, // Gmask + 0xff000000, // Bmask +#else + 0x00ff0000, // Rmask + 0x0000ff00, // Gmask + 0x000000ff, // Bmask +#endif + 0x00000000 ); } diff --git a/src/sw/defines.h b/src/sw/defines.h index a0377fc9..1cfd49d8 100644 --- a/src/sw/defines.h +++ b/src/sw/defines.h @@ -1,5 +1,16 @@ #pragma once +#include "common.h" + +// CONFIG: Change the size of a pixel. +// +// 32-bit: 0xAARRGGBB +// 16-bit: 0b0RRRRRGGGGGBBBBB +// 8-bit: 0bBBGGGRRR +#define PIXEL_SIZE 32 +//#define PIXEL_SIZE 16 +//#define PIXEL_SIZE 8 + #if defined(__GNUC__) && (__GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)) #define LIKELY(cond) __builtin_expect(!!(cond), 1) #define UNLIKELY(cond) __builtin_expect(!!(cond), 0) diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h index b2853c7e..0d61da67 100644 --- a/src/sw/pixel_convert.h +++ b/src/sw/pixel_convert.h @@ -2,15 +2,6 @@ #include "defines.h" -// CONFIG: Change the size of a pixel. -// -// 32-bit: 0xAARRGGBB -// 16-bit: 0b0RRRRRGGGGGBBBBB -// 8-bit: 0bBBGGGRRR -#define PIXEL_SIZE 32 -//#define PIXEL_SIZE 16 -//#define PIXEL_SIZE 8 - #if PIXEL_SIZE == 32 typedef uint32_t uintpixel_t; #elif PIXEL_SIZE == 16 @@ -22,6 +13,8 @@ typedef uint8_t uintpixel_t; #error "Unknown pixel size!" #endif +// The native format coming out of GameMaker Studio's assets. +// Bytes are in the correct order even in big endian mode. typedef union { struct { @@ -31,6 +24,7 @@ typedef union } Pixel32ABGR; +// This is the format the renderer knows how to work with. typedef union { struct { From deb760dfe8c8414696f0b1bc4b0e9c191609d9c0 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 18 May 2026 21:17:47 +0300 Subject: [PATCH 13/31] Don't call Runner_setNextFrame twice in a frame. --- src/sw/sw_renderer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index f039ed87..731aa267 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -388,9 +388,9 @@ static void SWRenderer_beginFrame(Renderer* renderer, int32_t gameW, int32_t gam // This used to be just one, "endFrame". Not sure what the different is. static void SWRenderer_endFrameInit(Renderer* renderer) { - SWRenderer* swr = (SWRenderer*) renderer; - assert(!swr->drawingToSurface); - Runner_setNextFrame(swr->fb, swr->width, swr->height); + (void) renderer; + + //this is kinda useless to do twice isn't it? } static void SWRenderer_endFrameEnd(Renderer* renderer) From e72eb28b6b20635eef42d976fc9e33ba78a18180 Mon Sep 17 00:00:00 2001 From: Un1q32 Date: Mon, 18 May 2026 14:20:07 -0400 Subject: [PATCH 14/31] fix --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 21474258..2563e3c0 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,9 @@ endif ifdef DISABLE_LEGACY_GL ifeq ($(PLATFORM),sdl) +ifdef DISABLE_SW_RENDERER $(error must enable at least 1 renderer) +endif else ifdef DISABLE_MODERN_GL $(error must enable at least 1 renderer) From 1bddaaf79b78aa8e6251d28de38c6d7df36b2f3a Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Tue, 19 May 2026 00:07:40 +0300 Subject: [PATCH 15/31] Implement big endian mode for the software renderer. This allows the software renderer to work correctly on big-endian systems like PowerPC Macs. Note that the framebuffer will be byteswapped from little-endian mode. --- src/sdl/main.c | 8 +------- src/sw/pixel_convert.h | 29 +++++++++++++++++++++++++---- src/sw/sw_renderer.c | 19 +++++-------------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/sdl/main.c b/src/sdl/main.c index 37833856..4fac77cb 100644 --- a/src/sdl/main.c +++ b/src/sdl/main.c @@ -615,16 +615,10 @@ void Runner_setNextFrame(uint32_t* framebuffer, int width, int height) height, 32, width * 4, -#ifdef IS_BIG_ENDIAN - 0x0000ff00, // Rmask - 0x00ff0000, // Gmask - 0xff000000, // Bmask -#else 0x00ff0000, // Rmask 0x0000ff00, // Gmask 0x000000ff, // Bmask -#endif - 0x00000000 + 0x00000000 // Amask ); } diff --git a/src/sw/pixel_convert.h b/src/sw/pixel_convert.h index 0d61da67..7eee4c2f 100644 --- a/src/sw/pixel_convert.h +++ b/src/sw/pixel_convert.h @@ -1,6 +1,7 @@ #pragma once #include "defines.h" +#include "binary_utils.h" #if PIXEL_SIZE == 32 typedef uint32_t uintpixel_t; @@ -14,7 +15,6 @@ typedef uint8_t uintpixel_t; #endif // The native format coming out of GameMaker Studio's assets. -// Bytes are in the correct order even in big endian mode. typedef union { struct { @@ -28,7 +28,11 @@ Pixel32ABGR; typedef union { struct { +#ifdef IS_BIG_ENDIAN + uint8_t a, r, g, b; +#else uint8_t b, g, r, a; +#endif } p; uint32_t l; } @@ -66,7 +70,7 @@ uint8_t abgr8888_to_rgb332(uint32_t xl) } FORCE_INLINE UNUSED -uintpixel_t swrConvertPixelTexture(uint32_t gmPixel) +uintpixel_t swrConvertPixelBase(uint32_t gmPixel) { #if PIXEL_SIZE == 32 return (gmPixel & 0xFF00FF00) | ((gmPixel & 0xFF) << 16) | ((gmPixel >> 16) & 0xFF); @@ -77,8 +81,25 @@ uintpixel_t swrConvertPixelTexture(uint32_t gmPixel) #endif } +#if PIXEL_SIZE == 32 +#define TRANSPARENT_MASK 0xFF000000 +#elif PIXEL_SIZE == 16 +#define TRANSPARENT_MASK 0x8000 +#endif + #if PIXEL_SIZE == 8 -#define swrConvertPixel(x) swrConvertPixelTexture((x) | 0xFF000000) + +#define swrConvertPixel(x) swrConvertPixelBase((x) | 0xFF000000) +#define swrConvertPixelTexture(x) swrConvertPixelBase(x) + +#elif defined IS_BIG_ENDIAN + +#define swrConvertPixel(x) swrConvertPixelBase(x) +#define swrConvertPixelTexture(x) swrConvertPixelBase(BinaryUtils_bswap32(x)) + #else -#define swrConvertPixel swrConvertPixelTexture + +#define swrConvertPixel swrConvertPixelBase +#define swrConvertPixelTexture swrConvertPixelBase + #endif diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 731aa267..46f02118 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -68,26 +68,17 @@ FORCE_INLINE int swrAbs(int x) { return x < 0 ? -x : x; } FORCE_INLINE bool opaque(uintpixel_t color) { -#if PIXEL_SIZE == 32 - return (color & 0xFF000000) != 0; -#elif PIXEL_SIZE == 16 - return (color & 0x8000) != 0; -#else +#if PIXEL_SIZE == 8 return (color != PXL_TRANSPARENT); +#else + return (color & TRANSPARENT_MASK) != 0; #endif } -typedef union -{ - struct { uint8_t b, g, r, a; } p; - uint32_t l; -} -Color32; - FORCE_INLINE uintpixel_t tint(uintpixel_t tintColor, uintpixel_t color) { #if PIXEL_SIZE == 32 - Color32 x, y; + Pixel32ARGB x, y; if ((tintColor & 0xFFFFFF) == 0xFFFFFF) return color; @@ -144,7 +135,7 @@ FORCE_INLINE void alphaBlend(uintpixel_t* dcolor, uintpixel_t scolor, int alpha) #endif #if PIXEL_SIZE == 32 - Color32 dc, sc; + Pixel32ARGB dc, sc; dc.l = *dcolor; sc.l = scolor; From 05d57f1e64da86b77bf0592fbd27e9a1d86fd1de Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Tue, 19 May 2026 22:38:06 +0300 Subject: [PATCH 16/31] * Add initial support for sprite surfaces. --- src/sw/sw_renderer.c | 226 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 199 insertions(+), 27 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 46f02118..024cf377 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -18,7 +18,7 @@ #define TEXTURE_LRU_LENGTH 16 -#define SURFACE_MAX_COUNT 16 +#define SURFACE_MAX_COUNT 64 typedef struct { @@ -49,9 +49,10 @@ typedef struct uint32_t textureIndexLRUHead; uint32_t textureIndexLRUTail; size_t textureCount; - - SWTexture** surfaces; size_t surfaceCount; + size_t totalTextureCount; + size_t originalTPagCount; + size_t originalSpriteCount; bool viewActive; int viewX, viewY, viewW, viewH; @@ -215,7 +216,7 @@ FORCE_INLINE int swrCeiling(float x) return i + (x > (float) i); } -static SWTexture* createTexture(const uint8_t* srcBuffer, int width, int height) +static SWTexture* swrCreateTexture(const uint8_t* srcBuffer, int width, int height) { SWTexture* txt = safeCalloc(1, sizeof(SWTexture)); txt->buffer = safeCalloc(width * height, sizeof(uintpixel_t)); @@ -223,8 +224,12 @@ static SWTexture* createTexture(const uint8_t* srcBuffer, int width, int height) const uint32_t* rgbaSrc = (const uint32_t*) srcBuffer; size_t sz = width * height; - for (size_t i = 0; i < sz; i++) - txt->buffer[i] = swrConvertPixelTexture(rgbaSrc[i]); + + if (srcBuffer) + { + for (size_t i = 0; i < sz; i++) + txt->buffer[i] = swrConvertPixelTexture(rgbaSrc[i]); + } txt->width = (uint16_t) width; txt->height = (uint16_t) height; @@ -232,6 +237,12 @@ static SWTexture* createTexture(const uint8_t* srcBuffer, int width, int height) return txt; } +static void swrFreeTexture(SWTexture* texture) +{ + free(texture->buffer); + free(texture); +} + static bool swrAddTextureIndexToLRU(SWRenderer* swr, int textureIndex) { uint32_t newIndex = (swr->textureIndexLRUHead + 1) % TEXTURE_LRU_LENGTH; @@ -263,8 +274,7 @@ static void swrEvictTextureFromCache(SWRenderer* swr, int textureIndex) SWTexture* texture = swr->textures[textureIndex]; swr->textures[textureIndex] = NULL; - free(texture->buffer); - free(texture); + swrFreeTexture(texture); } // Lazily decodes and uploads a TXTR page on first access. @@ -306,7 +316,7 @@ static bool swrEnsureTextureIsLoaded(SWRenderer* swr, uint32_t pageId) return false; } - swr->textures[pageId] = createTexture(pixels, w, h); + swr->textures[pageId] = swrCreateTexture(pixels, w, h); free(pixels); fprintf(stderr, "SWR: Loaded TXTR page %u (%dx%d)\n", pageId, w, h); @@ -341,19 +351,31 @@ static void SWRenderer_init(Renderer* renderer, DataWin* dataWin) swr->fb = safeCalloc(swr->width * swr->height, sizeof(uintpixel_t)); swr->fbPitch = swr->width; - //allocate surface buffer - swr->surfaces = safeCalloc(SURFACE_MAX_COUNT, sizeof(SWTexture*)); - swr->surfaceCount = SURFACE_MAX_COUNT; - //allocate texture buffer swr->textureCount = dataWin->txtr.count; - swr->textures = safeCalloc(swr->textureCount, sizeof(SWTexture*)); + swr->surfaceCount = SURFACE_MAX_COUNT; + swr->totalTextureCount = swr->textureCount + swr->surfaceCount; + swr->textures = safeCalloc(swr->totalTextureCount, sizeof(SWTexture*)); //allocate texture LRU cache to allow for dynamic unloading of textures swr->textureIndexLRU = safeCalloc(TEXTURE_LRU_LENGTH, sizeof(uint32_t)); swr->textureIndexLRUHead = 0; swr->textureIndexLRUTail = 0; + //HACK: this isn't good, really. This should seriously be refactored. + //expand datawin's tpag items list to include our surface count. + swr->originalTPagCount = dataWin->tpag.count; + dataWin->tpag.items = safeRealloc(dataWin->tpag.items, sizeof(TexturePageItem) * (dataWin->tpag.count + swr->surfaceCount)); + dataWin->tpag.count += swr->surfaceCount; + + swr->originalSpriteCount = dataWin->sprt.count; + + for (size_t i = swr->originalTPagCount; i < dataWin->tpag.count; i++) + { + memset(&dataWin->tpag.items[i], 0, sizeof(TexturePageItem)); + dataWin->tpag.items[i].texturePageId = -1; + } + fprintf(stderr, "SWRenderer initialized.\n"); } @@ -456,6 +478,34 @@ static void SWRenderer_endGUI(Renderer* renderer) UNIMP2(); } +// TODO[MrPowerGamerBR]: This is supposed to be refactored, not to modify data.win structs directly. +static int32_t swrFindSurfaceTextureSlot(SWRenderer* swr) +{ + // NOTE: dynamic textures are not enrolled into the eviction cache for + // hopefully obvious reasons ... + for (size_t i = swr->textureCount; i != swr->totalTextureCount; i++) + { + if (swr->textures[i] == NULL) { + return (int32_t) i; + } + } + + return -1; +} + +static int32_t swrFindSurfaceTPagSlot(SWRenderer* swr) +{ + DataWin* dw = swr->base.dataWin; + for (size_t i = swr->originalTPagCount; i != dw->tpag.count; i++) + { + if (dw->tpag.items[i].texturePageId == -1) { + return (int32_t) i; + } + } + + return -1; +} + static void swrTransformPosIfNeeded(SWRenderer* swr, float* dx, float* dy) { if (!swr->viewActive) return; @@ -935,12 +985,21 @@ static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x SWRenderer* swr = (SWRenderer*) renderer; DataWin* dwin = renderer->dataWin; - if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) return; + if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) { + fprintf(stderr, "%s: tpagIndex of %d is invalid\n", __func__, tpagIndex); + return; + } TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; int16_t pageId = tpag->texturePageId; - if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; - if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) { + fprintf(stderr, "%s: tpagIndex of %d is invalid, as pageId of %d is invalid\n", __func__, tpagIndex, pageId); + return; + } + if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) { + fprintf(stderr, "%s: could not ensure texture is loaded, tpagIndex: %d, pageId: %d\n", __func__, tpagIndex, pageId); + return; + } int sx = tpag->sourceX; int sy = tpag->sourceY; @@ -986,7 +1045,7 @@ static void SWRenderer_drawSpritePart(Renderer* renderer, int32_t tpagIndex, TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; int16_t pageId = tpag->texturePageId; - if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) return; if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; int sx = tpag->sourceX + srcOffX; @@ -1116,7 +1175,7 @@ static bool swrResolveFontState(SWRenderer* swr, DataWin* dw, Font* font, SwrFon state->fontTpag = &dw->tpag.items[state->fontTpagIndex]; int16_t pageId = state->fontTpag->texturePageId; - if (0 > pageId || (uint32_t) pageId >= swr->textureCount) return false; + if (0 > pageId || (uint32_t) pageId >= swr->totalTextureCount) return false; if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return false; state->fontPageId = pageId; @@ -1142,7 +1201,7 @@ static bool swrResolveGlyph( TexturePageItem* glyphTpag = &dw->tpag.items[tpagIdx]; int16_t pid = glyphTpag->texturePageId; - if (0 > pid || (uint32_t) pid >= swr->textureCount) return false; + if (0 > pid || (uint32_t) pid >= swr->totalTextureCount) return false; if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pid)) return false; *tpagIndex = tpagIdx; @@ -1329,7 +1388,7 @@ static void SWRenderer_drawTiled(Renderer* renderer, int32_t tpagIndex, TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; int16_t pageId = tpag->texturePageId; - if (0 > pageId || swr->textureCount <= (uint32_t) pageId) return; + if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) return; if (!swrEnsureTextureIsLoaded(swr, (uint32_t) pageId)) return; float axScale = fabsf(xscale); @@ -1541,16 +1600,129 @@ static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t su bool removeback, bool smooth, int32_t xorig, int32_t yorig) { - (void)renderer; (void)surfaceID; (void)x; (void)y; (void)w; (void)h; - (void)removeback; (void)smooth; (void)xorig; (void)yorig; - UNIMP(); - return 0; + SWRenderer* swr = (SWRenderer*) renderer; + + (void) removeback; + (void) smooth; + + if (surfaceID != -1) { + fprintf(stderr, "%s: Surfaces other than application_surface aren't supported yet!\n", __func__); + return 0; + } + + int32_t texturePageId = swrFindSurfaceTextureSlot(swr); + int32_t tpagIndex = swrFindSurfaceTPagSlot(swr); + if (texturePageId == -1 || tpagIndex == -1) { + fprintf(stderr, "%s: Surface overflow!!\n", __func__); + return 0; + } + + SWTexture* tex = swrCreateTexture(NULL, w, h); + + // grab the pixels. + for (int iy = 0; iy < h; iy++) + { + uintpixel_t* dstline = &tex->buffer[iy * tex->width]; + if ((iy + y) < 0 || (iy + y) >= swr->height) + { + for (int ix = 0; ix < w; ix++) + dstline[ix] = 0; + + continue; + } + + uintpixel_t* srcline = &swr->fb[(iy + y) * swr->width + x]; + + int ix = 0, sx = x; + // left edge + for (; sx < 0 && ix < w; sx++, ix++) + dstline[ix] = 0; + + // in-bounds + for (; sx < swr->width && ix < w; sx++, ix++) + dstline[ix] = srcline[ix] | TRANSPARENT_MASK; + + // right edge + for (; ix < w; ix++) + dstline[ix] = 0; + } + + int32_t spriteW = w; + int32_t spriteH = h; + + swr->textures[texturePageId] = tex; + + // TODO[MrPowerGamerBR]: This is supposed to be refactored, not to modify data.win structs directly. + DataWin* dw = swr->base.dataWin; + TexturePageItem* tpag = &dw->tpag.items[tpagIndex]; + tpag->sourceX = 0; + tpag->sourceY = 0; + tpag->sourceWidth = (uint16_t) spriteW; + tpag->sourceHeight = (uint16_t) spriteH; + tpag->targetX = 0; + tpag->targetY = 0; + tpag->targetWidth = (uint16_t) spriteW; + tpag->targetHeight = (uint16_t) spriteH; + tpag->boundingWidth = (uint16_t) spriteW; + tpag->boundingHeight = (uint16_t) spriteH; + tpag->texturePageId = texturePageId; + + uint32_t spriteIndex = DataWin_allocSpriteSlot(dw, swr->originalSpriteCount); + Sprite* sprite = &dw->sprt.sprites[spriteIndex]; + // name was set by DataWin_allocSpriteSlot ("__newsprite"); don't overwrite it here + sprite->width = (uint32_t) w; + sprite->height = (uint32_t) h; + sprite->originX = xorig; + sprite->originY = yorig; + sprite->textureCount = 1; + sprite->tpagIndices = safeMalloc(sizeof(int32_t)); + sprite->tpagIndices[0] = (int32_t) tpagIndex; + sprite->maskCount = 0; + sprite->masks = nullptr; + + fprintf(stderr, "%s: Allocated surface sprite with ID %d\n", __func__, spriteIndex); + return spriteIndex; } static void SWRenderer_deleteSprite(Renderer* renderer, int32_t spriteIndex) { - UNIMP(); - (void)renderer; (void)spriteIndex; + SWRenderer* swr = (SWRenderer*) renderer; + + DataWin* dw = renderer->dataWin; + if (0 > spriteIndex || dw->sprt.count <= (uint32_t) spriteIndex) return; + + // Refuse to delete original data.win sprites + if (swr->originalSpriteCount > (uint32_t) spriteIndex) { + fprintf(stderr, "%s: Sprite index %d is invalid.\n", __func__, spriteIndex); + return; + } + + Sprite* sprite = &dw->sprt.sprites[spriteIndex]; + if (sprite->textureCount == 0) return; // already deleted + + for (uint32_t i = 0; i < sprite->textureCount; i++) + { + int32_t tpagIdx = sprite->tpagIndices[i]; + if (tpagIdx >= 0 && (uint32_t) tpagIdx >= swr->originalTPagCount) { + TexturePageItem* tpag = &dw->tpag.items[tpagIdx]; + int16_t pageId = tpag->texturePageId; + if (pageId >= 0 && swr->totalTextureCount > (uint32_t) pageId) { + swrFreeTexture(swr->textures[pageId]); + swr->textures[pageId] = NULL; + } + // Mark TPAG slot as free for reuse + tpag->texturePageId = -1; + } + } + + free(sprite->tpagIndices); + sprite->tpagIndices = NULL; + + const char* keepName = sprite->name; + memset(sprite, 0, sizeof(Sprite)); + sprite->name = keepName; + + fprintf(stderr, "SWR: Deleted sprite %d\n", spriteIndex); } static void SWRenderer_drawTiledPart(Renderer* renderer, int32_t tpagIndex, From a70e78605b6c6d9f5d29558c9a7e3774bd3f92be Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Tue, 19 May 2026 23:08:25 +0300 Subject: [PATCH 17/31] * Fix yet another rotation pivot issue. This fixes Muffet's teapot in her right hand rotating around the wrong point. --- src/sw/sw_renderer.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 024cf377..f4751e48 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -204,6 +204,12 @@ FORCE_INLINE bool swrMustRotateTolerant(float angleDeg) return swrAbs(angleDegInt) >= 1; // 1/16 of a degree } +FORCE_INLINE int swrSgn(float x) +{ + if (x < 0) return -1; + return 1; +} + FORCE_INLINE int swrFloor(float x) { int i = (int) x; @@ -1019,8 +1025,8 @@ static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x if (UNLIKELY(swrMustRotate(angleDeg))) { - float pivotX = x - dx; - float pivotY = y - dy; + float pivotX = (x - dx) * swrSgn(xscale); + float pivotY = (y - dy) * swrSgn(yscale); swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX, pivotY); } else From 83c3c178068e07d2a92321bf88ff7b9deec64620 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Tue, 19 May 2026 23:54:23 +0300 Subject: [PATCH 18/31] * Fix a bug where createSpriteFromSurface completely ignored screen scale. --- src/sw/sw_renderer.c | 48 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index f4751e48..7a02a571 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -538,6 +538,38 @@ static void swrTransformSizeIfNeeded(SWRenderer* swr, float* dx, float* dy) if (dy) *dy *= ((float)swr->portH / swr->viewH); } +static void swrTransformPosIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive) return; + + if (dx) { + *dx -= swr->viewX; + *dx = *dx * swr->portW / swr->viewW; + *dx += swr->portX; + } + if (dy) { + *dy -= swr->viewY; + *dy = *dy * swr->portH / swr->viewH; + *dy += swr->portY; + } +} + +static void swrTransformSizeIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive || !swr->viewW || !swr->viewH) return; + + if (dx) *dx = *dx * swr->portW / swr->viewW; + if (dy) *dy = *dy * swr->portH / swr->viewH; +} + +static void swrReverseTransformSizeIntIfNeeded(SWRenderer* swr, int32_t* dx, int32_t* dy) +{ + if (!swr->viewActive) return; + + if (dx) *dx = *dx * swr->viewW / swr->portW; + if (dy) *dy = *dy * swr->viewH / swr->portH; +} + FORCE_INLINE void swrPlotPixel(Renderer* renderer, int x, int y, uintpixel_t color, int alpha) { SWRenderer* swr = (SWRenderer*) renderer; @@ -1014,8 +1046,8 @@ static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x float dx = (float)(tpag->targetX - originX); float dy = (float)(tpag->targetY - originY); - int dw = (int)(xscale * sw); - int dh = (int)(yscale * sh); + int dw = (int)(xscale * tpag->targetWidth); + int dh = (int)(yscale * tpag->targetHeight); dx *= xscale; dy *= yscale; dx += x; @@ -1608,6 +1640,10 @@ static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t su { SWRenderer* swr = (SWRenderer*) renderer; + swrTransformPosIntIfNeeded(swr, &x, &y); + swrTransformSizeIntIfNeeded(swr, &w, &h); + swrTransformSizeIntIfNeeded(swr, &xorig, &yorig); + (void) removeback; (void) smooth; @@ -1655,6 +1691,10 @@ static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t su int32_t spriteW = w; int32_t spriteH = h; + + int32_t targetW = spriteW; + int32_t targetH = spriteH; + swrReverseTransformSizeIntIfNeeded(swr, &targetW, &targetH); swr->textures[texturePageId] = tex; @@ -1667,8 +1707,8 @@ static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t su tpag->sourceHeight = (uint16_t) spriteH; tpag->targetX = 0; tpag->targetY = 0; - tpag->targetWidth = (uint16_t) spriteW; - tpag->targetHeight = (uint16_t) spriteH; + tpag->targetWidth = (uint16_t) (spriteW * swr->viewW / swr->portW); + tpag->targetHeight = (uint16_t) (spriteH * swr->viewH / swr->portH); tpag->boundingWidth = (uint16_t) spriteW; tpag->boundingHeight = (uint16_t) spriteH; tpag->texturePageId = texturePageId; From c2aac3224a3777b372e46abd7b2127394547ff89 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Wed, 20 May 2026 00:00:28 +0300 Subject: [PATCH 19/31] * Fix a bug where pivotX/Y didn't take into account the diff between sourceWidth/Height and targetWidth/Height --- src/sw/sw_renderer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 7a02a571..41b41e57 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1059,6 +1059,12 @@ static void SWRenderer_drawSprite(Renderer* renderer, int32_t tpagIndex, float x { float pivotX = (x - dx) * swrSgn(xscale); float pivotY = (y - dy) * swrSgn(yscale); + + if (tpag->targetWidth != tpag->sourceWidth) + pivotX *= (float)tpag->targetWidth / tpag->sourceWidth; + if (tpag->targetHeight != tpag->sourceHeight) + pivotY *= (float)tpag->targetHeight/ tpag->sourceHeight; + swrDrawSpriteRotated(renderer, dx, dy, dw, dh, texture, sx, sy, sw, sh, color, alpha, angleDeg, pivotX, pivotY); } else From 5e43ec904f880251e736a609f772daecab56e1be Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Wed, 20 May 2026 00:00:56 +0300 Subject: [PATCH 20/31] * SDL Frontend: Use fbWidth/fbHeight instead of reqW/reqH for the SDL video mode. --- src/sdl/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/main.c b/src/sdl/main.c index 4fac77cb..5461aeb0 100644 --- a/src/sdl/main.c +++ b/src/sdl/main.c @@ -844,7 +844,7 @@ int main(int argc, char* argv[]) { fbWidth = reqW; fbHeight = reqH; if(!args.headless) { - scr = SDL_SetVideoMode(reqW, reqH, 0, useSWRend ? 0 : (SDL_OPENGL | SDL_RESIZABLE)); + scr = SDL_SetVideoMode(fbWidth, fbHeight, 0, useSWRend ? 0 : (SDL_OPENGL | SDL_RESIZABLE)); if (!scr && useSWRend) { SDL_Rect** modes = SDL_ListModes(NULL, SDL_FULLSCREEN); if (modes && modes != (SDL_Rect**) -1 && modes[0]) { From 66d1235cbc9316c6473a73544dddd4174948c752 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Wed, 20 May 2026 00:04:01 +0300 Subject: [PATCH 21/31] Revert "* SDL Frontend: Use fbWidth/fbHeight instead of reqW/reqH for the SDL video mode." This reverts commit 5e43ec904f880251e736a609f772daecab56e1be. --- src/sdl/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/main.c b/src/sdl/main.c index 5461aeb0..4fac77cb 100644 --- a/src/sdl/main.c +++ b/src/sdl/main.c @@ -844,7 +844,7 @@ int main(int argc, char* argv[]) { fbWidth = reqW; fbHeight = reqH; if(!args.headless) { - scr = SDL_SetVideoMode(fbWidth, fbHeight, 0, useSWRend ? 0 : (SDL_OPENGL | SDL_RESIZABLE)); + scr = SDL_SetVideoMode(reqW, reqH, 0, useSWRend ? 0 : (SDL_OPENGL | SDL_RESIZABLE)); if (!scr && useSWRend) { SDL_Rect** modes = SDL_ListModes(NULL, SDL_FULLSCREEN); if (modes && modes != (SDL_Rect**) -1 && modes[0]) { From cb5c0be5d6cdca742549add30bec1d6d3b3cdd0e Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Wed, 20 May 2026 22:43:29 +0300 Subject: [PATCH 22/31] Add enableApplicationSurface. --- src/sw/sw_renderer.c | 110 +++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 41b41e57..0584109a 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1584,6 +1584,11 @@ static bool SWRenderer_setRenderTarget(Renderer* renderer, int32_t surfaceID) static float SWRenderer_getSurfaceWidth(Renderer* renderer, int32_t surfaceID) { + SWRenderer* swr = (SWRenderer*) renderer; + if (surfaceID == APPLICATION_SURFACE_ID) { + return (float) swr->width; + } + UNIMP(); (void)renderer; (void)surfaceID; return 0.0f; @@ -1591,6 +1596,11 @@ static float SWRenderer_getSurfaceWidth(Renderer* renderer, int32_t surfaceID) static float SWRenderer_getSurfaceHeight(Renderer* renderer, int32_t surfaceID) { + SWRenderer* swr = (SWRenderer*) renderer; + if (surfaceID == APPLICATION_SURFACE_ID) { + return (float) swr->height; + } + UNIMP(); (void)renderer; (void)surfaceID; return 0.0f; @@ -1789,53 +1799,63 @@ static void SWRenderer_drawTiledPart(Renderer* renderer, int32_t tpagIndex, (void)color; (void)alpha; } +static int32_t SWRenderer_ensureApplicationSurface(Renderer* renderer, int32_t width, int32_t height) +{ + UNIMP(); + (void) renderer; + (void) width; + (void) height; + return -1; +} + static RendererVtable swrVtable = { - .init = SWRenderer_init, - .destroy = SWRenderer_destroy, - .beginFrame = SWRenderer_beginFrame, - .endFrameInit = SWRenderer_endFrameInit, - .endFrameEnd = SWRenderer_endFrameEnd, - .beginView = SWRenderer_beginView, - .endView = SWRenderer_endView, - .beginGUI = SWRenderer_beginGUI, - .endGUI = SWRenderer_endGUI, - .drawSprite = SWRenderer_drawSprite, - .drawSpritePart = SWRenderer_drawSpritePart, - .drawSpritePos = SWRenderer_drawSpritePos, - .drawRectangle = SWRenderer_drawRectangle, - .drawRectangleColor = SWRenderer_drawRectangleColor, - .drawLine = SWRenderer_drawLine, - .drawTriangle = SWRenderer_drawTriangle, - .drawLineColor = SWRenderer_drawLineColor, - .drawText = SWRenderer_drawText, - .drawTextColor = SWRenderer_drawTextColor, - .flush = SWRenderer_flush, - .clearScreen = SWRenderer_clearScreen, - .createSpriteFromSurface = SWRenderer_createSpriteFromSurface, - .deleteSprite = SWRenderer_deleteSprite, - .gpuSetBlendMode = SWRenderer_gpuSetBlendMode, - .gpuSetBlendModeExt = SWRenderer_gpuSetBlendModeExt, - .gpuSetBlendEnable = SWRenderer_gpuSetBlendEnable, - .gpuSetAlphaTestEnable = SWRenderer_gpuSetAlphaTestEnable, - .gpuSetAlphaTestRef = SWRenderer_gpuSetAlphaTestRef, - .gpuSetColorWriteEnable = SWRenderer_gpuSetColorWriteEnable, - .gpuGetColorWriteEnable = SWRenderer_gpuGetColorWriteEnable, - .gpuGetBlendEnable = SWRenderer_gpuGetBlendEnable, - .gpuSetFog = SWRenderer_gpuSetFog, - .drawTile = NULL, - .drawTiled = SWRenderer_drawTiled, - .createSurface = SWRenderer_createSurface, - .surfaceExists = SWRenderer_surfaceExists, - .setRenderTarget = SWRenderer_setRenderTarget, - .getSurfaceWidth = SWRenderer_getSurfaceWidth, - .getSurfaceHeight = SWRenderer_getSurfaceHeight, - .drawSurface = SWRenderer_drawSurface, - .surfaceResize = SWRenderer_surfaceResize, - .surfaceFree = SWRenderer_surfaceFree, - .surfaceCopy = SWRenderer_surfaceCopy, - .surfaceGetPixels = SWRenderer_surfaceGetPixels, - .drawTiledPart = SWRenderer_drawTiledPart, + .init = SWRenderer_init, + .destroy = SWRenderer_destroy, + .beginFrame = SWRenderer_beginFrame, + .endFrameInit = SWRenderer_endFrameInit, + .endFrameEnd = SWRenderer_endFrameEnd, + .beginView = SWRenderer_beginView, + .endView = SWRenderer_endView, + .beginGUI = SWRenderer_beginGUI, + .endGUI = SWRenderer_endGUI, + .drawSprite = SWRenderer_drawSprite, + .drawSpritePart = SWRenderer_drawSpritePart, + .drawSpritePos = SWRenderer_drawSpritePos, + .drawRectangle = SWRenderer_drawRectangle, + .drawRectangleColor = SWRenderer_drawRectangleColor, + .drawLine = SWRenderer_drawLine, + .drawTriangle = SWRenderer_drawTriangle, + .drawLineColor = SWRenderer_drawLineColor, + .drawText = SWRenderer_drawText, + .drawTextColor = SWRenderer_drawTextColor, + .flush = SWRenderer_flush, + .clearScreen = SWRenderer_clearScreen, + .createSpriteFromSurface = SWRenderer_createSpriteFromSurface, + .deleteSprite = SWRenderer_deleteSprite, + .gpuSetBlendMode = SWRenderer_gpuSetBlendMode, + .gpuSetBlendModeExt = SWRenderer_gpuSetBlendModeExt, + .gpuSetBlendEnable = SWRenderer_gpuSetBlendEnable, + .gpuSetAlphaTestEnable = SWRenderer_gpuSetAlphaTestEnable, + .gpuSetAlphaTestRef = SWRenderer_gpuSetAlphaTestRef, + .gpuSetColorWriteEnable = SWRenderer_gpuSetColorWriteEnable, + .gpuGetColorWriteEnable = SWRenderer_gpuGetColorWriteEnable, + .gpuGetBlendEnable = SWRenderer_gpuGetBlendEnable, + .gpuSetFog = SWRenderer_gpuSetFog, + .drawTile = NULL, + .drawTiled = SWRenderer_drawTiled, + .createSurface = SWRenderer_createSurface, + .surfaceExists = SWRenderer_surfaceExists, + .setRenderTarget = SWRenderer_setRenderTarget, + .ensureApplicationSurface = SWRenderer_ensureApplicationSurface, + .getSurfaceWidth = SWRenderer_getSurfaceWidth, + .getSurfaceHeight = SWRenderer_getSurfaceHeight, + .drawSurface = SWRenderer_drawSurface, + .surfaceResize = SWRenderer_surfaceResize, + .surfaceFree = SWRenderer_surfaceFree, + .surfaceCopy = SWRenderer_surfaceCopy, + .surfaceGetPixels = SWRenderer_surfaceGetPixels, + .drawTiledPart = SWRenderer_drawTiledPart, }; void SWRenderer_clearFrameBuffer(Renderer* renderer, uint32_t color) From c951bd660c479a471cbbbc0e043cf91709a1b6f1 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sat, 23 May 2026 20:04:03 +0300 Subject: [PATCH 23/31] * Update drawText / drawTextColor definitions --- src/sw/sw_renderer.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 0584109a..14dbb3ae 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1276,7 +1276,7 @@ static bool swrResolveGlyph( return true; } -static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, float xscale, float yscale, UNUSED float angleDeg, int32_t color, UNUSED float alpha) +static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, float xscale, float yscale, float angleDeg, int32_t color, float alpha, float lineSeparation) { Renderer* renderer = &swr->base; DataWin* dwin = renderer->dataWin; @@ -1301,7 +1301,7 @@ static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, flo int textLen = (int) strlen(text); int lineCount = TextUtils_countLines(text, textLen); - float lineStride = TextUtils_lineStride(font); + float lineStride = (0.0f > lineSeparation) ? TextUtils_lineStride(font) : (lineSeparation / (font->scaleY != 0.0f ? font->scaleY : 1.0f)); // Vertical alignment offset float totalHeight = (float) lineCount * lineStride; @@ -1406,18 +1406,19 @@ static void swrDrawText(SWRenderer* swr, const char* text, float x, float y, flo } static void SWRenderer_drawText(Renderer* renderer, const char* text, float x, float y, - float xscale, float yscale, float angleDeg) + float xscale, float yscale, float angleDeg, float lineSeparation) { SWRenderer* swr = (SWRenderer*) renderer; - swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, renderer->drawColor, renderer->drawAlpha); + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, renderer->drawColor, renderer->drawAlpha, lineSeparation); } static void SWRenderer_drawTextColor(Renderer* renderer, const char* text, float x, float y, float xscale, float yscale, float angleDeg, - int32_t c1, int32_t c2, int32_t c3, int32_t c4, float alpha) + int32_t c1, int32_t c2, int32_t c3, int32_t c4, float alpha, + float lineSeparation) { SWRenderer* swr = (SWRenderer*) renderer; - swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha); + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha, lineSeparation); } static void SWRenderer_drawTiled(Renderer* renderer, int32_t tpagIndex, From e8eef6da1de301d048110b4eab6c739973d497ba Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sat, 23 May 2026 22:54:23 +0300 Subject: [PATCH 24/31] * Fix a bug with rotations Instead of scaling the position, scale the size. This fixes the smoke in Hotland's booster pad sections being all weird. --- src/sw/sw_renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 14dbb3ae..81b11fb7 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -996,7 +996,7 @@ static void swrDrawSpriteRotated( SWRenderer* swr = (SWRenderer*) renderer; swrTransformPosIfNeeded(swr, &dx, &dy); - swrTransformPosIfNeeded(swr, &pivotX, &pivotY); + swrTransformSizeIfNeeded(swr, &pivotX, &pivotY); swrTransformSizeIfNeeded(swr, &dw, &dh); swrDrawSpriteRotatedInternal( From 1024353b6c7c7010945528f498de8479e9aac8eb Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sat, 23 May 2026 23:04:23 +0300 Subject: [PATCH 25/31] * Add support for drawLineColor. Right now, it's incredibly bare-bones. --- src/sw/sw_renderer.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 81b11fb7..70404b26 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1178,6 +1178,8 @@ static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, floa (void)outline; uintpixel_t drawColorCvt = swrConvertPixel(renderer->drawColor); + + // TODO: draw triangle properly. swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, renderer->drawAlpha); swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, renderer->drawAlpha); swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, renderer->drawAlpha); @@ -1186,9 +1188,9 @@ static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, floa static void SWRenderer_drawLineColor(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uint32_t color1, uint32_t color2, float alpha) { - (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; - (void)width; (void)color1; (void)color2; (void)alpha; - UNIMP(); + // TODO: implement gradient properly + uintpixel_t actualColor = tint(swrConvertPixel(color1), swrConvertPixel(color2)); + swrDrawLine(renderer, x1, y1, x2, y2, width, actualColor, alpha); } typedef struct From a81e9c04ab43de94f8312c88c54457f854ae2bf6 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Sat, 23 May 2026 23:24:12 +0300 Subject: [PATCH 26/31] * Add support for gradient lines. Also, partial support for drawRectangleColor --- src/sw/sw_renderer.c | 229 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 197 insertions(+), 32 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 70404b26..9b3c0db1 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -580,7 +580,7 @@ FORCE_INLINE void swrPlotPixel(Renderer* renderer, int x, int y, uintpixel_t col alphaBlend(&swr->fb[y * swr->fbPitch + x], color, alpha); } -static void swrDrawHLineInt(Renderer* renderer, int dx, int dy, int dw, uintpixel_t color, int alpha) +static void swrDrawHLineInt(Renderer* renderer, int dx, int dy, int dw, uintpixel_t color, UNUSED uintpixel_t color2, int alpha) { SWRenderer *swr = (SWRenderer*) renderer; @@ -590,12 +590,48 @@ static void swrDrawHLineInt(Renderer* renderer, int dx, int dy, int dw, uintpixe if (dx + dw >= swr->width) dw = swr->width - dx; if (dw <= 0) return; - uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; - for (int i = 0; i < dw; i++) - alphaBlend(&line[i], color, alpha); +#if PIXEL_SIZE == 32 + if (color == color2) +#endif + { + uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; + for (int i = 0; i < dw; i++) + alphaBlend(&line[i], color, alpha); + } +#if PIXEL_SIZE == 32 + else + { + Pixel32ARGB clr1, clr2; + clr1.l = color; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; + rstep /= dw; + gstep /= dw; + bstep /= dw; + + uintpixel_t *line = &swr->fb[dy * swr->fbPitch + dx]; + for (int i = 0; i < dw; i++) + { + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + alphaBlend(&line[i], resultPixel.l, alpha); + } + } +#endif } -static void swrDrawHLine(Renderer* renderer, float dx, float dy, float dw, uintpixel_t color, float alpha) +static void swrDrawHLine(Renderer* renderer, float dx, float dy, float dw, uintpixel_t color, uintpixel_t color2, float alpha) { SWRenderer *swr = (SWRenderer*) renderer; float thickness = 1; @@ -604,10 +640,10 @@ static void swrDrawHLine(Renderer* renderer, float dx, float dy, float dw, uintp swrTransformSizeIfNeeded(swr, &dw, &thickness); // TODO: use thickness - swrDrawHLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dw), color, swrIntAlpha(alpha)); + swrDrawHLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dw), color, color2, swrIntAlpha(alpha)); } -static void swrDrawVLineInt(Renderer* renderer, int dx, int dy, int dh, uintpixel_t color, int alpha) +static void swrDrawVLineInt(Renderer* renderer, int dx, int dy, int dh, uintpixel_t color, UNUSED uintpixel_t color2, int alpha) { SWRenderer *swr = (SWRenderer*) renderer; @@ -617,14 +653,50 @@ static void swrDrawVLineInt(Renderer* renderer, int dx, int dy, int dh, uintpixe if (dy + dh >= swr->height) dh = swr->height - dy; if (dh <= 0) return; - for (int i = 0; i < dh; i++) +#if PIXEL_SIZE == 32 + if (color == color2) +#endif + { + for (int i = 0; i < dh; i++) + { + uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; + alphaBlend(&line[0], color, alpha); + } + } +#if PIXEL_SIZE == 32 + else { - uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; - alphaBlend(&line[0], color, alpha); + Pixel32ARGB clr1, clr2; + clr1.l = color; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; + rstep /= dh; + gstep /= dh; + bstep /= dh; + + for (int i = 0; i < dh; i++) + { + uintpixel_t *line = &swr->fb[(dy + i) * swr->fbPitch + dx]; + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + alphaBlend(&line[0], resultPixel.l, alpha); + } } +#endif } -static void swrDrawVLine(Renderer* renderer, float dx, float dy, float dh, uintpixel_t color, float alpha) +static void swrDrawVLine(Renderer* renderer, float dx, float dy, float dh, uintpixel_t color, uintpixel_t color2, float alpha) { SWRenderer *swr = (SWRenderer*) renderer; float thickness = 1; @@ -633,27 +705,35 @@ static void swrDrawVLine(Renderer* renderer, float dx, float dy, float dh, uintp swrTransformSizeIfNeeded(swr, &thickness, &dh); // TODO: use thickness - swrDrawVLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dh), color, swrIntAlpha(alpha)); + swrDrawVLineInt(renderer, swrFloor(dx), swrFloor(dy), swrCeiling(dh), color, color2, swrIntAlpha(alpha)); } static void swrDrawRectangle(Renderer* renderer, float x1, float y1, float x2, float y2, uintpixel_t color, float alpha) { - swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color, alpha); - swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color, alpha); - swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color, alpha); - swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color, alpha); + swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color, color, alpha); + swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color, color, alpha); + swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color, color, alpha); + swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color, color, alpha); +} + +static void swrDrawRectangleColor(Renderer* renderer, float x1, float y1, float x2, float y2, uintpixel_t color1, uintpixel_t color2, uintpixel_t color3, uintpixel_t color4, float alpha) +{ + swrDrawHLine(renderer, x1, y1, (x2 - x1) + 1, color1, color2, alpha); + swrDrawHLine(renderer, x1, y2, (x2 - x1) + 1, color3, color4, alpha); + swrDrawVLine(renderer, x1, y1, (y2 - y1) + 1, color1, color3, alpha); + swrDrawVLine(renderer, x2, y1, (y2 - y1) + 1, color2, color4, alpha); } -static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, int width, uintpixel_t color, int alpha) +static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, int width, uintpixel_t color1, uintpixel_t color2, int alpha) { if (x1 == x2) { - swrDrawVLineInt(renderer, x1, swrMin(y1, y2), swrAbs(y1 - y2), color, alpha); + swrDrawVLineInt(renderer, x1, swrMin(y1, y2), swrAbs(y1 - y2), color1, color2, alpha); return; } if (y1 == y2) { - swrDrawHLineInt(renderer, swrMin(x1, x2), y1, swrAbs(x1 - x2), color, alpha); + swrDrawHLineInt(renderer, swrMin(x1, x2), y1, swrAbs(x1 - x2), color1, color2, alpha); return; } @@ -661,6 +741,20 @@ static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, i int dx1 = swrAbs(dx), dy1 = swrAbs(dy), xe, ye, x, y; int px = 2 * dy1 - dx1, py = 2 * dx1 - dy1; + uintpixel_t color = color1; +#if PIXEL_SIZE == 32 + Pixel32ARGB clr1, clr2; + clr1.l = color1; + clr2.l = color2; + + uint32_t rinit = clr1.p.r << 20; + uint32_t ginit = clr1.p.g << 20; + uint32_t binit = clr1.p.b << 20; + int32_t rstep = ((int)clr2.p.r - clr1.p.r) << 20; + int32_t gstep = ((int)clr2.p.g - clr1.p.g) << 20; + int32_t bstep = ((int)clr2.p.b - clr1.p.b) << 20; +#endif + if (dy1 <= dx1) { if (dx >= 0) @@ -672,6 +766,16 @@ static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, i x = x2, y = y2, xe = x1; } +#if PIXEL_SIZE == 32 + if (dx1 > 0) { + rstep /= dx1; + gstep /= dx1; + bstep /= dx1; + } else { + rstep = gstep = bstep = 0; + } +#endif + swrPlotPixel(renderer, x, y, color, alpha); for (int i = 0; x < xe; i++) @@ -687,6 +791,17 @@ static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, i px += 2 * (dy1 - dx1); } +#if PIXEL_SIZE == 32 + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + color = resultPixel.l; +#endif + swrPlotPixel(renderer, x, y, color, alpha); } } @@ -701,6 +816,16 @@ static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, i x = x2, y = y2, ye = y1; } +#if PIXEL_SIZE == 32 + if (dy1 > 0) { + rstep /= dy1; + gstep /= dy1; + bstep /= dy1; + } else { + rstep = gstep = bstep = 0; + } +#endif + swrPlotPixel(renderer, x, y, color, alpha); for (int i = 0; y < ye; i++) @@ -716,18 +841,29 @@ static void swrDrawLineInt(Renderer* renderer, int x1, int y1, int x2, int y2, i py += 2 * (dx1 - dy1); } +#if PIXEL_SIZE == 32 + Pixel32ARGB resultPixel; + resultPixel.p.r = rinit >> 20; + resultPixel.p.g = ginit >> 20; + resultPixel.p.b = binit >> 20; + rinit += rstep; + ginit += gstep; + binit += bstep; + color = resultPixel.l; +#endif + swrPlotPixel(renderer, x, y, color, alpha); } } } -static void swrDrawLine(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uintpixel_t color, float alpha) +static void swrDrawLine(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uintpixel_t color, uintpixel_t color2, float alpha) { SWRenderer* swr = (SWRenderer*) renderer; swrTransformPosIfNeeded(swr, &x1, &y1); swrTransformPosIfNeeded(swr, &x2, &y2); swrTransformSizeIfNeeded(swr, &width, NULL); - swrDrawLineInt(renderer, swrFloor(x1), swrFloor(y1), swrCeiling(x2), swrCeiling(y2), swrCeiling(width), color, swrIntAlpha(alpha)); + swrDrawLineInt(renderer, swrFloor(x1), swrFloor(y1), swrCeiling(x2), swrCeiling(y2), swrCeiling(width), color, color2, swrIntAlpha(alpha)); } static void swrDrawSpriteInternal( @@ -1149,7 +1285,7 @@ static void SWRenderer_drawRectangle(Renderer* renderer, float x1, float y1, flo if (xd <= 0 || yd <= 0) return; for (int y = 0; y <= yd; y++) { - swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor, alphaInt); + swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor, pxcolor, alphaInt); } } } @@ -1158,9 +1294,33 @@ static void SWRenderer_drawRectangleColor(Renderer* renderer, float x1, float y1 uint32_t color1, uint32_t color2, uint32_t color3, uint32_t color4, float alpha, bool outline) { - (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; - (void)color1; (void)color2; (void)color3; (void)color4; (void)alpha; (void)outline; - UNIMP(); + uintpixel_t pxcolor1 = swrConvertPixel(color1); + uintpixel_t pxcolor2 = swrConvertPixel(color2); + uintpixel_t pxcolor3 = swrConvertPixel(color3); + uintpixel_t pxcolor4 = swrConvertPixel(color4); + + SWRenderer* swr = (SWRenderer*) renderer; + + if (outline) + { + swrDrawRectangleColor(renderer, x1, y1, x2, y2, pxcolor1, pxcolor2, pxcolor3, pxcolor4, alpha); + } + else + { + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + + int alphaInt = swrIntAlpha(alpha); + int x1i = swrFloor(x1), x2i = swrCeiling(x2), y1i = swrFloor(y1), y2i = swrCeiling(y2); + int xd = x2i - x1i; + int yd = y2i - y1i; + if (xd <= 0 || yd <= 0) return; + + // TODO: blending vertically + for (int y = 0; y <= yd; y++) { + swrDrawHLineInt(renderer, x1i, y1i + y, xd, pxcolor1, pxcolor2, alphaInt); + } + } } static void SWRenderer_drawLine(Renderer* renderer, float x1, float y1, float x2, float y2, @@ -1169,7 +1329,8 @@ static void SWRenderer_drawLine(Renderer* renderer, float x1, float y1, float x2 (void)renderer; (void)x1; (void)y1; (void)x2; (void)y2; (void)width; (void)color; (void)alpha; - swrDrawLine(renderer, x1, y1, x2, y2, width, swrConvertPixel(color), alpha); + uintpixel_t colorCvt = swrConvertPixel(color); + swrDrawLine(renderer, x1, y1, x2, y2, width, colorCvt, colorCvt, alpha); } static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, @@ -1180,17 +1341,15 @@ static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, floa uintpixel_t drawColorCvt = swrConvertPixel(renderer->drawColor); // TODO: draw triangle properly. - swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, renderer->drawAlpha); - swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, renderer->drawAlpha); - swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); } static void SWRenderer_drawLineColor(Renderer* renderer, float x1, float y1, float x2, float y2, float width, uint32_t color1, uint32_t color2, float alpha) { - // TODO: implement gradient properly - uintpixel_t actualColor = tint(swrConvertPixel(color1), swrConvertPixel(color2)); - swrDrawLine(renderer, x1, y1, x2, y2, width, actualColor, alpha); + swrDrawLine(renderer, x1, y1, x2, y2, width, swrConvertPixel(color1), swrConvertPixel(color2), alpha); } typedef struct @@ -1420,6 +1579,12 @@ static void SWRenderer_drawTextColor(Renderer* renderer, const char* text, float float lineSeparation) { SWRenderer* swr = (SWRenderer*) renderer; + + // TODO: allow c2, c3, c4 + (void) c2; + (void) c3; + (void) c4; + swrDrawText(swr, text, x, y, xscale, yscale, angleDeg, c1, renderer->drawAlpha, lineSeparation); } From cea76f115eec8d80c1318bc7586e062276897eac Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 25 May 2026 17:26:19 +0300 Subject: [PATCH 27/31] * Add initial implementation of the triangle render function. This function renders a triangle. Obviously. --- src/sw/sw_renderer.c | 135 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 8 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 9b3c0db1..73ee33b0 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -412,10 +412,23 @@ static void SWRenderer_endFrameInit(Renderer* renderer) //this is kinda useless to do twice isn't it? } +static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, + float x3, float y3, bool outline); + static void SWRenderer_endFrameEnd(Renderer* renderer) { SWRenderer* swr = (SWRenderer*) renderer; assert(!swr->drawingToSurface); + + static int i = 0; + i += 4; + i = i % 480; + + renderer->drawColor = 0xFFFFFF; + SWRenderer_drawTriangle(renderer, 320, 480 - i, 620, 240, 20, i, false); + //renderer->drawColor = 0xFFFF00; + //SWRenderer_drawTriangle(renderer, 320, 40, 20, 440, 620, 360, true); + Runner_setNextFrame(swr->fb, swr->width, swr->height); } @@ -1333,17 +1346,123 @@ static void SWRenderer_drawLine(Renderer* renderer, float x1, float y1, float x2 swrDrawLine(renderer, x1, y1, x2, y2, width, colorCvt, colorCvt, alpha); } -static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, - float x3, float y3, bool outline) +static void swrDrawTriangleInternal(SWRenderer* swr, int xup, int yup, int xleft, int yleft, int xright, int yright, uint32_t color, int alpha) { - (void)outline; + // Figure out the maximum Y extent of the triangle. + // (Note that we know yup is the minimum.) + int xmid, ymid, xmid2, xmax, ymax; + if (yleft < yright) { + xmax = xright, ymax = yright; + xmid = xleft, ymid = yleft; + if (yright == yup) + xmid2 = xup; + else + xmid2 = xup + (xright - xup) * (ymid - yup) / (yright - yup); + } else { + xmax = xleft, ymax = yleft; + xmid = xright, ymid = yright; + if (yleft == yup) + xmid2 = xup; + else + xmid2 = xup + (xleft - xup) * (ymid - yup) / (yleft - yup); + } - uintpixel_t drawColorCvt = swrConvertPixel(renderer->drawColor); + for (int y = yup; y <= ymax; y++) + { + if (y < 0) continue; + if (y >= swr->height) break; + + int x1 = xup, x2 = xup; + if (y < ymid) + { + // Lines: between up and mid, and between up and max + if (ymid != yup) + x1 = xup + (xmid - xup + 1) * (y - yup) / (ymid - yup); + + if (ymid != yup) + x2 = xup + (xmid2 - xup + 1) * (y - yup) / (ymid - yup); + } + else + { + // Lines: between mid and max, and between up and max + if (ymax != yup) + x1 = xup + (xmax - xup + 1) * (y - yup) / (ymax - yup); + + if (ymax != ymid) + x2 = xmid + (xmax - xmid + 1) * (y - ymid) / (ymax - ymid); + } + + if (x1 >= x2) { + int tmp = x1; + x1 = x2; + x2 = tmp; + } + + if (x1 < 0) x1 = 0; + if (x1 >= swr->width) return; + if (x2 < 0) return; + if (x2 >= swr->width) x2 = swr->width - 1; + if (x1 > x2) return; + + uintpixel_t* line = &swr->fb[y * swr->width]; + for (int x = x1; x <= x2; x++) { + alphaBlend(&line[x], color, alpha); + } + } +} + +static void swrDrawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, float x3, float y3, uint32_t color, float alpha) +{ + float xup, yup, xleft, yleft, xright, yright; + + //which vertex is higher? + xup = x1, yup = y1; + xleft = x2, yleft = y2; + xright = x3, yright = y3; + if (yup > y2) { + xup = x2, yup = y2; + xleft = x1, yleft = y1; + //xright = x3, yright = y3; + } + if (yup > y3) { + xup = x3, yup = y3; + xleft = x1, yleft = y1; + xright = x2, yright = y2; + } + + if (xleft > xright) { + float tmp = xleft; + xleft = xright; + xright = tmp; + tmp = yleft; + yleft = yright; + yright = tmp; + } - // TODO: draw triangle properly. - swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); - swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); - swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + swrDrawTriangleInternal( + (SWRenderer*) renderer, + swrFloor(xup), swrFloor(yup), + swrFloor(xleft), swrCeiling(yleft), + swrFloor(xright), swrCeiling(yright), + swrConvertPixel(color), + swrIntAlpha(alpha) + ); +} + +static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, + float x3, float y3, bool outline) +{ + if (outline) + { + uintpixel_t drawColorCvt = swrConvertPixel(renderer->drawColor); + swrDrawLine(renderer, x1, y1, x2, y2, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x1, y1, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + swrDrawLine(renderer, x2, y2, x3, y3, 1, drawColorCvt, drawColorCvt, renderer->drawAlpha); + } + else + { + swrDrawTriangle(renderer, x1, y1, x2, y2, x3, y3, renderer->drawColor, renderer->drawAlpha); + } } static void SWRenderer_drawLineColor(Renderer* renderer, float x1, float y1, float x2, float y2, From 3f3b3d090b3cc8677f7c501db9ae74a9b9c0a809 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 25 May 2026 17:27:16 +0300 Subject: [PATCH 28/31] * Actually apply transforms to this triangle + remove the test code. --- src/sw/sw_renderer.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 73ee33b0..5d86cebe 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -412,23 +412,10 @@ static void SWRenderer_endFrameInit(Renderer* renderer) //this is kinda useless to do twice isn't it? } -static void SWRenderer_drawTriangle(Renderer* renderer, float x1, float y1, float x2, float y2, - float x3, float y3, bool outline); - static void SWRenderer_endFrameEnd(Renderer* renderer) { SWRenderer* swr = (SWRenderer*) renderer; assert(!swr->drawingToSurface); - - static int i = 0; - i += 4; - i = i % 480; - - renderer->drawColor = 0xFFFFFF; - SWRenderer_drawTriangle(renderer, 320, 480 - i, 620, 240, 20, i, false); - //renderer->drawColor = 0xFFFF00; - //SWRenderer_drawTriangle(renderer, 320, 40, 20, 440, 620, 360, true); - Runner_setNextFrame(swr->fb, swr->width, swr->height); } @@ -1415,6 +1402,11 @@ static void swrDrawTriangle(Renderer* renderer, float x1, float y1, float x2, fl { float xup, yup, xleft, yleft, xright, yright; + SWRenderer* swr = (SWRenderer*) renderer; + swrTransformPosIfNeeded(swr, &x1, &y1); + swrTransformPosIfNeeded(swr, &x2, &y2); + swrTransformPosIfNeeded(swr, &x3, &y3); + //which vertex is higher? xup = x1, yup = y1; xleft = x2, yleft = y2; @@ -1440,7 +1432,7 @@ static void swrDrawTriangle(Renderer* renderer, float x1, float y1, float x2, fl } swrDrawTriangleInternal( - (SWRenderer*) renderer, + swr, swrFloor(xup), swrFloor(yup), swrFloor(xleft), swrCeiling(yleft), swrFloor(xright), swrCeiling(yright), From 140304e44ac99faec2f3e25e8c3615ff42810030 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 25 May 2026 18:00:25 +0300 Subject: [PATCH 29/31] * Bugfixes with the triangle renderer --- src/sw/sw_renderer.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 5d86cebe..37e52d12 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1337,20 +1337,16 @@ static void swrDrawTriangleInternal(SWRenderer* swr, int xup, int yup, int xleft { // Figure out the maximum Y extent of the triangle. // (Note that we know yup is the minimum.) - int xmid, ymid, xmid2, xmax, ymax; + int xmid, ymid, xmid2 = xup, xmax, ymax; if (yleft < yright) { xmax = xright, ymax = yright; xmid = xleft, ymid = yleft; - if (yright == yup) - xmid2 = xup; - else + if (yright != yup) xmid2 = xup + (xright - xup) * (ymid - yup) / (yright - yup); } else { xmax = xleft, ymax = yleft; xmid = xright, ymid = yright; - if (yleft == yup) - xmid2 = xup; - else + if (yleft != yup) xmid2 = xup + (xleft - xup) * (ymid - yup) / (yleft - yup); } @@ -1360,23 +1356,23 @@ static void swrDrawTriangleInternal(SWRenderer* swr, int xup, int yup, int xleft if (y >= swr->height) break; int x1 = xup, x2 = xup; - if (y < ymid) + if (y <= ymid) { // Lines: between up and mid, and between up and max if (ymid != yup) - x1 = xup + (xmid - xup + 1) * (y - yup) / (ymid - yup); + x1 = xup + (xmid - xup) * (y - yup) / (ymid - yup); if (ymid != yup) - x2 = xup + (xmid2 - xup + 1) * (y - yup) / (ymid - yup); + x2 = xup + (xmid2 - xup) * (y - yup) / (ymid - yup); } else { // Lines: between mid and max, and between up and max if (ymax != yup) - x1 = xup + (xmax - xup + 1) * (y - yup) / (ymax - yup); + x1 = xup + (xmax - xup) * (y - yup) / (ymax - yup); if (ymax != ymid) - x2 = xmid + (xmax - xmid + 1) * (y - ymid) / (ymax - ymid); + x2 = xmid + (xmax - xmid) * (y - ymid) / (ymax - ymid); } if (x1 >= x2) { @@ -1386,10 +1382,10 @@ static void swrDrawTriangleInternal(SWRenderer* swr, int xup, int yup, int xleft } if (x1 < 0) x1 = 0; - if (x1 >= swr->width) return; - if (x2 < 0) return; + if (x1 >= swr->width) continue; + if (x2 < 0) continue; if (x2 >= swr->width) x2 = swr->width - 1; - if (x1 > x2) return; + if (x1 > x2) continue; uintpixel_t* line = &swr->fb[y * swr->width]; for (int x = x1; x <= x2; x++) { From 23bd43d09315c9f5b7dc7966f977fb6fd12e5e66 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 25 May 2026 18:30:34 +0300 Subject: [PATCH 30/31] * Fix a bug where drawSpritePart was doubly handling sprite flipping. This results in sprites not being flipped when they should be, e.g. Asriel's background in his battle --- src/sw/sw_renderer.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 37e52d12..4d5fc116 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1219,10 +1219,6 @@ static void SWRenderer_drawSpritePart(Renderer* renderer, int32_t tpagIndex, if (tpagIndex < 0 || (uint32_t) tpagIndex >= dwin->tpag.count) return; - bool flipX = false, flipY = false; - if (xscale < 0) flipX = true, xscale = -xscale; - if (yscale < 0) flipY = true, yscale = -yscale; - TexturePageItem* tpag = &dwin->tpag.items[tpagIndex]; int16_t pageId = tpag->texturePageId; if (0 > pageId || swr->totalTextureCount <= (uint32_t) pageId) return; @@ -1237,8 +1233,6 @@ static void SWRenderer_drawSpritePart(Renderer* renderer, int32_t tpagIndex, float dy = y; int dw = swrCeiling(xscale * sw); int dh = swrCeiling(yscale * sh); - if (flipX) dx -= dw; - if (flipY) dy -= dh; SWTexture* texture = swr->textures[pageId]; From 08090dc81c7db5d1d90ba0ef729ec4e895ff15e9 Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Mon, 25 May 2026 20:11:07 +0300 Subject: [PATCH 31/31] * Small 8bpp renderer fix --- src/sw/sw_renderer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sw/sw_renderer.c b/src/sw/sw_renderer.c index 4d5fc116..faf0e271 100644 --- a/src/sw/sw_renderer.c +++ b/src/sw/sw_renderer.c @@ -1967,8 +1967,12 @@ static int32_t SWRenderer_createSpriteFromSurface(Renderer* renderer, int32_t su // in-bounds for (; sx < swr->width && ix < w; sx++, ix++) +#if PIXEL_SIZE == 8 + dstline[ix] = srcline[ix]; +#else dstline[ix] = srcline[ix] | TRANSPARENT_MASK; - +#endif + // right edge for (; ix < w; ix++) dstline[ix] = 0;