Compare commits

...

19 Commits

Author SHA1 Message Date
7e409fc14a work towards DeferredCommandDraw being universal, support for DeferredCommandDepthRange, rework of cirlce mesh (has a bug still), get_quad_element_buffer() now more general, as it should be with gl_any 2024-10-17 21:01:35 +03:00
aa3cab87d2 skip switching texture modes when they're the same as the last used 2024-10-16 22:52:10 +03:00
1dc0dea762 no need for packed types no more 2024-10-16 03:31:02 +03:00
7f56ed8421 remove alignas for twn_types.h, it looks to be just as performant and even more in cases i looked at 2024-10-16 02:32:04 +03:00
119b706638 minor optimizations over sprite path 2024-10-15 19:32:42 +03:00
f2bbc1863e cache sprite srcrects 2024-10-15 18:43:02 +03:00
768daf1f54 move pipelines and texture modes to deferred commands 2024-10-15 18:35:08 +03:00
139394c6de partial implementation of double buffered render 2024-10-15 15:29:45 +03:00
446402c2e0 don't write unused color to vertices, utilize flat shading for only 2 important edges 2024-10-14 21:23:44 +03:00
f7a718003e send vertex data packed 2024-10-14 20:19:18 +03:00
f087bf1f7f fix depth clearing, ghostly modes not using depth layers, ortho with 0..1 2024-10-14 16:00:27 +03:00
19bf88d44e finally properly implemented depth optimization for 2d 2024-10-14 15:31:56 +03:00
3535a185df don't use depth range hack in rect case 2024-10-14 12:16:23 +03:00
d34516c4ee Merge branch 'main' of https://git.poto.cafe/wanp/townengine 2024-10-14 11:48:46 +03:00
b295c5920c rendering: use sprite batching techniques for rect primitives, unite their render path 2024-10-14 11:46:07 +03:00
f7f27119e1 use static, fixed arrays for circle geometry data instead of allocating for each one 2024-10-13 22:32:59 -03:00
ffab6a3924 cache font _files_ as well to avoid duplicate buffers 2024-10-13 21:34:05 -03:00
82bad550e5 CMakeLists.txt: fixes 2024-10-13 23:43:00 +03:00
19b9812b3e /bin/twn: load symbols from libgame.so 2024-10-13 22:51:48 +03:00
24 changed files with 992 additions and 359 deletions

View File

@ -9,11 +9,11 @@ if(NOT EMSCRIPTEN)
endif() endif()
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified # CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
if(NOT CMAKE_BUILD_TYPE) if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
if(NOT TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug) if(NOT DEFINED TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
set(TWN_SANITIZE ON) set(TWN_SANITIZE ON)
endif() endif()
@ -104,6 +104,7 @@ set(TWN_NONOPT_SOURCE_FILES
src/rendering/twn_draw.c src/rendering/twn_draw_c.h src/rendering/twn_draw.c src/rendering/twn_draw_c.h
src/rendering/twn_sprites.c src/rendering/twn_sprites.c
src/rendering/twn_rects.c
src/rendering/twn_text.c src/rendering/twn_text.c
src/rendering/twn_triangles.c src/rendering/twn_triangles.c
src/rendering/twn_circles.c src/rendering/twn_circles.c
@ -183,7 +184,7 @@ function(give_options_without_warnings target)
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}> $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}> $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
-Bsymbolic-functions -Bsymbolic-functions
-Wl,--hash-style=gnu) $<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
target_compile_definitions(${target} PRIVATE target_compile_definitions(${target} PRIVATE
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME> $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>

View File

@ -30,7 +30,9 @@ static void handle_input(void)
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255}; (Color){(uint8_t)(state->bunniesCount % 190 + 50),
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }
@ -46,7 +48,9 @@ static void handle_input(void)
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255}; (Color){(uint8_t)(state->bunniesCount % 190 + 50),
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }

View File

@ -3,7 +3,7 @@
#include "twn_game_api.h" #include "twn_game_api.h"
#define MAX_BUNNIES 100000 // 100K bunnies limit #define MAX_BUNNIES 500000 // 100K bunnies limit
#define BUNNY_W 26 #define BUNNY_W 26
#define BUNNY_H 37 #define BUNNY_H 37
#define SPRITE_SCALE 1 #define SPRITE_SCALE 1

View File

@ -20,9 +20,9 @@ static void title_tick(State *state) {
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 })); ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */ /* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1; size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len); char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number); snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
const char *font = "/fonts/kenney-pixel.ttf"; const char *font = "/fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;
@ -39,7 +39,6 @@ static void title_tick(State *state) {
); );
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font); draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
free(text_str); free(text_str);
} }

View File

@ -16,7 +16,7 @@ case "$1" in
;; ;;
gdb ) unset DEBUGINFOD_URLS gdb ) unset DEBUGINFOD_URLS
$0 build && gdb -ex run --args "$(basename $PWD)" "${@:2}" $0 build && gdb --se=libgame.so -ex run --args "$(basename $PWD)" "${@:2}"
;; ;;
apitrace ) case "$2" in apitrace ) case "$2" in

View File

@ -22,6 +22,7 @@ TWN_API void draw_sprite(char const *path,
TWN_API void draw_rectangle(Rect rect, Color color); TWN_API void draw_rectangle(Rect rect, Color color);
/* pushes a filled circle onto the circle render queue */ /* pushes a filled circle onto the circle render queue */
/* note that its edges may look jagged with a radius larger than 2048 */
TWN_API void draw_circle(Vec2 position, float radius, Color color); TWN_API void draw_circle(Vec2 position, float radius, Color color);
/* TODO: have font optional, with something minimal coming embedded */ /* TODO: have font optional, with something minimal coming embedded */

View File

