Tilemap stuff

This commit is contained in:
2026-06-19 13:17:20 -05:00
parent 57b2cdb9d1
commit 4e491d8332
52 changed files with 2372 additions and 362 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
)
add_subdirectory(error)
add_subdirectory(render)
add_subdirectory(display)
+6
View File
@@ -0,0 +1,6 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(render)
@@ -6,4 +6,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
rendergl.c
rendertexturegl.c
rendertilemapcgl.c
)
+313
View File
@@ -0,0 +1,313 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendergl.h"
#include "display/render/rendertexturegl.h"
#include "display/render/rendertilemapcgl.h"
#include "error/errorgl.h"
#include "display/render/rop.h"
#include <cglm/cglm.h>
/* ---- 2D shader: xy rgba uv, depth from uniform -------------------------- */
static const char *VERT2D =
"#version 330 core\n"
"layout(location=0) in vec2 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"layout(location=2) in vec2 aUV;\n"
"uniform vec2 uRes;\n"
"uniform vec2 uOffset;\n"
"uniform float uDepth;\n"
"out vec4 vColor; out vec2 vUV;\n"
"void main() {\n"
" vec2 clip = ((aPos + uOffset) / uRes) * 2.0 - 1.0;\n"
" clip.y = -clip.y;\n"
" gl_Position = vec4(clip, uDepth, 1.0);\n"
" vColor = aColor; vUV = aUV;\n"
"}\n";
/* ---- 3D shader: xyz rgba uv, full MVP ----------------------------------- */
static const char *VERT3D =
"#version 330 core\n"
"layout(location=0) in vec3 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"layout(location=2) in vec2 aUV;\n"
"uniform mat4 uMVP;\n"
"out vec4 vColor; out vec2 vUV;\n"
"void main() {\n"
" gl_Position = uMVP * vec4(aPos, 1.0);\n"
" vColor = aColor; vUV = aUV;\n"
"}\n";
/* Palette shader: uTexIndices is a GL_R8 W×H texture (one byte per pixel);
* uTexPalette is a 256×1 RGBA texture (the colour table).
* The index value (0-255) stored as a normalised float in R8 is converted back
* to an exact texel centre using (raw*(255/256) + 0.5/256) before sampling,
* giving pixel-exact palette lookups for all 256 possible indices. */
static const char *FRAG =
"#version 330 core\n"
"in vec4 vColor; in vec2 vUV;\n"
"uniform sampler2D uTexIndices;\n"
"uniform sampler2D uTexPalette;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" float raw = texture(uTexIndices, vUV).r;\n"
" float u = raw * (255.0/256.0) + (0.5/256.0);\n"
" fragColor = texture(uTexPalette, vec2(u, 0.5)) * vColor;\n"
"}\n";
/* ---- State --------------------------------------------------------------- */
typedef struct {
GLuint prog, vao, vbo;
GLint uRes, uOffset, uDepth, uTexIndices, uTexPalette;
} rgl2d_t;
typedef struct {
GLuint prog, vao, vbo;
GLint uMVP, uTexIndices, uTexPalette;
mat4 proj, view;
} rgl3d_t;
static rgl2d_t gl2d;
static rgl3d_t gl3d;
/* ---- Helpers ------------------------------------------------------------- */
static GLuint buildProgram(const char *vsrc, const char *fsrc) {
GLuint v = glCreateShader(GL_VERTEX_SHADER);
GLuint f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(v, 1, &vsrc, NULL); glCompileShader(v);
glShaderSource(f, 1, &fsrc, NULL); glCompileShader(f);
GLuint p = glCreateProgram();
glAttachShader(p, v); glAttachShader(p, f);
glLinkProgram(p);
glDeleteShader(v); glDeleteShader(f);
return p;
}
/* ---- Init ---------------------------------------------------------------- */
errorret_t renderGLInit(void) {
/* 2D — 8 floats/vert: x y r g b a u v */
gl2d.prog = buildProgram(VERT2D, FRAG);
gl2d.uRes = glGetUniformLocation(gl2d.prog, "uRes");
gl2d.uOffset = glGetUniformLocation(gl2d.prog, "uOffset");
gl2d.uDepth = glGetUniformLocation(gl2d.prog, "uDepth");
gl2d.uTexIndices = glGetUniformLocation(gl2d.prog, "uTexIndices");
gl2d.uTexPalette = glGetUniformLocation(gl2d.prog, "uTexPalette");
glGenVertexArrays(1, &gl2d.vao); glGenBuffers(1, &gl2d.vbo);
glBindVertexArray(gl2d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl2d.vbo);
glBufferData(GL_ARRAY_BUFFER, 6*8*sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
errorChain(errorGLCheck());
/* 3D — 9 floats/vert: x y z r g b a u v */
gl3d.prog = buildProgram(VERT3D, FRAG);
gl3d.uMVP = glGetUniformLocation(gl3d.prog, "uMVP");
gl3d.uTexIndices = glGetUniformLocation(gl3d.prog, "uTexIndices");
gl3d.uTexPalette = glGetUniformLocation(gl3d.prog, "uTexPalette");
glm_mat4_identity(gl3d.proj);
glm_mat4_identity(gl3d.view);
glGenVertexArrays(1, &gl3d.vao); glGenBuffers(1, &gl3d.vbo);
glBindVertexArray(gl3d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl3d.vbo);
glBufferData(GL_ARRAY_BUFFER, 6*9*sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)(7*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
errorChain(errorGLCheck());
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
renderGLTextureInit();
errorChain(errorGLCheck());
errorOk();
}
/* ---- 2D draw ------------------------------------------------------------- */
static void draw2DSprite(const ropsprite_t *s, float rW, float rH) {
float r = s->tint.r/255.0f, g = s->tint.g/255.0f;
float b = s->tint.b/255.0f, a = s->tint.a/255.0f;
float x0 = (float)s->x, y0 = (float)s->y;
float x1 = x0+(float)s->w, y1 = y0+(float)s->h;
float u0 = s->uvX/255.0f, v0 = s->uvY/255.0f;
float u1 = (s->uvX+s->uvW)/255.0f, v1 = (s->uvY+s->uvH)/255.0f;
/* int16 depth: 0=front, 32767=back mapped to [0,1] NDC Z */
float depth = (float)s->header.depth / 32767.0f;
GLfloat verts[6][8] = {
{x0,y1, r,g,b,a, u0,v1},
{x0,y0, r,g,b,a, u0,v0},
{x1,y0, r,g,b,a, u1,v0},
{x0,y1, r,g,b,a, u0,v1},
{x1,y0, r,g,b,a, u1,v0},
{x1,y1, r,g,b,a, u1,v1},
};
glUseProgram(gl2d.prog);
glUniform2f(gl2d.uRes, rW, rH);
glUniform2f(gl2d.uOffset, 0.0f, 0.0f);
glUniform1f(gl2d.uDepth, depth);
glUniform1i(gl2d.uTexIndices, 0);
glUniform1i(gl2d.uTexPalette, 1);
renderGLTextureBind(s->texture);
glBindVertexArray(gl2d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl2d.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
static void draw2DTilemapChunk(const roptilemapc_t *t, float rW, float rH) {
const glchunkentry_t *e = renderGLTilemapChunkGet(t->chunk);
if(!e) return;
float depth = (float)t->header.depth / 32767.0f;
glUseProgram(gl2d.prog);
glUniform2f(gl2d.uRes, rW, rH);
glUniform2f(gl2d.uOffset, (float)t->x, (float)t->y);
glUniform1f(gl2d.uDepth, depth);
glUniform1i(gl2d.uTexIndices, 0);
glUniform1i(gl2d.uTexPalette, 1);
renderGLTextureBind(e->tileset);
glBindVertexArray(e->vao);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)e->vertCount);
}
/* ---- 3D draw ------------------------------------------------------------- */
static void draw3DQuad(const ropquad3d_t *q) {
float r = q->tint.r/255.0f, g = q->tint.g/255.0f;
float b = q->tint.b/255.0f, a = q->tint.a/255.0f;
float u0 = q->uvX/255.0f, v0 = q->uvY/255.0f;
float u1 = (q->uvX+q->uvW)/255.0f, v1 = (q->uvY+q->uvH)/255.0f;
float cx = (float)q->cx, cy = (float)q->cy, cz = (float)q->cz;
float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz;
float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz;
float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz;
float trx = cx+rx+ux, tr_y= cy+ry+uy, trz = cz+rz+uz;
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
GLfloat verts[6][9] = {
{tlx,tly,tlz, r,g,b,a, u0,v0},
{blx,bly,blz, r,g,b,a, u0,v1},
{brx,bry,brz, r,g,b,a, u1,v1},
{tlx,tly,tlz, r,g,b,a, u0,v0},
{brx,bry,brz, r,g,b,a, u1,v1},
{trx,tr_y,trz, r,g,b,a, u1,v0},
};
mat4 mvp;
glm_mat4_mul(gl3d.proj, gl3d.view, mvp);
glUseProgram(gl3d.prog);
glUniformMatrix4fv(gl3d.uMVP, 1, GL_FALSE, (GLfloat *)mvp);
glUniform1i(gl3d.uTexIndices, 0);
glUniform1i(gl3d.uTexPalette, 1);
renderGLTextureBind(q->texture);
glBindVertexArray(gl3d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl3d.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
/* ---- Flush --------------------------------------------------------------- */
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH) {
glViewport(0, 0, winW, winH);
errorChain(errorGLCheck());
uint32_t offset = 0;
while(offset < buf->byteCount) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
ropop_t op = (ropop_t)hdr->op;
switch(op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
glClearColor(
c->color.r/255.0f, c->color.g/255.0f,
c->color.b/255.0f, c->color.a/255.0f
);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_SPRITE: {
draw2DSprite((const ropsprite_t *)hdr, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
case ROP_SET_PROJECTION: {
const ropprojection_t *p = (const ropprojection_t *)hdr;
if(p->fovY > FIXED_ZERO)
glm_perspective(
fixedToFloat(p->fovY), fixedToFloat(p->aspect),
fixedToFloat(p->nearZ), fixedToFloat(p->farZ),
gl3d.proj
);
else
glm_mat4_identity(gl3d.proj);
break;
}
case ROP_SET_VIEW: {
const ropview_t *v = (const ropview_t *)hdr;
vec3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
vec3 tgt = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
vec3 up = {0.0f, 1.0f, 0.0f};
glm_lookat(eye, tgt, up, gl3d.view);
break;
}
case ROP_DRAW_QUAD_3D: {
draw3DQuad((const ropquad3d_t *)hdr);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_TILEMAP_CHUNK: {
draw2DTilemapChunk((const roptilemapc_t *)hdr, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
default:
break;
}
offset += ropOpSize(op);
}
glUseProgram(0);
glBindVertexArray(0);
errorOk();
}
/* ---- Dispose ------------------------------------------------------------- */
void renderGLDispose(void) {
renderGLTilemapChunkTableDispose();
renderGLTextureTableDispose();
if(gl2d.vbo) { glDeleteBuffers(1, &gl2d.vbo); gl2d.vbo = 0; }
if(gl2d.vao) { glDeleteVertexArrays(1, &gl2d.vao); gl2d.vao = 0; }
if(gl2d.prog) { glDeleteProgram(gl2d.prog); gl2d.prog = 0; }
if(gl3d.vbo) { glDeleteBuffers(1, &gl3d.vbo); gl3d.vbo = 0; }
if(gl3d.vao) { glDeleteVertexArrays(1, &gl3d.vao); gl3d.vao = 0; }
if(gl3d.prog) { glDeleteProgram(gl3d.prog); gl3d.prog = 0; }
}
@@ -8,7 +8,7 @@
#pragma once
#include "duskgl.h"
#include "error/error.h"
#include "render/ropbuffer.h"
#include "display/render/ropbuffer.h"
errorret_t renderGLInit(void);
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH);
+150
View File
@@ -0,0 +1,150 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendertexturegl.h"
#include "display/color.h"
#include "assert/assert.h"
#include <stdlib.h>
/* Each slot stores a CPU-side copy of the palette and index data.
* Dirty flags are set by the getters; renderGLTextureBind re-uploads
* to the GPU textures lazily before binding. */
typedef struct {
GLuint idxTex;
GLuint palTex;
uint8_t *cpuIndices; /* heap-allocated w*h */
color_t palette[256];
uint16_t w, h;
uint8_t idxDirty;
uint8_t palDirty;
} gltexentry_t;
static gltexentry_t glTexTable[RTEXTURE_MAX];
static uint16_t glTexNext = 1;
static void initIndexTex(GLuint id, uint16_t w, uint16_t h, const uint8_t *data) {
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
static void initPaletteTex(GLuint id, const color_t *palette) {
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, palette);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void renderGLTextureInit(void) {
static const uint8_t whiteIdx = 0;
static color_t whitePal[256];
whitePal[0] = COLOR_WHITE;
gltexentry_t *e = &glTexTable[0];
e->cpuIndices = (uint8_t *)malloc(1);
assertNotNull(e->cpuIndices, "GL: failed to allocate fallback index buffer");
e->cpuIndices[0] = 0;
e->palette[0] = COLOR_WHITE;
e->w = e->h = 1;
e->idxDirty = e->palDirty = 0;
glGenTextures(1, &e->idxTex);
glGenTextures(1, &e->palTex);
initIndexTex(e->idxTex, 1, 1, &whiteIdx);
initPaletteTex(e->palTex, whitePal);
}
void renderGLTextureTableDispose(void) {
for(uint16_t i = 0; i < glTexNext; i++) {
gltexentry_t *e = &glTexTable[i];
if(e->idxTex) { glDeleteTextures(1, &e->idxTex); e->idxTex = 0; }
if(e->palTex) { glDeleteTextures(1, &e->palTex); e->palTex = 0; }
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
}
glTexNext = 1;
}
rtexture_t renderGLTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
) {
assertTrue(glTexNext < RTEXTURE_MAX, "Texture table full");
rtexture_t handle = (rtexture_t)glTexNext++;
gltexentry_t *e = &glTexTable[handle];
uint32_t pixels = (uint32_t)w * h;
e->cpuIndices = (uint8_t *)malloc(pixels);
assertNotNull(e->cpuIndices, "GL: failed to allocate index buffer");
memcpy(e->cpuIndices, indices, pixels);
memcpy(e->palette, palette, 256 * sizeof(color_t));
e->w = w; e->h = h;
e->idxDirty = e->palDirty = 0;
glGenTextures(1, &e->idxTex);
glGenTextures(1, &e->palTex);
initIndexTex(e->idxTex, w, h, indices);
initPaletteTex(e->palTex, palette);
return handle;
}
void renderGLTextureDispose(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return;
gltexentry_t *e = &glTexTable[tex];
if(e->idxTex) { glDeleteTextures(1, &e->idxTex); e->idxTex = 0; }
if(e->palTex) { glDeleteTextures(1, &e->palTex); e->palTex = 0; }
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
}
color_t *renderGLTextureGetPalette(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return NULL;
glTexTable[tex].palDirty = 1;
return glTexTable[tex].palette;
}
uint8_t *renderGLTextureGetIndices(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return NULL;
glTexTable[tex].idxDirty = 1;
return glTexTable[tex].cpuIndices;
}
void renderGLTextureGetSize(rtexture_t tex, uint16_t *w, uint16_t *h) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX || !glTexTable[tex].idxTex) {
*w = 1; *h = 1;
return;
}
*w = glTexTable[tex].w;
*h = glTexTable[tex].h;
}
/* Flush any dirty CPU data to GPU, then bind both texture units. */
void renderGLTextureBind(rtexture_t tex) {
gltexentry_t *e = (tex < RTEXTURE_MAX && glTexTable[tex].idxTex)
? &glTexTable[tex]
: &glTexTable[0];
if(e->palDirty) {
glBindTexture(GL_TEXTURE_2D, e->palTex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_RGBA, GL_UNSIGNED_BYTE, e->palette);
e->palDirty = 0;
}
if(e->idxDirty) {
glBindTexture(GL_TEXTURE_2D, e->idxTex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, e->w, e->h, GL_RED, GL_UNSIGNED_BYTE, e->cpuIndices);
e->idxDirty = 0;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, e->idxTex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, e->palTex);
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskgl.h"
#include "display/render/rtexture.h"
#include "display/color.h"
#define RTEXTURE_MAX 256
void renderGLTextureInit(void);
void renderGLTextureTableDispose(void);
rtexture_t renderGLTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
);
void renderGLTextureDispose(rtexture_t tex);
void renderGLTextureBind(rtexture_t tex);
color_t *renderGLTextureGetPalette(rtexture_t tex);
uint8_t *renderGLTextureGetIndices(rtexture_t tex);
void renderGLTextureGetSize(rtexture_t tex, uint16_t *w, uint16_t *h);
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendertilemapcgl.h"
#include "display/render/rendertexturegl.h"
#include "assert/assert.h"
#include <stdlib.h>
static glchunkentry_t glChunkTable[RTILEMAPCHUNK_MAX];
static uint16_t glChunkNext = 1; /* 0 reserved for RTILEMAPCHUNK_INVALID */
void renderGLTilemapChunkTableDispose(void) {
for(uint16_t i = 1; i < glChunkNext; i++) {
glchunkentry_t *e = &glChunkTable[i];
if(e->vbo) { glDeleteBuffers(1, &e->vbo); e->vbo = 0; }
if(e->vao) { glDeleteVertexArrays(1, &e->vao); e->vao = 0; }
}
glChunkNext = 1;
}
rtilemapchunk_t renderGLTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
) {
assertTrue(glChunkNext < RTILEMAPCHUNK_MAX, "Tilemap chunk table full");
assertTrue(tileW > 0 && tileH > 0, "Tile dimensions must be > 0");
uint16_t texW, texH;
renderGLTextureGetSize(tileset, &texW, &texH);
assertTrue(texW >= tileW && texH >= tileH, "Tileset smaller than one tile");
uint16_t tilesPerRow = texW / tileW;
float fTexW = (float)texW;
float fTexH = (float)texH;
float fTileW = (float)tileW;
float fTileH = (float)tileH;
rtilemapchunk_t handle = (rtilemapchunk_t)glChunkNext++;
glchunkentry_t *e = &glChunkTable[handle];
e->tileset = tileset;
uint32_t tileCount = (uint32_t)chunkW * chunkH;
e->vertCount = tileCount * 6;
/* Temporary CPU buffer: 6 verts × 8 floats (x y r g b a u v) per tile */
GLfloat *verts = (GLfloat *)malloc(e->vertCount * 8 * sizeof(GLfloat));
assertNotNull(verts, "GL: failed to allocate tilemap chunk vertex buffer");
uint32_t v = 0;
for(uint32_t ci = 0; ci < tileCount; ci++) {
uint8_t idx = tileIndices[ci];
uint16_t tileCol = idx % tilesPerRow;
uint16_t tileRow = idx / tilesPerRow;
float px0 = (float)((ci % chunkW) * tileW);
float py0 = (float)((ci / chunkW) * tileH);
float px1 = px0 + fTileW;
float py1 = py0 + fTileH;
float u0 = (float)(tileCol * tileW) / fTexW;
float v0 = (float)(tileRow * tileH) / fTexH;
float u1 = u0 + fTileW / fTexW;
float v1 = v0 + fTileH / fTexH;
#define V(px,py,uu,vv) \
verts[v*8+0]=(px); verts[v*8+1]=(py); \
verts[v*8+2]=1.0f; verts[v*8+3]=1.0f; \
verts[v*8+4]=1.0f; verts[v*8+5]=1.0f; \
verts[v*8+6]=(uu); verts[v*8+7]=(vv); v++
V(px0, py1, u0, v1);
V(px0, py0, u0, v0);
V(px1, py0, u1, v0);
V(px0, py1, u0, v1);
V(px1, py0, u1, v0);
V(px1, py1, u1, v1);
#undef V
}
glGenVertexArrays(1, &e->vao);
glGenBuffers(1, &e->vbo);
glBindVertexArray(e->vao);
glBindBuffer(GL_ARRAY_BUFFER, e->vbo);
glBufferData(
GL_ARRAY_BUFFER,
(GLsizeiptr)(e->vertCount * 8 * sizeof(GLfloat)),
verts,
GL_STATIC_DRAW
);
/* layout: aPos(2) aColor(4) aUV(2) — matches the 2D shader */
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
free(verts);
return handle;
}
void renderGLTilemapChunkDispose(rtilemapchunk_t chunk) {
if(chunk == RTILEMAPCHUNK_INVALID || chunk >= RTILEMAPCHUNK_MAX) return;
glchunkentry_t *e = &glChunkTable[chunk];
if(e->vbo) { glDeleteBuffers(1, &e->vbo); e->vbo = 0; }
if(e->vao) { glDeleteVertexArrays(1, &e->vao); e->vao = 0; }
}
const glchunkentry_t *renderGLTilemapChunkGet(rtilemapchunk_t chunk) {
if(chunk == RTILEMAPCHUNK_INVALID || chunk >= RTILEMAPCHUNK_MAX) return NULL;
if(!glChunkTable[chunk].vao) return NULL;
return &glChunkTable[chunk];
}
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskgl.h"
#include "display/render/rtilemapchunk.h"
#include "display/render/rtexture.h"
#define RTILEMAPCHUNK_MAX 256
/* Opaque GL state for one pre-built chunk. */
typedef struct {
GLuint vao, vbo;
uint32_t vertCount;
rtexture_t tileset;
} glchunkentry_t;
void renderGLTilemapChunkTableDispose(void);
rtilemapchunk_t renderGLTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
);
void renderGLTilemapChunkDispose(rtilemapchunk_t chunk);
const glchunkentry_t *renderGLTilemapChunkGet(rtilemapchunk_t chunk);
-153
View File
@@ -1,153 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render/rendergl.h"
#include "error/errorgl.h"
#include "render/rop.h"
static const char *VERT_SRC =
"#version 330 core\n"
"layout(location=0) in vec2 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"uniform vec2 uRes;\n"
"out vec4 vColor;\n"
"void main() {\n"
" vec2 clip = (aPos / uRes) * 2.0 - 1.0;\n"
" clip.y = -clip.y;\n"
" gl_Position = vec4(clip, 0.0, 1.0);\n"
" vColor = aColor;\n"
"}\n";
static const char *FRAG_SRC =
"#version 330 core\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vColor;\n"
"}\n";
typedef struct {
GLuint prog;
GLuint vao;
GLuint vbo;
GLint uRes;
} rendergl_t;
static rendergl_t renderGL;
static GLuint compileShader(GLenum type, const char *src) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, NULL);
glCompileShader(s);
return s;
}
errorret_t renderGLInit(void) {
GLuint vert = compileShader(GL_VERTEX_SHADER, VERT_SRC);
GLuint frag = compileShader(GL_FRAGMENT_SHADER, FRAG_SRC);
renderGL.prog = glCreateProgram();
glAttachShader(renderGL.prog, vert);
glAttachShader(renderGL.prog, frag);
glLinkProgram(renderGL.prog);
glDeleteShader(vert);
glDeleteShader(frag);
errorChain(errorGLCheck());
renderGL.uRes = glGetUniformLocation(renderGL.prog, "uRes");
glGenVertexArrays(1, &renderGL.vao);
glGenBuffers(1, &renderGL.vbo);
glBindVertexArray(renderGL.vao);
glBindBuffer(GL_ARRAY_BUFFER, renderGL.vbo);
/* 6 verts * (2 pos + 4 color) floats — enough for one sprite */
glBufferData(GL_ARRAY_BUFFER, 6 * 6 * sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
errorChain(errorGLCheck());
errorOk();
}
static void drawSprite(const ropsprite_t *s, float rW, float rH) {
float r = s->tint.r / 255.0f;
float g = s->tint.g / 255.0f;
float b = s->tint.b / 255.0f;
float a = s->tint.a / 255.0f;
float x0 = (float)s->x;
float y0 = (float)s->y;
float x1 = x0 + (float)s->w;
float y1 = y0 + (float)s->h;
GLfloat verts[6][6] = {
{ x0, y1, r,g,b,a },
{ x0, y0, r,g,b,a },
{ x1, y0, r,g,b,a },
{ x0, y1, r,g,b,a },
{ x1, y0, r,g,b,a },
{ x1, y1, r,g,b,a },
};
glBindVertexArray(renderGL.vao);
glBindBuffer(GL_ARRAY_BUFFER, renderGL.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
(void)rW; (void)rH;
}
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH) {
glViewport(0, 0, winW, winH);
errorChain(errorGLCheck());
glUseProgram(renderGL.prog);
glUniform2f(renderGL.uRes, (GLfloat)winW, (GLfloat)winH);
errorChain(errorGLCheck());
for(uint32_t i = 0; i < buf->count; i++) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + i * ROP_SIZE);
switch(hdr->op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
glClearColor(
c->color.r / 255.0f,
c->color.g / 255.0f,
c->color.b / 255.0f,
c->color.a / 255.0f
);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_SPRITE: {
const ropsprite_t *s = (const ropsprite_t *)hdr;
drawSprite(s, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
default:
break;
}
}
glUseProgram(0);
glBindVertexArray(0);
errorOk();
}
void renderGLDispose(void) {
if(renderGL.vbo) { glDeleteBuffers(1, &renderGL.vbo); renderGL.vbo = 0; }
if(renderGL.vao) { glDeleteVertexArrays(1, &renderGL.vao); renderGL.vao = 0; }
if(renderGL.prog) { glDeleteProgram(renderGL.prog); renderGL.prog = 0; }
}