diff --git a/src/context.h b/src/context.h index 7ad1d64..54c4f99 100644 --- a/src/context.h +++ b/src/context.h @@ -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; diff --git a/src/game/scenes/ingame.c b/src/game/scenes/ingame.c index 1bea67b..9958539 100644 --- a/src/game/scenes/ingame.c +++ b/src/game/scenes/ingame.c @@ -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 }); } diff --git a/src/main.c b/src/main.c index a41cb68..af5bfa2 100644 --- a/src/main.c +++ b/src/main.c @@ -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. */ diff --git a/src/private/rendering.h b/src/private/rendering.h index 2c1dee4..fcaa614 100644 --- a/src/private/rendering.h +++ b/src/private/rendering.h @@ -5,6 +5,7 @@ #include "../util.h" #include +#include #include @@ -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 diff --git a/src/rendering.c b/src/rendering.c index 734f679..7108981 100644 --- a/src/rendering.c +++ b/src/rendering.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -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); } diff --git a/src/rendering.h b/src/rendering.h index 018f79f..99b0a4f 100644 --- a/src/rendering.h +++ b/src/rendering.h @@ -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); diff --git a/src/textures.c b/src/textures.c index ef26380..c202ff2 100644 --- a/src/textures.c +++ b/src/textures.c @@ -15,7 +15,7 @@ #include -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) { diff --git a/src/textures.h b/src/textures.h index bff3cbb..7d6c7ea 100644 --- a/src/textures.h +++ b/src/textures.h @@ -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); diff --git a/src/util.h b/src/util.h index bb90b3e..34c69df 100644 --- a/src/util.h +++ b/src/util.h @@ -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 */ /*