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.
This commit is contained in:
John Lorentzson 2025-04-17 11:34:31 +02:00
parent 2928b8a362
commit 62898c85f8

View file

@ -3,6 +3,8 @@
#include "../Rendering.h"
#include <set>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@ -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<unsigned int>* 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<unsigned int>;
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)
{