work-in-progress for 3d triangle rendering

This commit is contained in:
veclav talica 2024-07-10 19:15:28 +03:00
parent e2ed4b2c2d
commit b280243d30
9 changed files with 300 additions and 23 deletions

View File

@ -24,7 +24,10 @@ typedef struct context {
struct rect_primitive *render_queue_rectangles;
struct circle_primitive *render_queue_circles;
struct audio_channel_pair *audio_channels;
struct mesh_batch *uncolored_mesh_batches; /* texture_cache reflected */
struct mesh_batch_item *uncolored_mesh_batches_loners; /* path reflected */
struct audio_channel_item *audio_channels;
SDL_AudioDeviceID audio_device;
int audio_stream_frequency;
SDL_AudioFormat audio_stream_format;

View File

@ -11,7 +11,29 @@ static void ingame_tick(struct state *state) {
world_drawdef(scn->world);
player_calc(scn->player);
get_audio_args("soundtrack")->volume -= 0.01f;
// unfurl_triangle("/assets/title.png",
// (t_fvec3){ 1250, 700, 0 },
// (t_fvec3){ 0, 800, 0 },
// (t_fvec3){ 0, 0, 0 },
// (t_shvec2){ 0, 360 },
// (t_shvec2){ 0, 0 },
// (t_shvec2){ 360, 0 });
unfurl_triangle("/assets/red.png",
(t_fvec3){ 0, 0, 0 },
(t_fvec3){ RENDER_BASE_WIDTH, 0, 0 },
(t_fvec3){ RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0 },
(t_shvec2){ 0, 0 },
(t_shvec2){ 80, 0 },
(t_shvec2){ 80, 80 });
unfurl_triangle("/assets/red.png",
(t_fvec3){ RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0 },
(t_fvec3){ 0, RENDER_BASE_HEIGHT, 0 },
(t_fvec3){ 0, 0, 0 },
(t_shvec2){ 80, 80 },
(t_shvec2){ 0, 80 },
(t_shvec2){ 0, 0 });
}

View File

@ -44,6 +44,24 @@ static void poll_events(void) {
}
static void APIENTRY opengl_log(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam)
{
(void)source;
(void)type;
(void)id;
(void)severity;
(void)userParam;
log_info("OpenGL: %.*s\n", length, message);
}
void main_loop(void) {
/*
if (!ctx.is_running) {
@ -149,6 +167,7 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
/* init got far enough to create a window */
ctx.window = SDL_CreateWindow("emerald",
@ -178,6 +197,8 @@ static bool initialize(void) {
goto fail;
}
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
/* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window);
@ -235,6 +256,12 @@ static bool initialize(void) {
ctx.debug = false;
#endif
/* hook up opengl debugging callback */
if (ctx.debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL);
}
/* random seeding */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */
/* it should vary between game instances. i checked! random enough for me. */

View File

@ -5,6 +5,7 @@
#include "../util.h"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stdbool.h>
@ -31,4 +32,26 @@ struct circle_primitive {
t_fvec2 position;
};
/* batch of primitives with overlapping properties */
struct mesh_batch {
GLuint buffer; /* server side storage */
uint8_t *data; /* client side storage */
// size_t buffer_len; /* element count */
};
struct mesh_batch_item {
char *key;
struct mesh_batch value;
};
/* is structure that is in opengl vertex array */
struct uncolored_space_triangle {
t_fvec3 v0;
t_shvec2 uv0;
t_fvec3 v1;
t_shvec2 uv1;
t_fvec3 v2;
t_shvec2 uv2;
};
#endif

View File

@ -6,6 +6,7 @@
#include <stb_ds.h>
#include <glad/glad.h>
#include <stddef.h>
#include <stdlib.h>
#include <tgmath.h>
@ -17,6 +18,12 @@ void render_queue_clear(void) {
arrsetlen(ctx.render_queue_sprites, 0);
arrsetlen(ctx.render_queue_rectangles, 0);
arrsetlen(ctx.render_queue_circles, 0);
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i)
arrsetlen(ctx.uncolored_mesh_batches[i].data, 0);
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i)
arrsetlen(ctx.uncolored_mesh_batches_loners[i].value.data, 0);
}
@ -91,6 +98,78 @@ void push_circle(t_fvec2 position, float radius, t_color color) {
}
/* TODO: automatic handling of repeating textures */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2)
{
/* corrected atlas texture coordinates */
t_shvec2 uv0c, uv1c, uv2c;
struct mesh_batch *batch_p;
textures_load(&ctx.texture_cache, path);
const int atlas_index = textures_get_atlas_index(&ctx.texture_cache, path);
if (atlas_index == -1) {
/* loners span whole texture i assume */
uv0c = uv0;
uv1c = uv1;
uv2c = uv2;
batch_p = &shgetp(ctx.uncolored_mesh_batches_loners, path)->value;
} else {
const size_t old_len = arrlenu(ctx.uncolored_mesh_batches);
if ((size_t)atlas_index + 1 >= old_len) {
/* grow to accommodate texture cache atlases */
arrsetlen(ctx.uncolored_mesh_batches, atlas_index + 1);
/* zero initialize it all, it's a valid state */
SDL_memset(&ctx.uncolored_mesh_batches[atlas_index],
0,
sizeof (struct mesh_batch) * ((atlas_index + 1) - old_len));
}
const SDL_Rect srcrect = ctx.texture_cache.hash[atlas_index].value.srcrect; /* TODO: does it work? */
/* fixed point galore */
const int16_t srcratx = (int16_t)srcrect.x * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcratw = (int16_t)srcrect.w * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcrath = (int16_t)srcrect.h * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcraty = (int16_t)srcrect.y * (INT16_MAX / TEXTURE_ATLAS_SIZE); /* flip? */
uv0c.x = (int16_t)(srcratx + ((uint16_t)((uv0.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv0c.y = (int16_t)(srcraty + ((uint16_t)((uv0.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
uv1c.x = (int16_t)(srcratx + ((uint16_t)((uv1.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv1c.y = (int16_t)(srcraty + ((uint16_t)((uv1.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
uv2c.x = (int16_t)(srcratx + ((uint16_t)((uv2.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv2c.y = (int16_t)(srcraty + ((uint16_t)((uv2.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
batch_p = &ctx.uncolored_mesh_batches[atlas_index];
}
struct uncolored_space_triangle *data = (struct uncolored_space_triangle *)batch_p->data;
struct uncolored_space_triangle pack = {
.v0 = v0,
// .uv0 = uv0c,
.uv0 = { 0, 0 },
.v1 = v1,
// .uv1 = uv1c,
.uv1 = { INT16_MAX, 0 },
.v2 = v2,
// .uv2 = uv2c,
.uv2 = { INT16_MAX, INT16_MAX },
};
arrpush(data, pack);
batch_p->data = (uint8_t *)data;
}
/* compare functions for the sort in render_sprites */
static int cmp_atlases(const void *a, const void *b) {
int index_a = ((const struct sprite_primitive *)a)->atlas_index;
@ -312,9 +391,6 @@ static void render_circle(struct circle_primitive *circle) {
static void render_sprites(void) {
if (ctx.texture_cache.is_dirty)
textures_update_current_atlas(&ctx.texture_cache);
sort_sprites(ctx.render_queue_sprites);
for (size_t i = 0; i < arrlenu(ctx.render_queue_sprites); ++i) {
@ -337,36 +413,120 @@ static void render_circles(void) {
}
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch) {
size_t data_len = arrlenu(batch->data);
/* create vertex array object */
if (batch->buffer == 0)
glGenBuffers(1, &batch->buffer);
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
/* TODO: try using mapped buffers while building batches instead? */
/* this way we could skip client side copy that is kept until commitment */
/* alternatively we could commit glBufferSubData based on a threshold */
/* upload batched data */
glBufferData(GL_ARRAY_BUFFER,
data_len * sizeof (struct uncolored_space_triangle),
batch->data,
GL_STREAM_DRAW);
/* vertex specification*/
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,
GL_FLOAT,
offsetof(struct uncolored_space_triangle, v1),
(void *)offsetof(struct uncolored_space_triangle, v0));
/* note: propagates further to where texture binding is done */
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_SHORT,
offsetof(struct uncolored_space_triangle, v1),
(void *)offsetof(struct uncolored_space_triangle, uv0));
// for (size_t i = 0; i < data_len; ++i) {
// struct uncolored_space_triangle t = ((struct uncolored_space_triangle *)batch->data)[i];
// log_info("{%i, %i, %i, %i, %i, %i}\n", t.uv0.x, t.uv0.y, t.uv1.x, t.uv1.y, t.uv2.x, t.uv2.y);
// }
/* commit for drawing */
glDrawArrays(GL_TRIANGLES, 0, 3 * (int)data_len);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
/* invalidate the buffer immediately */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
static void render_space(void) {
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
glBegin(GL_TRIANGLES);
glColor4f(0.0, 1.0, 1.0, 1.0);
glVertex2f(300.0,210.0);
glVertex2f(340.0,215.0);
glVertex2f(320.0,250.0);
glEnd();
glUseProgramObjectARB(0);
glActiveTexture(GL_TEXTURE0);
/* solid white, no modulation */
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i) {
if (arrlenu(&ctx.uncolored_mesh_batches[i].data) > 0) {
SDL_Texture *const atlas = textures_get_atlas(&ctx.texture_cache, (int)i);
SDL_GL_BindTexture(atlas, NULL, NULL);
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i]);
SDL_GL_UnbindTexture(atlas);
}
}
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i) {
if (arrlenu(&ctx.uncolored_mesh_batches_loners[i].value.data) > 0) {
SDL_Texture *const atlas = textures_get_loner(&ctx.texture_cache,
ctx.uncolored_mesh_batches_loners[i].key);
SDL_GL_BindTexture(atlas, NULL, NULL);
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches_loners[i].value);
SDL_GL_UnbindTexture(atlas);
}
}
glPopMatrix();
}
void render(void) {
if (ctx.texture_cache.is_dirty)
textures_update_current_atlas(&ctx.texture_cache);
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
render_space();
// glDisable(GL_CULL_FACE);
// glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
/* TODO: write with no depth test, just fill it in */
render_sprites();
render_rectangles();
render_circles();
// SDL_RenderPresent(ctx.renderer);
// glEnable(GL_CULL_FACE);
// glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
/* TODO: use depth test to optimize gui regions away */
render_space();
SDL_RenderFlush(ctx.renderer);
SDL_GL_SwapWindow(ctx.window);
}

View File

@ -35,6 +35,34 @@ void push_rectangle(t_frect rect, t_color color);
/* pushes a filled circle onto the circle render queue */
void push_circle(t_fvec2 position, float radius, t_color color);
/* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */
void unfurl_triangle(const char *path,
/* */
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
/* TODO: */
/* pushes a colored textured 3d triangle onto the render queue */
// void unfurl_colored_triangle(const char *path,
// t_fvec3 v0,
// t_fvec3 v1,
// t_fvec3 v2,
// t_shvec2 uv0,
// t_shvec2 uv1,
// t_shvec2 uv2,
// t_color c0,
// t_color c1,
// t_color c2);
/* TODO: billboarding */
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
/* renders the background, then the primitives in all render queues */
void render(void);

View File

@ -15,7 +15,7 @@
#include <stdio.h>
static SDL_Surface *image_to_surface(char *path) {
static SDL_Surface *image_to_surface(const char *path) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
if (handle == NULL)
goto fail;
@ -236,7 +236,7 @@ void textures_dump_atlases(struct texture_cache *cache) {
}
void textures_load(struct texture_cache *cache, char *path) {
void textures_load(struct texture_cache *cache, const char *path) {
/* no need to do anything if it was loaded already */
if (shgeti(cache->hash, path) >= 0 || shgeti(cache->loner_hash, path) >= 0)
return;
@ -300,18 +300,19 @@ void textures_update_current_atlas(struct texture_cache *cache) {
}
SDL_Rect textures_get_srcrect(struct texture_cache *cache, char *path) {
SDL_Rect textures_get_srcrect(struct texture_cache *cache, const char *path) {
struct texture_cache_item *texture = shgetp_null(cache->hash, path);
if (texture == NULL) {
CRY("Texture lookup failed.",
"Tried to get texture that isn't loaded.");
return (SDL_Rect){ 0, 0, 0, 0 };
}
return texture->value.srcrect;
}
int textures_get_atlas_index(struct texture_cache *cache, char *path) {
int textures_get_atlas_index(struct texture_cache *cache, const char *path) {
struct texture_cache_item *texture = shgetp_null(cache->hash, path);
/* it might be a loner texture */
@ -339,7 +340,7 @@ SDL_Texture *textures_get_atlas(struct texture_cache *cache, int index) {
}
SDL_Texture *textures_get_loner(struct texture_cache *cache, char *path) {
SDL_Texture *textures_get_loner(struct texture_cache *cache, const char *path) {
struct texture_cache_item *texture = shgetp_null(cache->loner_hash, path);
if (texture == NULL) {

View File

@ -51,25 +51,25 @@ void textures_dump_atlases(struct texture_cache *cache);
/* loads an image if it isn't in the cache, otherwise a no-op. */
/* can be called from anywhere at any time after init, useful if you want to */
/* preload textures you know will definitely be used */
void textures_load(struct texture_cache *cache, char *path);
void textures_load(struct texture_cache *cache, const char *path);
/* repacks the current texture atlas based on the texture cache */
void textures_update_current_atlas(struct texture_cache *cache);
/* returns a rect in a texture cache atlas based on a path, for drawing */
/* if the texture is not found, returns a zero-filled rect (so check w or h) */
SDL_Rect textures_get_srcrect(struct texture_cache *cache, char *path);
SDL_Rect textures_get_srcrect(struct texture_cache *cache, const char *path);
/* returns which atlas the texture in the path is in, starting from 0 */
/* if the texture is not found, returns INT_MIN */
int textures_get_atlas_index(struct texture_cache *cache, char *path);
int textures_get_atlas_index(struct texture_cache *cache, const char *path);
/* returns a pointer to the atlas at `index` */
/* if the index is out of bounds, returns NULL. */
/* you can get the index via texture_get_atlas_index */
SDL_Texture *textures_get_atlas(struct texture_cache *cache, int index);
SDL_Texture *textures_get_loner(struct texture_cache *cache, char *path);
SDL_Texture *textures_get_loner(struct texture_cache *cache, const char *path);
/* returns the number of atlases in the cache */
size_t textures_get_num_atlases(struct texture_cache *cache);

View File

@ -109,6 +109,19 @@ typedef struct fvec2 {
} t_fvec2;
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec3 {
float x, y, z;
} t_fvec3;
/* a point in some space (short) */
typedef struct shvec2 {
short x, y;
} t_shvec2;
/* decrements an lvalue (which should be an int), stopping at 0 */
/* meant for tick-based timers in game logic */
/*