@ -1,6 +1,9 @@
#ifndef TWN_TEXTURES_MODES_H #ifndef TWN_TEXTURES_MODES_H
#define TWN_TEXTURES_MODES_H #define TWN_TEXTURES_MODES_H
/* TODO: rename, as it doesn't have to be about textures only, but blending */
/* TODO: move from public /include/ tree */
/* alpha channel information */ /* alpha channel information */
typedef enum TextureMode { typedef enum TextureMode {
TEXTURE_MODE_OPAQUE, /* all pixels are solid */ TEXTURE_MODE_OPAQUE, /* all pixels are solid */

View File

@ -8,7 +8,6 @@
/* a point in some space (integer) */ /* a point in some space (integer) */
typedef struct Vec2i { typedef struct Vec2i {
_Alignas(8)
int32_t x; int32_t x;
int32_t y; int32_t y;
} Vec2i; } Vec2i;
@ -16,7 +15,6 @@ _Alignas(8)
/* a point in some space (floating point) */ /* a point in some space (floating point) */
typedef struct Vec2 { typedef struct Vec2 {
_Alignas(8)
float x; float x;
float y; float y;
} Vec2; } Vec2;
@ -25,7 +23,6 @@ _Alignas(8)
/* a point in some three dimension space (floating point) */ /* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */ /* y goes up, x goes to the right */
typedef struct Vec3 { typedef struct Vec3 {
_Alignas(16)
float x; float x;
float y; float y;
float z; float z;
@ -35,7 +32,6 @@ _Alignas(16)
/* a point in some three dimension space (floating point) */ /* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */ /* y goes up, x goes to the right */
typedef struct Vec4 { typedef struct Vec4 {
_Alignas(16)
float x; float x;
float y; float y;
float z; float z;
@ -45,7 +41,6 @@ _Alignas(16)
/* 32-bit color data */ /* 32-bit color data */
typedef struct Color { typedef struct Color {
_Alignas(4)
uint8_t r; uint8_t r;
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
@ -55,7 +50,6 @@ _Alignas(4)
/* a rectangle with the origin at the upper left (integer) */ /* a rectangle with the origin at the upper left (integer) */
typedef struct Recti { typedef struct Recti {
_Alignas(16)
int32_t x; int32_t x;
int32_t y; int32_t y;
int32_t w; int32_t w;
@ -65,7 +59,6 @@ _Alignas(16)
/* a rectangle with the origin at the upper left (floating point) */ /* a rectangle with the origin at the upper left (floating point) */
typedef struct Rect { typedef struct Rect {
_Alignas(16)
float x; float x;
float y; float y;
float w; float w;

View File

@ -1,4 +1,3 @@
#include "twn_util.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_draw.h" #include "twn_draw.h"
@ -22,28 +21,17 @@ void draw_circle(Vec2 position, float radius, Color color) {
arrput(ctx.render_queue_2d, primitive); arrput(ctx.render_queue_2d, primitive);
} }
/* TODO: caching and reuse scheme */
/* vertices_out and indices_out MUST BE FREED */
void create_circle_geometry(Vec2 position, void create_circle_geometry(Vec2 position,
Color color,
float radius, float radius,
size_t num_vertices, size_t num_vertices,
SDL_Vertex **vertices_out, Vec2 vertices[])
int **indices_out)
{ {
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
/* the angle (in radians) to rotate by on each iteration */ /* the angle (in radians) to rotate by on each iteration */
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180); float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
vertices[0].position.x = (float)position.x; vertices[0].x = (float)position.x;
vertices[0].position.y = (float)position.y; vertices[0].y = (float)position.y;
vertices[0].color.r = color.r;
vertices[0].color.g = color.g;
vertices[0].color.b = color.b;
vertices[0].color.a = color.a;
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
/* this point will rotate around the center */ /* this point will rotate around the center */
float start_x = 0.0f - radius; float start_x = 0.0f - radius;
@ -52,37 +40,13 @@ void create_circle_geometry(Vec2 position,
for (size_t i = 1; i < num_vertices + 1; ++i) { for (size_t i = 1; i < num_vertices + 1; ++i) {
float final_seg_rotation_angle = (float)i * seg_rotation_angle; float final_seg_rotation_angle = (float)i * seg_rotation_angle;
vertices[i].position.x = float c, s;
cosf(final_seg_rotation_angle) * start_x - sincosf(final_seg_rotation_angle, &s, &c);
sinf(final_seg_rotation_angle) * start_y;
vertices[i].position.y =
cosf(final_seg_rotation_angle) * start_y +
sinf(final_seg_rotation_angle) * start_x;
vertices[i].position.x += position.x; vertices[i].x = c * start_x - s * start_y;
vertices[i].position.y += position.y; vertices[i].y = c * start_y + s * start_x;
vertices[i].color.r = color.r; vertices[i].x += position.x;
vertices[i].color.g = color.g; vertices[i].y += position.y;
vertices[i].color.b = color.b;
vertices[i].color.a = color.a;
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
size_t triangle_offset = 3 * (i - 1);
/* center point index */
indices[triangle_offset] = 0;
/* generated point index */
indices[triangle_offset + 1] = (int)i;
size_t index = (i + 1) % num_vertices;
if (index == 0)
index = num_vertices;
indices[triangle_offset + 2] = (int)index;
} }
*vertices_out = vertices;
*indices_out = indices;
} }

View File

@ -35,22 +35,6 @@ void render_queue_clear(void) {
} }
/* rectangle */
void draw_rectangle(Rect rect, Color color) {
RectPrimitive rectangle = {
.rect = rect,
.color = color,
};
Primitive2D primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
}
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) { void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
const float bt = (float)border_thickness; /* i know! */ const float bt = (float)border_thickness; /* i know! */
const float bt2 = bt * 2; /* combined size of the two borders in an axis */ const float bt2 = bt * 2; /* combined size of the two borders in an axis */
@ -197,36 +181,152 @@ static void render_2d(void) {
const size_t render_queue_len = arrlenu(ctx.render_queue_2d); const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
size_t batch_count = 0; struct Render2DInvocation {
Primitive2D const *primitive;
double layer;
union {
struct QuadBatch quad_batch;
};
};
/* first, collect all invocations, while merging into batches where applicable */
/* we separate into opaque and transparent ones, as it presents optimization opportunities */
struct Render2DInvocation *opaque_invocations = NULL;
struct Render2DInvocation *ghostly_invocations = NULL;
arrsetcap(opaque_invocations, render_queue_len);
arrsetcap(ghostly_invocations, render_queue_len);
for (size_t i = 0; i < render_queue_len; ++i) { for (size_t i = 0; i < render_queue_len; ++i) {
const Primitive2D *current = &ctx.render_queue_2d[i]; const Primitive2D *current = &ctx.render_queue_2d[i];
// TODO: https://gamedev.stackexchange.com/questions/101136/using-full-resolution-of-depth-buffer-for-2d-rendering
double const layer = ((double)((render_queue_len + 1) - i) / (double)(render_queue_len + 1)) * 0.75;
switch (current->type) { switch (current->type) {
case PRIMITIVE_2D_SPRITE: { case PRIMITIVE_2D_SPRITE: {
const struct SpriteBatch batch = const struct QuadBatch batch =
collect_sprite_batch(current, render_queue_len - i); collect_sprite_batch(current, render_queue_len - i);
/* TODO: what's even the point? just use OR_EQUAL comparison */ struct Render2DInvocation const invocation = {
set_depth_range((double)batch_count / UINT16_MAX, 1.0); .primitive = current,
render_sprites(current, batch); .quad_batch = batch,
.layer = layer,
};
i += batch.size - 1; ++batch_count; if (batch.mode == TEXTURE_MODE_GHOSTLY)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
i += batch.size - 1;
break; break;
} }
case PRIMITIVE_2D_RECT:
render_rectangle(&current->rect); case PRIMITIVE_2D_RECT: {
const struct QuadBatch batch =
collect_rect_batch(current, render_queue_len - i);
struct Render2DInvocation const invocation = {
.primitive = current,
.quad_batch = batch,
.layer = layer,
};
if (batch.mode == TEXTURE_MODE_GHOSTLY)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
i += batch.size - 1;
break; break;
}
case PRIMITIVE_2D_CIRCLE: {
struct Render2DInvocation const invocation = {
.primitive = current,
.layer = layer,
};
if (current->circle.color.a != 255)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
break;
}
case PRIMITIVE_2D_TEXT: {
struct Render2DInvocation const invocation = {
.primitive = current,
.layer = layer,
};
arrput(ghostly_invocations, invocation);
break;
}
default:
SDL_assert(false);
}
}
/* first issue all opaque primitives, front-to-back */
for (size_t i = 0; i < arrlenu(opaque_invocations); ++i) {
struct Render2DInvocation const invocation = opaque_invocations[arrlenu(opaque_invocations) - 1 - i];
/* idea here is to set constant z write that moves further and further along */
/* with that every batch can early z reject against the previous */
/* additionally, it will also apply for future transparent passes, sandwitching in-between */
set_depth_range(invocation.layer, 1.0);
switch (invocation.primitive->type) {
case PRIMITIVE_2D_SPRITE: {
render_sprite_batch(invocation.primitive, invocation.quad_batch);
break;
}
case PRIMITIVE_2D_RECT: {
render_rect_batch(invocation.primitive, invocation.quad_batch);
break;
}
/* TODO: circle batching */
case PRIMITIVE_2D_CIRCLE: case PRIMITIVE_2D_CIRCLE:
render_circle(&current->circle); render_circle(&invocation.primitive->circle);
break; break;
case PRIMITIVE_2D_TEXT: case PRIMITIVE_2D_TEXT:
render_text(&current->text); default:
SDL_assert(false);
}
}
/* then issue all transparent primitives, back-to-front */
for (size_t i = 0; i < arrlenu(ghostly_invocations); ++i) {
struct Render2DInvocation const invocation = ghostly_invocations[i];
/* now we use it not for writing layers, but inferring ordering */
set_depth_range(invocation.layer, 1.0);
switch (invocation.primitive->type) {
case PRIMITIVE_2D_SPRITE: {
render_sprite_batch(invocation.primitive, invocation.quad_batch);
break;
}
case PRIMITIVE_2D_RECT: {
render_rect_batch(invocation.primitive, invocation.quad_batch);
break;
}
/* TODO: circle batching */
case PRIMITIVE_2D_CIRCLE:
render_circle(&invocation.primitive->circle);
break;
case PRIMITIVE_2D_TEXT:
render_text(&invocation.primitive->text);
break; break;
default: default:
SDL_assert(false); SDL_assert(false);
} }
} }
arrfree(opaque_invocations);
arrfree(ghostly_invocations);
} }
@ -274,11 +374,11 @@ void render(void) {
} }
} }
render_space(); start_render_frame(); {
render_skybox(); /* after space, as to use depth buffer for early rejection */ render_space();
render_2d(); render_skybox(); /* after space, as to use depth buffer for early z rejection */
swap_buffers(); render_2d();
clear_draw_buffer(); } end_render_frame();
} }

View File

@ -20,7 +20,10 @@
extern Matrix4 camera_projection_matrix; extern Matrix4 camera_projection_matrix;
extern Matrix4 camera_look_at_matrix; extern Matrix4 camera_look_at_matrix;
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6) #define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
#define CIRCLE_VERTICES_MAX 2048
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
typedef GLuint VertexBuffer; typedef GLuint VertexBuffer;
@ -126,22 +129,25 @@ void render(void);
/* clears all render queues */ /* clears all render queues */
void render_queue_clear(void); void render_queue_clear(void);
/* fills two existing arrays with the geometry data of a circle */
/* the size of indices must be at least 3 times the number of vertices */
void create_circle_geometry(Vec2 position, void create_circle_geometry(Vec2 position,
Color color,
float radius, float radius,
size_t num_vertices, size_t num_vertices,
SDL_Vertex **vertices_out, Vec2 vertices[]);
int **indices_out);
struct SpriteBatch { struct QuadBatch {
size_t size; /* how many primitives are in current batch */ size_t size; /* how many primitives are in current batch */
TextureMode mode; TextureMode mode; /* how color should be applied */
bool constant_colored; /* whether colored batch is uniformly colored */ bool constant_colored; /* whether colored batch is uniformly colored */
bool repeat; /* whether repeat is needed */ bool repeat; /* whether repeat is needed */
} collect_sprite_batch(const Primitive2D primitives[], size_t len); bool textured;
} collect_quad_batch(const Primitive2D primitives[], size_t len);
void render_sprites(const Primitive2D primitives[], void render_quad_batch(const Primitive2D primitives[], struct QuadBatch batch);
const struct SpriteBatch batch); struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len);
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
void draw_uncolored_space_traingle_batch(MeshBatch *batch, void draw_uncolored_space_traingle_batch(MeshBatch *batch,
TextureKey texture_key); TextureKey texture_key);
@ -160,16 +166,18 @@ void text_cache_reset_arena(TextCache *cache);
VertexBuffer create_vertex_buffer(void); VertexBuffer create_vertex_buffer(void);
VertexBuffer get_scratch_vertex_array(void);
void delete_vertex_buffer(VertexBuffer buffer); void delete_vertex_buffer(VertexBuffer buffer);
void specify_vertex_buffer(VertexBuffer buffer, void *data, size_t bytes); void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
/* uses present in 1.5 buffer mapping feature */ /* uses present in 1.5 buffer mapping feature */
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes); VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */ /* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder, bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
void *bytes, void const *bytes,
size_t size); size_t size);
/* state */ /* state */
@ -182,7 +190,9 @@ void swap_buffers(void);
void set_depth_range(double low, double high); void set_depth_range(double low, double high);
void bind_quad_element_buffer(void); VertexBuffer get_quad_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
void render_circle(const CirclePrimitive *circle); void render_circle(const CirclePrimitive *circle);
@ -194,19 +204,17 @@ void use_2d_pipeline(void);
void use_texture_mode(TextureMode mode); void use_texture_mode(TextureMode mode);
void upload_quad_vertices(Rect rect); void finally_render_quads(Primitive2D const primitives[],
struct QuadBatch batch,
VertexBuffer buffer);
void finally_render_sprites(Primitive2D const primitives[], size_t get_quad_payload_size(struct QuadBatch batch);
struct SpriteBatch batch,
VertexBuffer buffer);
size_t get_sprite_payload_size(struct SpriteBatch batch); bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
VertexBufferBuilder *builder,
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch, Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
VertexBufferBuilder *builder, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, Color color);
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
Color color);
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch, void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
TextureKey texture_key, TextureKey texture_key,
@ -235,4 +243,8 @@ void pop_fog(void);
void finally_pop_fog(void); void finally_pop_fog(void);
void start_render_frame(void);
void end_render_frame(void);
#endif #endif

