Tilemap stuff
This commit is contained in:
@@ -9,4 +9,4 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(display)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user