From 62898c85f8af99360078c6efe01c9a0194191280 Mon Sep 17 00:00:00 2001 From: John Lorentzson Date: Thu, 17 Apr 2025 11:34:31 +0200 Subject: [PATCH] Add system that renders "cheap" "tiles" without color keying The game appears to render level tiles as 16x16 pixel tiles, and many of these do not require any transparency. So we now compute which tiles are completely opaque and store those in a set, so that when we draw them we can skip color keying. --- src/Backends/Rendering/Software.cpp | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/Backends/Rendering/Software.cpp b/src/Backends/Rendering/Software.cpp index f59c0d63..572fc06b 100644 --- a/src/Backends/Rendering/Software.cpp +++ b/src/Backends/Rendering/Software.cpp @@ -3,6 +3,8 @@ #include "../Rendering.h" +#include +#include #include #include #include @@ -14,12 +16,16 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) +int surfaceCount = 0; + typedef struct RenderBackend_Surface { unsigned char *pixels; size_t width; size_t height; size_t pitch; + int id; + std::set* cheapRectangles; } RenderBackend_Surface; typedef struct RenderBackend_GlyphAtlas @@ -86,12 +92,16 @@ RenderBackend_Surface* RenderBackend_CreateSurface(size_t width, size_t height, surface->width = width; surface->height = height; surface->pitch = width * 4; + surface->id = surfaceCount; + surface->cheapRectangles = new std::set; + surfaceCount += 1; return surface; } void RenderBackend_FreeSurface(RenderBackend_Surface *surface) { + delete surface->cheapRectangles; free(surface->pixels); free(surface); } @@ -108,29 +118,106 @@ void RenderBackend_RestoreSurface(RenderBackend_Surface *surface) (void)surface; } +static void dumpSurface(RenderBackend_Surface* surface) { + char filename[64] = {0}; + sprintf(filename, "s%02d.ppm", surface->id); + FILE* fp = fopen(filename, "w"); + fprintf(fp, "P3\n%ld %ld\n255\n", surface->width, surface->height); + + for(int y = 0; y < surface->height; y++) { + int lineidx = y * surface->pitch; + for(int x = 0; x < surface->width; x++) { + int pixidx = lineidx + (x * 4); +#ifdef LITTLE_ENDIAN + fprintf(fp, "%d %d %d\n", + surface->pixels[pixidx + 2], + surface->pixels[pixidx + 1], + surface->pixels[pixidx + 0]); +#else + // Don't bother dumping out surfaces on the Sun. + // Hacky as hell, I know, but this isn't a forever project. +#endif + } + } + + fclose(fp); +} + +// Determines whether a particular tile uses transparency (color keying). +static bool rectUsesColorKey(RenderBackend_Surface* surface, RenderBackend_Rect* rect) { + for(int line = rect->top; line < rect->bottom; line++) { + for(int column = rect->left; column < rect->right; column++) { + unsigned index = (line * surface->pitch) + (column * 4); + if(surface->pixels[index + 0] == 0 && + surface->pixels[index + 1] == 0 && + surface->pixels[index + 2] == 0 && + surface->pixels[index + 3] == 0) { + return true; + } + } + } + + return false; +} + +// Goes through the surface and determines which 16x16 tiles can be drawn +// without using color keying. Such tiles are "cheap". +static void computeCheapRects(RenderBackend_Surface* surface) { + int rcolumns = surface->width / 16; + int rlines = surface->height / 16; + + for(unsigned int ry = 0; ry < rlines; ry++) { + for(unsigned int rx = 0; rx < rcolumns; rx++) { + unsigned int px = rx * 16; + unsigned int py = ry * 16; + RenderBackend_Rect rect = { .left = px, .top = py, .right = px + 16, .bottom = py + 16 }; + if(rectUsesColorKey(surface, &rect) == false) { + unsigned int rectOrigin = (px << 16) | py; + surface->cheapRectangles->insert(rectOrigin); + } + } + } +} + void RenderBackend_UploadSurface(RenderBackend_Surface *surface, const unsigned char *pixels, size_t width, size_t height) { + surface->cheapRectangles->clear(); + for (size_t y = 0; y < height; ++y) { int srcl = y * width * 3; int dstl = y * surface->pitch; for (size_t x = 0; x < width; ++x) { int srcr = x * 3; int dstr = x * 4; + #ifdef LITTLE_ENDIAN surface->pixels[dstl + dstr + 0] = pixels[srcl + srcr + 2]; surface->pixels[dstl + dstr + 1] = pixels[srcl + srcr + 1]; surface->pixels[dstl + dstr + 2] = pixels[srcl + srcr + 0]; + surface->pixels[dstl + dstr + 3] = 0; #else + surface->pixels[dstl + dstr + 0] = 0; surface->pixels[dstl + dstr + 1] = pixels[srcl + srcr + 2]; surface->pixels[dstl + dstr + 2] = pixels[srcl + srcr + 1]; surface->pixels[dstl + dstr + 3] = pixels[srcl + srcr + 0]; #endif } } + + computeCheapRects(surface); + +#ifdef LITTLE_ENDIAN // HACK!! Only dump surfaces on a little endian machine, i.e. my dev machine. + dumpSurface(surface); +#endif } ATTRIBUTE_HOT void RenderBackend_Blit(RenderBackend_Surface *source_surface, const RenderBackend_Rect *rect, RenderBackend_Surface *destination_surface, long x, long y, bool colour_key) { + // If the empty tile is used, don't bother blitting. + if(source_surface->id == 20 && rect->left == 0 && rect->top == 0) { + return; + } + RenderBackend_Rect rect_clamped; rect_clamped.left = rect->left; @@ -169,6 +256,13 @@ ATTRIBUTE_HOT void RenderBackend_Blit(RenderBackend_Surface *source_surface, con if (rect_clamped.right - rect_clamped.left <= 0) return; + // If we're using a 16x16 tile... + if(rect->bottom - rect->top == 16 || rect->right - rect->left == 16) { + // ...enable color key only if this rectangle isn't a known cheap one. + // This way we don't need colorkeying for tiles that aren't transparent + colour_key = (source_surface->cheapRectangles->count((rect->left << 16) | rect->top) == 0); + } + // Do the actual blitting if (colour_key) {