View File

@ -3,15 +3,15 @@
#include "twn_util_c.h" #include "twn_util_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_text_c.h" #include "twn_text_c.h"
#include "twn_types.h"
#include <glad/glad.h> #include <glad/glad.h>
#include <stb_ds.h> #include <stb_ds.h>
/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
/* this is only doable is we take out color attribute to separate array or a portion of it */
/* interleaved vertex array data */ /* interleaved vertex array data */
/* TODO: use int16_t for uvs */
/* TODO: use packed types? */
/* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */
typedef struct ElementIndexedQuad { typedef struct ElementIndexedQuad {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2 v0;
@ -31,7 +31,6 @@ typedef struct ElementIndexedQuad {
Color c3; Color c3;
} ElementIndexedQuad; } ElementIndexedQuad;
typedef struct ElementIndexedQuadWithoutColor { typedef struct ElementIndexedQuadWithoutColor {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2 v0;
@ -48,6 +47,75 @@ typedef struct ElementIndexedQuadWithoutColor {
} ElementIndexedQuadWithoutColor; } ElementIndexedQuadWithoutColor;
typedef struct ElementIndexedQuadWithoutTexture {
/* upper-left */
Vec2 v0;
Color c0;
/* bottom-left */
Vec2 v1;
Color c1;
/* bottom-right */
Vec2 v2;
Color c2;
/* upper-right */
Vec2 v3;
Color c3;
} ElementIndexedQuadWithoutTexture;
typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
/* upper-left */
Vec2 v0;
/* bottom-left */
Vec2 v1;
/* bottom-right */
Vec2 v2;
/* upper-right */
Vec2 v3;
} ElementIndexedQuadWithoutColorWithoutTexture;
typedef struct {
size_t offset;
GLenum type;
GLsizei stride;
GLuint buffer;
uint8_t arity; /* leave at 0 to signal pointer as unused */
} AttributeArrayPointer;
/* allows us to have generic way to issue draws as well as */
/* deferring new draw calls while previous frame is still being drawn */
typedef struct {
AttributeArrayPointer vertices;
AttributeArrayPointer texcoords;
bool constant_colored;
union {
AttributeArrayPointer colors;
Color color;
};
bool textured, texture_repeat, uses_gpu_key;
TextureKey texture_key;
GPUTexture gpu_texture;
GLuint element_buffer;
GLsizei element_count;
GLsizei range_start, range_end;
double depth_range_low, depth_range_high;
} DeferredCommandDraw;
typedef struct {
Color color;
bool clear_color;
bool clear_depth;
bool clear_stencil;
} DeferredCommandClear;
typedef enum { typedef enum {
PIPELINE_NO, PIPELINE_NO,
PIPELINE_SPACE, PIPELINE_SPACE,
@ -55,10 +123,266 @@ typedef enum {
} Pipeline; } Pipeline;
typedef struct {
Pipeline pipeline;
} DeferredCommandUsePipeline;
typedef struct {
TextureMode mode;
} DeferredCommandUseTextureMode;
typedef struct {
double low, high;
} DeferredCommandDepthRange;
typedef struct {
enum DeferredCommandType {
DEFERRED_COMMAND_TYPE_DRAW,
DEFERRED_COMMAND_TYPE_CLEAR,
DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
} type;
union {
DeferredCommandDraw draw;
DeferredCommandClear clear;
DeferredCommandUsePipeline use_pipeline;
DeferredCommandUseTextureMode use_texture_mode;
DeferredCommandDepthRange depth_range;
};
} DeferredCommand;
static TextureMode texture_mode_last_used = -1;
static Pipeline pipeline_last_used = PIPELINE_NO; static Pipeline pipeline_last_used = PIPELINE_NO;
/* potentially double buffered array of vertex array handles */
/* we assume they will be refilled fully each frame */
static size_t scratch_va_front_used, scratch_va_back_used;
static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays;
static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays;
static void restart_scratch_vertex_arrays(void) {
scratch_va_front_used = 0;
scratch_va_back_used = 0;
if (ctx.render_double_buffered) {
current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
&back_scratch_vertex_arrays : &front_scratch_vertex_arrays;
}
}
GLuint get_scratch_vertex_array(void) {
size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
&scratch_va_front_used : &scratch_va_back_used;
if (arrlenu(*current_scratch_vertex_array) <= *used) {
GLuint handle;
glGenBuffers(1, &handle);
arrpush(*current_scratch_vertex_array, handle);
}
(*used)++;
return (*current_scratch_vertex_array)[*used - 1];
}
static void finally_use_2d_pipeline(void);
static void finally_use_space_pipeline(void);
static void finally_use_texture_mode(TextureMode mode);
static DeferredCommand *deferred_commands;
static void issue_deferred_draw_commands(void) {
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
switch (deferred_commands[i].type) {
case DEFERRED_COMMAND_TYPE_DEPTH_RANGE: {
glDepthRange(deferred_commands[i].depth_range.low, deferred_commands[i].depth_range.high);
break;
}
case DEFERRED_COMMAND_TYPE_CLEAR: {
glClearColor((1.0f / 255) * deferred_commands[i].clear.color.r,
(1.0f / 255) * deferred_commands[i].clear.color.g,
(1.0f / 255) * deferred_commands[i].clear.color.b,
(1.0f / 255) * deferred_commands[i].clear.color.a);
/* needed as we might mess with it */
glDepthRange(0.0, 1.0);
glDepthMask(GL_TRUE);
glClear((deferred_commands[i].clear.clear_color ? GL_COLOR_BUFFER_BIT : 0) |
(deferred_commands[i].clear.clear_depth ? GL_DEPTH_BUFFER_BIT : 0) |
(deferred_commands[i].clear.clear_stencil ? GL_STENCIL_BUFFER_BIT : 0) );
break;
}
case DEFERRED_COMMAND_TYPE_DRAW: {
DeferredCommandDraw const command = deferred_commands[i].draw;
/* TODO: don't assume a single vertex array ? */
SDL_assert(command.vertices.arity != 0);
SDL_assert(command.vertices.buffer);
SDL_assert(command.element_count != 0);
SDL_assert(command.element_buffer);
glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(command.vertices.arity,
command.vertices.type,
command.vertices.stride,
(void *)command.vertices.offset);
if (command.texcoords.arity != 0) {
SDL_assert(command.texcoords.buffer == command.vertices.buffer);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(command.texcoords.arity,
command.texcoords.type,
command.texcoords.stride,
(void *)command.texcoords.offset);
}
if (command.colors.arity != 0) {
SDL_assert(command.colors.buffer == command.vertices.buffer);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(command.colors.arity,
command.colors.type,
command.colors.stride,
(void *)command.colors.offset);
} else if (command.constant_colored)
glColor4ub(command.color.r,
command.color.g,
command.color.b,
command.color.a);
if (command.textured) {
if (command.uses_gpu_key)
glBindTexture(GL_TEXTURE_2D, command.gpu_texture);
else if (command.texture_repeat)
textures_bind_repeating(&ctx.texture_cache, command.texture_key);
else
textures_bind(&ctx.texture_cache, command.texture_key);
}
if (command.range_start == command.range_end)
glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL);
else
glDrawRangeElements(GL_TRIANGLES,
command.range_start,
command.range_end,
command.element_count,
GL_UNSIGNED_SHORT,
NULL);
/* state clearing */
if (command.textured)
glBindTexture(GL_TEXTURE_2D, 0);
if (command.colors.arity != 0)
glDisableClientState(GL_COLOR_ARRAY);
if (command.texcoords.arity != 0)
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
break;
}
case DEFERRED_COMMAND_TYPE_USE_PIPIELINE: {
switch (deferred_commands[i].use_pipeline.pipeline) {
case PIPELINE_2D:
finally_use_2d_pipeline();
break;
case PIPELINE_SPACE:
finally_use_space_pipeline();
break;
case PIPELINE_NO:
default:
SDL_assert(false);
}
break;
}
case DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE: {
finally_use_texture_mode(deferred_commands[i].use_texture_mode.mode);
break;
}
default:
SDL_assert(false);
}
}
}
void clear_draw_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_CLEAR,
.clear = (DeferredCommandClear) {
.clear_color = true,
.clear_depth = true,
.clear_stencil = true,
.color = (Color) { 230, 230, 230, 1 }
}
};
arrpush(deferred_commands, command);
}
void start_render_frame(void) {
clear_draw_buffer();
}
void end_render_frame(void) {
if (!ctx.render_double_buffered || ctx.game.frame_number == 0) {
issue_deferred_draw_commands();
SDL_GL_SwapWindow(ctx.window);
arrsetlen(deferred_commands, 0);
restart_scratch_vertex_arrays();
} else {
/* instead of waiting for frame to finish for the swap, we continue */
/* while issuing new state for the next call, but deferring any fragment emitting calls for later */
/* actual swap will happen when next frame is fully finished, introducing a delay */
SDL_GL_SwapWindow(ctx.window);
issue_deferred_draw_commands();
restart_scratch_vertex_arrays();
glFlush();
arrsetlen(deferred_commands, 0);
}
}
void use_space_pipeline(void) { void use_space_pipeline(void) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
.use_pipeline = { PIPELINE_SPACE }
};
arrpush(deferred_commands, command);
}
static void finally_use_space_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) if (pipeline_last_used == PIPELINE_SPACE)
return; return;
@ -91,11 +415,22 @@ void use_space_pipeline(void) {
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&camera_look_at_matrix.row[0].x); glLoadMatrixf(&camera_look_at_matrix.row[0].x);
texture_mode_last_used = -1;
pipeline_last_used = PIPELINE_SPACE; pipeline_last_used = PIPELINE_SPACE;
} }
void use_2d_pipeline(void) { void use_2d_pipeline(void) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
.use_pipeline = { PIPELINE_2D }
};
arrpush(deferred_commands, command);
}
static void finally_use_2d_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) { if (pipeline_last_used == PIPELINE_SPACE) {
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFlush(); glFlush();
@ -127,80 +462,30 @@ void use_2d_pipeline(void) {
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, -1, 1); glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, 0, 1);
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
texture_mode_last_used = -1;
pipeline_last_used = PIPELINE_2D; pipeline_last_used = PIPELINE_2D;
} }
void upload_quad_vertices(Rect rect) {
/* client memory needs to be reachable on glDraw*, so */
static float vertices[6 * 2];
vertices[0] = rect.x; vertices[1] = rect.y;
vertices[2] = rect.x; vertices[3] = rect.y + rect.h;
vertices[4] = rect.x + rect.w; vertices[5] = rect.y + rect.h;
vertices[6] = rect.x + rect.w; vertices[7] = rect.y + rect.h;
vertices[8] = rect.x + rect.w; vertices[9] = rect.y;
vertices[10] = rect.x; vertices[11] = rect.y;
glVertexPointer(2, GL_FLOAT, 0, (void *)&vertices);
}
void render_rectangle(const RectPrimitive *rectangle) {
glColor4ub(rectangle->color.r, rectangle->color.g,
rectangle->color.b, rectangle->color.a);
glEnableClientState(GL_VERTEX_ARRAY);
upload_quad_vertices(rectangle->rect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
}
void render_circle(const CirclePrimitive *circle) {
SDL_Vertex *vertices = NULL;
int *indices = NULL;
int num_vertices = (int)circle->radius;
create_circle_geometry(circle->position,
circle->color,
circle->radius,
num_vertices,
&vertices,
&indices);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
sizeof (SDL_Vertex),
&vertices->position);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
sizeof (SDL_Vertex),
&vertices->color);
glDrawElements(GL_TRIANGLES,
num_vertices * 3,
GL_UNSIGNED_INT,
indices);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
SDL_free(vertices);
SDL_free(indices);
}
void use_texture_mode(TextureMode mode) { void use_texture_mode(TextureMode mode) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
.use_texture_mode = { mode }
};
arrpush(deferred_commands, command);
}
static void finally_use_texture_mode(TextureMode mode) {
if (texture_mode_last_used == mode)
return;
static GLuint lists = 0; static GLuint lists = 0;
if (!lists) { if (!lists) {
lists = glGenLists(3); lists = glGenLists(3);
@ -209,7 +494,7 @@ void use_texture_mode(TextureMode mode) {
glNewList(lists + 0, GL_COMPILE); { glNewList(lists + 0, GL_COMPILE); {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS); glDepthFunc(GL_LESS);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST); glDisable(GL_ALPHA_TEST);
} glEndList(); } glEndList();
@ -217,7 +502,7 @@ void use_texture_mode(TextureMode mode) {
/* seethrough */ /* seethrough */
glNewList(lists + 1, GL_COMPILE); { glNewList(lists + 1, GL_COMPILE); {
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST); glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f); glAlphaFunc(GL_EQUAL, 1.0f);
@ -239,6 +524,8 @@ void use_texture_mode(TextureMode mode) {
} else { } else {
glCallList(lists + 2); glCallList(lists + 2);
} }
texture_mode_last_used = mode;
} }
@ -258,11 +545,11 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder, bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
void *bytes, size_t size) { void const *bytes, size_t size) {
if (builder->bytes_left == 0) if (builder->bytes_left == 0)
return false; return false;
SDL_memcpy(builder->mapping, bytes, size); memcpy(builder->mapping, bytes, size);
builder->bytes_left -= size; builder->bytes_left -= size;
/* trigger data send */ /* trigger data send */
@ -277,92 +564,107 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
} }
void finally_render_sprites(const Primitive2D primitives[], void finally_render_quads(const Primitive2D primitives[],
const struct SpriteBatch batch, const struct QuadBatch batch,
const VertexBuffer buffer) const VertexBuffer buffer)
{ {
(void)buffer; DeferredCommandDraw command = {0};
GLsizei off; GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
GLsizei voff;
GLsizei uvoff;
if (!batch.constant_colored) { if (!batch.constant_colored && batch.textured) {
off = offsetof(ElementIndexedQuad, v1); off = offsetof(ElementIndexedQuad, v1);
voff = offsetof(ElementIndexedQuad, v0); voff = offsetof(ElementIndexedQuad, v0);
uvoff = offsetof(ElementIndexedQuad, uv0); uvoff = offsetof(ElementIndexedQuad, uv0);
} else { coff = offsetof(ElementIndexedQuad, c0);
} else if (batch.constant_colored && batch.textured) {
off = offsetof(ElementIndexedQuadWithoutColor, v1); off = offsetof(ElementIndexedQuadWithoutColor, v1);
voff = offsetof(ElementIndexedQuadWithoutColor, v0); voff = offsetof(ElementIndexedQuadWithoutColor, v0);
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0); uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
} else if (!batch.constant_colored && !batch.textured) {
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
coff = offsetof(ElementIndexedQuad, c0);
} else if (batch.constant_colored && !batch.textured) {
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
} }
/* vertex specification */ command.vertices = (AttributeArrayPointer) {
glEnableClientState(GL_VERTEX_ARRAY); .arity = 2,
glVertexPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = off,
off, .offset = voff,
(void *)(size_t)voff); .buffer = buffer
};
glEnableClientState(GL_TEXTURE_COORD_ARRAY); if (batch.textured)
glClientActiveTexture(GL_TEXTURE0); command.texcoords = (AttributeArrayPointer) {
glTexCoordPointer(2, .arity = 2,
GL_FLOAT, .type = GL_FLOAT,
off, .stride = off,
(void *)(size_t)uvoff); .offset = uvoff,
.buffer = buffer
};
if (!batch.constant_colored) { if (!batch.constant_colored) {
glEnableClientState(GL_COLOR_ARRAY); command.colors = (AttributeArrayPointer) {
glColorPointer(4, .arity = 4,
GL_UNSIGNED_BYTE, .type = GL_UNSIGNED_BYTE,
off, .stride = off,
(void *)offsetof(ElementIndexedQuad, c0)); .offset = coff,
} else .buffer = buffer
glColor4ub(primitives[0].sprite.color.r, };
primitives[0].sprite.color.g, } else {
primitives[0].sprite.color.b, command.constant_colored = true;
primitives[0].sprite.color.a); command.color = primitives[0].sprite.color;
}
if (!batch.repeat) if (batch.textured) {
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key); /* TODO: bad, don't */
else command.textured = true;
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key); command.texture_key = primitives->sprite.texture_key;
command.texture_repeat = batch.repeat;
}
bind_quad_element_buffer(); command.element_buffer = get_quad_element_buffer();
command.element_count = 6 * (GLsizei)batch.size;
command.range_end = 6 * (GLsizei)batch.size;
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL); use_texture_mode(batch.mode);
/* clear the state */ DeferredCommand final_command = {
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); .type = DEFERRED_COMMAND_TYPE_DRAW,
glBindBuffer(GL_ARRAY_BUFFER, 0); .draw = command
};
glDisableClientState(GL_TEXTURE_COORD_ARRAY); arrpush(deferred_commands, final_command);
glDisableClientState(GL_VERTEX_ARRAY);
if (!batch.constant_colored)
glDisableClientState(GL_COLOR_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
} }
size_t get_sprite_payload_size(struct SpriteBatch batch) { size_t get_quad_payload_size(struct QuadBatch batch) {
if (batch.constant_colored) if (batch.constant_colored && batch.textured)
return sizeof (ElementIndexedQuadWithoutColor); return sizeof (ElementIndexedQuadWithoutColor);
else else if (!batch.constant_colored && batch.textured)
return sizeof (ElementIndexedQuad); return sizeof (ElementIndexedQuad);
else if (batch.constant_colored && !batch.textured)
return sizeof (ElementIndexedQuadWithoutTexture);
else if (!batch.constant_colored && !batch.textured)
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
SDL_assert(false);
return 0;
} }
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch, bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
VertexBufferBuilder *builder, VertexBufferBuilder *builder,
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
Color color) Color color)
{ {
if (!batch.constant_colored) { if (!batch.constant_colored && batch.textured) {
ElementIndexedQuad buffer_element = { ElementIndexedQuad const buffer_element = {
.v0 = v0, .v0 = v0,
.v1 = v1, .v1 = v1,
.v2 = v2, .v2 = v2,
@ -375,15 +677,15 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
/* equal for all (flat shaded) */ /* equal for all (flat shaded) */
.c0 = color, .c0 = color,
.c1 = color, // .c1 = color,
.c2 = color, .c2 = color,
.c3 = color, // .c3 = color,
}; };
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else { } else if (batch.constant_colored && batch.textured) {
ElementIndexedQuadWithoutColor buffer_element = { ElementIndexedQuadWithoutColor const buffer_element = {
.v0 = v0, .v0 = v0,
.v1 = v1, .v1 = v1,
.v2 = v2, .v2 = v2,
@ -395,8 +697,37 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
.uv3 = uv3, .uv3 = uv3,
}; };
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else if (!batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutTexture const buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
/* equal for all (flat shaded) */
.c0 = color,
// .c1 = color,
.c2 = color,
// .c3 = color,
};
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else if (batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
};
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} }
SDL_assert(false);
return false;
} }
@ -465,44 +796,46 @@ void finally_draw_text(FontData const *font_data,
Color color, Color color,
VertexBuffer buffer) VertexBuffer buffer)
{ {
(void)buffer; DeferredCommandDraw command = {0};
/* vertex specification */ command.vertices = (AttributeArrayPointer) {
glEnableClientState(GL_VERTEX_ARRAY); .arity = 2,
glVertexPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, v0),
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0)); .buffer = buffer
};
glEnableClientState(GL_TEXTURE_COORD_ARRAY); command.texcoords = (AttributeArrayPointer) {
glClientActiveTexture(GL_TEXTURE0); .arity = 2,
glTexCoordPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0)); .buffer = buffer
};
bind_quad_element_buffer(); command.constant_colored = true;
command.color = color;
command.gpu_texture = font_data->texture;
command.uses_gpu_key = true;
command.textured = true;
command.element_buffer = get_quad_element_buffer();
command.element_count = 6 * (GLsizei)len;
command.range_end = 6 * (GLsizei)len;
use_texture_mode(TEXTURE_MODE_GHOSTLY); use_texture_mode(TEXTURE_MODE_GHOSTLY);
glBindTexture(GL_TEXTURE_2D, font_data->texture); DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
glColor4ub(color.r, color.g, color.b, color.a); arrpush(deferred_commands, final_command);
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)len, GL_UNSIGNED_SHORT, NULL);
/* clear the state */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
/* TODO: why doesn't it get restored if not placed here? */ /* TODO: why doesn't it get restored if not placed here? */
glDepthMask(GL_TRUE); // glDepthMask(GL_TRUE);
} }
@ -531,6 +864,47 @@ static void load_cubemap_side(const char *path, GLenum target) {
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
} }
void render_circle(const CirclePrimitive *circle) {
static Vec2 vertices[CIRCLE_VERTICES_MAX];
int num_vertices = CIRCLE_VERTICES_MAX - 1;
create_circle_geometry(circle->position,
circle->radius,
num_vertices,
vertices);
VertexBuffer buffer = get_scratch_vertex_array();
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
DeferredCommandDraw command = {0};
command.vertices = (AttributeArrayPointer) {
.arity = 2,
.type = GL_FLOAT,
.stride = sizeof (Vec2),
.offset = 0,
.buffer = buffer
};
command.constant_colored = true;
command.color = circle->color;
command.element_buffer = get_circle_element_buffer();
command.element_count = num_vertices * 3;
command.range_end = num_vertices * 3;
use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY);
DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
arrpush(deferred_commands, final_command);
}
void finally_render_skybox(char *paths) { void finally_render_skybox(char *paths) {
static GLuint cubemap = 0; static GLuint cubemap = 0;
static char *paths_cache = NULL; static char *paths_cache = NULL;
@ -697,3 +1071,16 @@ void finally_apply_fog(float start, float end, float density, Color color) {
void finally_pop_fog(void) { void finally_pop_fog(void) {
glDisable(GL_FOG); glDisable(GL_FOG);
} }
void set_depth_range(double low, double high) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
.depth_range = {
.low = low,
.high = high
}
};
arrpush(deferred_commands, command);
}

View File

@ -26,65 +26,66 @@ void delete_vertex_buffer(VertexBuffer buffer) {
} }
void specify_vertex_buffer(VertexBuffer buffer, void *data, size_t bytes) { void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes) {
glBindBuffer(GL_ARRAY_BUFFER, buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW); glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
} }
void bind_quad_element_buffer(void) { VertexBuffer get_quad_element_buffer(void) {
static GLuint buffer = 0; static VertexBuffer buffer = 0;
/* it's only generated once at runtime */ /* it's only generated once at runtime */
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
if (buffer == 0) { if (buffer == 0) {
glGenBuffers(1, &buffer); buffer = create_vertex_buffer();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
NULL,
GL_STATIC_DRAW);
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
GL_WRITE_ONLY); GLshort indices[6];
if (!indices) indices[0] = (GLshort)(i * 4 + 0);
CRY("Quad indices generation", "glMapBuffer() failed"); indices[1] = (GLshort)(i * 4 + 1);
indices[2] = (GLshort)(i * 4 + 2);
indices[3] = (GLshort)(i * 4 + 2);
indices[4] = (GLshort)(i * 4 + 3);
indices[5] = (GLshort)(i * 4 + 0);
for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
indices[i * 6 + 0] = (uint16_t)(i * 4 + 0);
indices[i * 6 + 1] = (uint16_t)(i * 4 + 1);
indices[i * 6 + 2] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 3] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 4] = (uint16_t)(i * 4 + 3);
indices[i * 6 + 5] = (uint16_t)(i * 4 + 0);
} }
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); SDL_assert_always(buffer);
} else return buffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
} }
void clear_draw_buffer(void) { VertexBuffer get_circle_element_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/ static VertexBuffer buffer = 0;
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
/* TODO: don't clear color when skybox is applied? */ if (buffer == 0) {
/* for that window should match framebuffer */ buffer = create_vertex_buffer();
/* also it is driver dependent, from what i can gather */ VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT | for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
GL_STENCIL_BUFFER_BIT); /* first one is center point index, always zero */
} GLshort indices[3];
indices[0] = 0;
void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window); /* generated point index */
} indices[1] = (GLshort)i;
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
void set_depth_range(double low, double high) { if (index == 0) /* don't use center for outer ring */
glDepthRange(low, high); index = (CIRCLE_VERTICES_MAX - 1);
indices[2] = (GLshort)index;
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
}
}
SDL_assert_always(buffer);
return buffer;
} }

30
src/rendering/twn_quads.c Normal file
View File

@ -0,0 +1,30 @@
#include "twn_draw_c.h"
#include <stddef.h>
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
return collect_sprite_batch(primitives, len);
else if (primitives[0].type == PRIMITIVE_2D_RECT)
return collect_rect_batch(primitives, len);
else
SDL_assert(false);
return (struct QuadBatch){0};
}
/* assumes that orthogonal matrix setup is done already */
void render_quad_batch(const Primitive2D primitives[],
const struct QuadBatch batch)
{
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
render_sprite_batch(primitives, batch);
else if (primitives[0].type == PRIMITIVE_2D_RECT)
render_rect_batch(primitives, batch);
else
SDL_assert(false);
return (struct QuadBatch){0};
}

102
src/rendering/twn_rects.c Normal file
View File

@ -0,0 +1,102 @@
#include "twn_draw.h"
#include "twn_draw_c.h"
#include "twn_engine_context_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_textures_c.h"
#include "twn_option.h"
#include <stb_ds.h>
#include <stdbool.h>
#include <stddef.h>
void draw_rectangle(Rect rect, Color color) {
RectPrimitive rectangle = {
.rect = rect,
.color = color,
};
Primitive2D primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
}
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len) {
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
SDL_assert(primitives && len != 0);
struct QuadBatch batch = {
.mode = primitives[0].rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY,
.constant_colored = true,
};
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
/* batch size is clamped so that reallocated short indices could be used */
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
len = QUAD_ELEMENT_BUFFER_LENGTH;
for (size_t i = 0; i < len; ++i) {
const Primitive2D *const current = &primitives[i];
/* don't touch things other than rectangles */
if (current->type != PRIMITIVE_2D_RECT)
break;
/* only collect the same blend modes */
if ((current->rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY) != batch.mode)
break;
/* if all are modulated the same we can skip sending the color data */
if (*(const uint32_t *)&current->rect.color != uniform_color)
batch.constant_colored = false;
++batch.size;
}
return batch;
}
/* assumes that orthogonal matrix setup is done already */
void render_rect_batch(const Primitive2D primitives[],
const struct QuadBatch batch)
{
SDL_assert(primitives && batch.size != 0);
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
VertexBuffer const vertex_array = get_scratch_vertex_array();
use_texture_mode(batch.mode);
/* vertex population over a vertex buffer builder interface */
{
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back, to gain benefit of an early z rejection */
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const RectPrimitive rect = primitives[cur].rect;
Vec2 v0 = { rect.rect.x, rect.rect.y };
Vec2 v1 = { rect.rect.x, rect.rect.y + rect.rect.h };
Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h };
Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
push_quad_payload_to_vertex_buffer_builder(
batch, &payload,
v0, v1, v2, v3,
(Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
rect.color);
}
}
finally_render_quads(primitives, batch, vertex_array);
}

View File

@ -60,15 +60,18 @@ void draw_sprite_args(const DrawSpriteArgs args) {
} }
struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) { struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
/* assumes that first primitive is already a sprite */ SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
SDL_assert(primitives && len != 0);
const uint16_t texture_key_id = primitives[0].sprite.texture_key.id; const uint16_t texture_key_id = primitives[0].sprite.texture_key.id;
const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key); const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key);
struct SpriteBatch batch = { struct QuadBatch batch = {
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key), .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
.constant_colored = true, .constant_colored = true,
.repeat = primitives[0].sprite.repeat, .repeat = primitives[0].sprite.repeat,
.textured = true,
}; };
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color; const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
@ -116,35 +119,40 @@ struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t l
/* assumes that orthogonal matrix setup is done already */ /* assumes that orthogonal matrix setup is done already */
void render_sprites(const Primitive2D primitives[], void render_sprite_batch(const Primitive2D primitives[],
const struct SpriteBatch batch) const struct QuadBatch batch)
{ {
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */ SDL_assert(primitives && batch.size != 0);
static VertexBuffer vertex_array = 0; SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode); VertexBuffer const vertex_array = get_scratch_vertex_array();
const Rect dims = const Rect dims =
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key); textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
/* cached srcrect */
Rect cached_srcrect;
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
/* vertex population over a vertex buffer builder interface */ /* vertex population over a vertex buffer builder interface */
{ {
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_sprite_payload_size(batch) * batch.size); VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
for (size_t i = 0; i < batch.size; ++i) { for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back */ /* render opaques front to back, to gain benefit of an early z rejection */
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1; const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const SpritePrimitive sprite = primitives[cur].sprite; const SpritePrimitive sprite = primitives[cur].sprite;
/* TODO: try caching it */ if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) {
const Rect srcrect = cached_srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key); cached_srcrect_key = primitives[cur].sprite.texture_key;
}
Rect const srcrect = cached_srcrect;
Vec2 uv0, uv1, uv2, uv3; Vec2 uv0, uv1, uv2, uv3;
if (!sprite.repeat) { if (!batch.repeat) {
const float wr = srcrect.w / dims.w; const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h; const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w; const float xr = srcrect.x / dims.w;
@ -233,9 +241,9 @@ void render_sprites(const Primitive2D primitives[],
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y }; v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
} }
push_sprite_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color); push_quad_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
} }
} }
finally_render_sprites(primitives, batch, vertex_array); finally_render_quads(primitives, batch, vertex_array);
} }

View File

@ -31,6 +31,19 @@ typedef struct StringArena {
static StringArena string_arena; static StringArena string_arena;
typedef struct FontFileBuffer {
size_t len;
unsigned char *buffer;
} FontFileBuffer;
typedef struct FontFileCacheItem {
char *key;
FontFileBuffer value;
} FontFileCacheItem;
static FontFileCacheItem *font_file_cache_hash;
static void string_arena_init(StringArena *arena) { static void string_arena_init(StringArena *arena) {
arena->head = cmalloc(sizeof *arena->head); arena->head = cmalloc(sizeof *arena->head);
arena->current_block = arena->head; arena->current_block = arena->head;
@ -103,12 +116,22 @@ static FontData *text_load_font_data(const char *path, int height_px) {
{ {
unsigned char *buf = NULL; unsigned char *buf = NULL;
int64_t buf_len = file_to_bytes(path, &buf); int64_t buf_len = 0;
/* if the file was already loaded just get it */
FontFileCacheItem *font_file_ptr = shgetp_null(font_file_cache_hash, path);
if (font_file_ptr != NULL) {
buf = font_file_ptr->value.buffer;
buf_len = font_file_ptr->value.len;
} else {
buf_len = file_to_bytes(path, &buf);
FontFileBuffer buffer = { buf_len, buf };
shput(font_file_cache_hash, path, buffer);
}
stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0)); stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
/* might as well get these now, for later */ /* might as well get these now, for later */
font_data->file_bytes = buf;
font_data->file_bytes_len = buf_len;
font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px); font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px);
stbtt_GetFontVMetrics( stbtt_GetFontVMetrics(
&font_data->info, &font_data->info,
@ -142,16 +165,13 @@ static FontData *text_load_font_data(const char *path, int height_px) {
static void text_destroy_font_data(FontData *font_data) { static void text_destroy_font_data(FontData *font_data) {
SDL_free(font_data->file_bytes);
delete_gpu_texture(font_data->texture); delete_gpu_texture(font_data->texture);
SDL_free(font_data); SDL_free(font_data);
} }
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) { static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
VertexBuffer vertex_array = 0; VertexBuffer const vertex_array = get_scratch_vertex_array();
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t len = SDL_strlen(text); const size_t len = SDL_strlen(text);
@ -192,7 +212,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
static void ensure_font_cache(const char *font_path, int height_px) { static void ensure_font_cache(const char *font_path, int height_px) {
/* HACK: stupid, bad, don't do this */ /* HACK: don't */
bool is_cached = false; bool is_cached = false;
for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
FontData *font_data = ctx.text_cache.data[i]; FontData *font_data = ctx.text_cache.data[i];
@ -229,6 +249,8 @@ void render_text(const TextPrimitive *text) {
void text_cache_init(TextCache *cache) { void text_cache_init(TextCache *cache) {
arrsetlen(cache->data, 0); arrsetlen(cache->data, 0);
string_arena_init(&string_arena); string_arena_init(&string_arena);
sh_new_arena(font_file_cache_hash);
} }
@ -237,8 +259,13 @@ void text_cache_deinit(TextCache *cache) {
text_destroy_font_data(ctx.text_cache.data[i]); text_destroy_font_data(ctx.text_cache.data[i]);
} }
arrfree(cache->data); for (size_t i = 0; i < shlenu(font_file_cache_hash); ++i) {
SDL_free(font_file_cache_hash[i].value.buffer);
}
shfree(font_file_cache_hash);
string_arena_deinit(&string_arena); string_arena_deinit(&string_arena);
arrfree(cache->data);
} }

View File

@ -22,8 +22,6 @@ typedef struct FontData {
stbtt_fontinfo info; stbtt_fontinfo info;
const char *file_path; const char *file_path;
unsigned char *file_bytes;
size_t file_bytes_len;
GPUTexture texture; GPUTexture texture;

View File

@ -45,9 +45,7 @@ void draw_triangle(const char *path,
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch, void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
TextureKey texture_key) TextureKey texture_key)
{ {
static VertexBuffer vertex_array = 0; VertexBuffer const vertex_array = get_scratch_vertex_array();
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t primitives_len = arrlenu(batch->primitives); const size_t primitives_len = arrlenu(batch->primitives);

View File

@ -15,5 +15,6 @@
#include "rendering/twn_fog.c" #include "rendering/twn_fog.c"
#include "rendering/twn_skybox.c" #include "rendering/twn_skybox.c"
#include "rendering/twn_sprites.c" #include "rendering/twn_sprites.c"
#include "rendering/twn_rects.c"
#include "rendering/twn_text.c" #include "rendering/twn_text.c"
#include "rendering/twn_triangles.c" #include "rendering/twn_triangles.c"

View File

@ -77,6 +77,7 @@ typedef struct EngineContext {
bool window_size_has_changed; bool window_size_has_changed;
bool resync_flag; bool resync_flag;
bool was_successful; bool was_successful;
bool render_double_buffered;
} EngineContext; } EngineContext;
/* TODO: does it need to be marked with TWN_API? */ /* TODO: does it need to be marked with TWN_API? */

View File

@ -658,6 +658,8 @@ static bool initialize(void) {
} }
*/ */
ctx.render_double_buffered = true;
return true; return true;
fail: fail:

View File

@ -50,6 +50,7 @@ typedef struct TextureCache {
typedef struct TextureKey { uint16_t id; } TextureKey; typedef struct TextureKey { uint16_t id; } TextureKey;
/* tests whether given key structure corresponds to any texture */ /* tests whether given key structure corresponds to any texture */
#define TEXTURE_KEY_INVALID (TextureKey) { (uint16_t)-1 }
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1) #define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
void textures_cache_init(struct TextureCache *cache, SDL_Window *window); void textures_cache_init(struct TextureCache *cache, SDL_Window *window);

View File

@ -39,7 +39,7 @@ static inline float fast_sqrt(float x)
static inline Vec2 fast_cossine(float a) { static inline Vec2 fast_cossine(float a) {
const float s = SDL_sinf(a); const float s = sinf(a);
return (Vec2){ return (Vec2){
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1), .x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
.y = s .y = s