replace categorized, sorted render queues with a single ordered 2d queue

This commit is contained in:
wanp 2024-07-15 23:31:54 -03:00
parent 5ae59b51d3
commit bdf2a54107
5 changed files with 71 additions and 123 deletions

View File

@ -20,9 +20,7 @@ typedef struct context {
struct texture_cache texture_cache; struct texture_cache texture_cache;
struct input_state input; struct input_state input;
struct sprite_primitive *render_queue_sprites; struct primitive_2d *render_queue_2d;
struct rect_primitive *render_queue_rectangles;
struct circle_primitive *render_queue_circles;
struct mesh_batch_item *uncolored_mesh_batches; struct mesh_batch_item *uncolored_mesh_batches;
struct audio_channel_item *audio_channels; struct audio_channel_item *audio_channels;
@ -38,7 +36,7 @@ typedef struct context {
int64_t prev_frame_time; int64_t prev_frame_time;
int64_t desired_frametime; /* how long one tick should be */ int64_t desired_frametime; /* how long one tick should be */
int64_t frame_accumulator; int64_t frame_accumulator;
int64_t delta_averager_residual; int64_t delta_averager_residual;
int64_t time_averager[4]; int64_t time_averager[4];
int64_t delta_time; /* preserves real time frame delta with no manipilation */ int64_t delta_time; /* preserves real time frame delta with no manipilation */
uint64_t tick_count; uint64_t tick_count;

View File

@ -28,7 +28,7 @@ static void poll_events(void) {
case SDL_QUIT: case SDL_QUIT:
ctx.is_running = false; ctx.is_running = false;
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
if (e.window.windowID != ctx.window_id) if (e.window.windowID != ctx.window_id)
break; break;
@ -72,7 +72,7 @@ void main_loop(void) {
emscripten_cancel_main_loop(); emscripten_cancel_main_loop();
} }
*/ */
/* frame timer */ /* frame timer */
int64_t current_frame_time = SDL_GetPerformanceCounter(); int64_t current_frame_time = SDL_GetPerformanceCounter();
int64_t delta_time = current_frame_time - ctx.prev_frame_time; int64_t delta_time = current_frame_time - ctx.prev_frame_time;
@ -130,7 +130,7 @@ void main_loop(void) {
ctx.frame_accumulator += delta_time; ctx.frame_accumulator += delta_time;
/* spiral of death protection */ /* spiral of death protection */
if (ctx.frame_accumulator > ctx.desired_frametime * 8) { if (ctx.frame_accumulator > ctx.desired_frametime * 8) {
ctx.resync_flag = true; ctx.resync_flag = true;
} }
@ -203,7 +203,7 @@ static bool initialize(void) {
/* might need this to have multiple windows */ /* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window); ctx.window_id = SDL_GetWindowID(ctx.window);
glViewport(0, 0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT); glViewport(0, 0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT);
/* TODO: */ /* TODO: */
@ -238,14 +238,14 @@ static bool initialize(void) {
} }
/* filesystem time */ /* filesystem time */
/* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */ /* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
if (!PHYSFS_init(ctx.argv[0]) || if (!PHYSFS_init(ctx.argv[0]) ||
!PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true)) !PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true))
{ {
CRY_PHYSFS("Filesystem initialization failed."); CRY_PHYSFS("Filesystem initialization failed.");
goto fail; goto fail;
} }
/* you could change this at runtime if you wanted */ /* you could change this at runtime if you wanted */
ctx.update_multiplicity = 1; ctx.update_multiplicity = 1;
@ -287,9 +287,7 @@ static bool initialize(void) {
/* rendering */ /* rendering */
/* these are dynamic arrays and will be allocated lazily by stb_ds */ /* these are dynamic arrays and will be allocated lazily by stb_ds */
ctx.render_queue_sprites = NULL; ctx.render_queue_2d = NULL;
ctx.render_queue_rectangles = NULL;
ctx.render_queue_circles = NULL;
ctx.circle_radius_hash = NULL; ctx.circle_radius_hash = NULL;
textures_cache_init(&ctx.texture_cache, ctx.window); textures_cache_init(&ctx.texture_cache, ctx.window);
@ -324,8 +322,7 @@ static void clean_up(void) {
input_state_deinit(&ctx.input); input_state_deinit(&ctx.input);
arrfree(ctx.render_queue_sprites); arrfree(ctx.render_queue_2d);
arrfree(ctx.render_queue_rectangles);
textures_cache_deinit(&ctx.texture_cache); textures_cache_deinit(&ctx.texture_cache);
PHYSFS_deinit(); PHYSFS_deinit();

View File

@ -16,7 +16,6 @@ struct sprite_primitive {
double rotation; double rotation;
SDL_BlendMode blend_mode; SDL_BlendMode blend_mode;
t_texture_key texture_key; t_texture_key texture_key;
int layer;
bool flip_x; bool flip_x;
bool flip_y; bool flip_y;
}; };
@ -32,6 +31,22 @@ struct circle_primitive {
t_fvec2 position; t_fvec2 position;
}; };
enum primitive_2d_type {
PRIMITIVE_2D_SPRITE,
PRIMITIVE_2D_RECT,
PRIMITIVE_2D_CIRCLE,
};
struct primitive_2d {
enum primitive_2d_type type;
union {
struct sprite_primitive sprite;
struct rect_primitive rect;
struct circle_primitive circle;
};
};
/* union for in-place recalculation of texture coordinates */ /* union for in-place recalculation of texture coordinates */
union uncolored_space_triangle { union uncolored_space_triangle {
/* pending for sending, uvs are not final as texture atlases could update */ /* pending for sending, uvs are not final as texture atlases could update */

View File

@ -16,9 +16,7 @@ void render_queue_clear(void) {
/* since i don't intend to free the queues, */ /* since i don't intend to free the queues, */
/* it's faster and simpler to just "start over" */ /* it's faster and simpler to just "start over" */
/* and start overwriting the existing data */ /* and start overwriting the existing data */
arrsetlen(ctx.render_queue_sprites, 0); arrsetlen(ctx.render_queue_2d, 0);
arrsetlen(ctx.render_queue_rectangles, 0);
arrsetlen(ctx.render_queue_circles, 0);
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0); arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
@ -31,7 +29,6 @@ void render_queue_clear(void) {
* because they will be called multiple times in the main loop * because they will be called multiple times in the main loop
* before anything is really rendered * before anything is really rendered
*/ */
/* sprite */ /* sprite */
void push_sprite(char *path, t_frect rect) { void push_sprite(char *path, t_frect rect) {
struct sprite_primitive sprite = { struct sprite_primitive sprite = {
@ -40,12 +37,16 @@ void push_sprite(char *path, t_frect rect) {
.rotation = 0.0, .rotation = 0.0,
.blend_mode = SDL_BLENDMODE_BLEND, .blend_mode = SDL_BLENDMODE_BLEND,
.texture_key = textures_get_key(&ctx.texture_cache, path), .texture_key = textures_get_key(&ctx.texture_cache, path),
.layer = 0,
.flip_x = false, .flip_x = false,
.flip_y = false, .flip_y = false,
}; };
arrput(ctx.render_queue_sprites, sprite); struct primitive_2d primitive = {
.type = PRIMITIVE_2D_SPRITE,
.sprite = sprite,
};
arrput(ctx.render_queue_2d, primitive);
} }
@ -55,13 +56,17 @@ void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
.color = args.color, .color = args.color,
.rotation = args.rotation, .rotation = args.rotation,
.blend_mode = args.blend_mode, .blend_mode = args.blend_mode,
.texture_key = textures_get_key(&ctx.texture_cache, args.path), .texture_key = textures_get_key(&ctx.texture_cache, args.path),
.layer = args.layer,
.flip_x = args.flip_x, .flip_x = args.flip_x,
.flip_y = args.flip_y, .flip_y = args.flip_y,
}; };
arrput(ctx.render_queue_sprites, sprite); struct primitive_2d primitive = {
.type = PRIMITIVE_2D_SPRITE,
.sprite = sprite,
};
arrput(ctx.render_queue_2d, primitive);
} }
@ -72,7 +77,12 @@ void push_rectangle(t_frect rect, t_color color) {
.color = color, .color = color,
}; };
arrput(ctx.render_queue_rectangles, rectangle); struct primitive_2d primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
} }
@ -84,7 +94,12 @@ void push_circle(t_fvec2 position, float radius, t_color color) {
.position = position, .position = position,
}; };
arrput(ctx.render_queue_circles, circle); struct primitive_2d primitive = {
.type = PRIMITIVE_2D_CIRCLE,
.circle = circle,
};
arrput(ctx.render_queue_2d, primitive);
} }
@ -123,76 +138,6 @@ void unfurl_triangle(const char *path,
} }
/* 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)->texture_key.id;
int index_b = ((const struct sprite_primitive *)b)->texture_key.id;
return (index_a > index_b) - (index_a < index_b);
}
static int cmp_blend_modes(const void *a, const void *b) {
SDL_BlendMode mode_a = ((const struct sprite_primitive *)a)->blend_mode;
SDL_BlendMode mode_b = ((const struct sprite_primitive *)b)->blend_mode;
return (mode_a > mode_b) - (mode_a < mode_b);
}
static int cmp_colors(const void *a, const void *b) {
t_color color_a = ((const struct sprite_primitive *)a)->color;
t_color color_b = ((const struct sprite_primitive *)b)->color;
/* check reds */
if (color_a.r < color_b.r)
return -1;
else if (color_a.r > color_b.r)
return 1;
/* reds were equal, check greens */
else if (color_a.g < color_b.g)
return -1;
else if (color_a.g > color_b.g)
return 1;
/* greens were equal, check blues */
else if (color_a.b < color_b.b)
return -1;
else if (color_a.b > color_b.b)
return 1;
/* blues were equal, check alphas */
else if (color_a.a < color_b.a)
return -1;
else if (color_a.a > color_b.a)
return 1;
/* entirely equal */
else
return 0;
}
static int cmp_layers(const void *a, const void *b) {
int layer_a = ((const struct sprite_primitive *)a)->layer;
int layer_b = ((const struct sprite_primitive *)b)->layer;
return (layer_a > layer_b) - (layer_a < layer_b);
}
/* TODO: not that we're SDL free we need to implement batching ourselves */
/* necessary to allow the renderer to draw in batches in the best case */
static void sort_sprites(struct sprite_primitive *sprites) {
size_t sprites_len = arrlenu(sprites);
qsort(sprites, sprites_len, sizeof *sprites, cmp_atlases);
qsort(sprites, sprites_len, sizeof *sprites, cmp_blend_modes);
qsort(sprites, sprites_len, sizeof *sprites, cmp_colors);
qsort(sprites, sprites_len, sizeof *sprites, cmp_layers);
}
static void upload_quad_vertices(t_frect rect) { static void upload_quad_vertices(t_frect rect) {
/* client memory needs to be reachable on glDraw*, so */ /* client memory needs to be reachable on glDraw*, so */
static float vertices[6 * 2]; static float vertices[6 * 2];
@ -277,7 +222,7 @@ static void create_circle_geometry(t_fvec2 position,
/* 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].position.x = (float)position.x;
vertices[0].position.y = (float)position.y; vertices[0].position.y = (float)position.y;
vertices[0].color.r = color.r; vertices[0].color.r = color.r;
@ -366,25 +311,21 @@ static void render_circle(struct circle_primitive *circle) {
} }
static void render_sprites(void) { static void render_2d(void) {
sort_sprites(ctx.render_queue_sprites); for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) {
struct primitive_2d *current = &ctx.render_queue_2d[i];
for (size_t i = 0; i < arrlenu(ctx.render_queue_sprites); ++i) { switch (current->type) {
render_sprite(&ctx.render_queue_sprites[i]); case PRIMITIVE_2D_SPRITE:
} render_sprite(&current->sprite);
} break;
case PRIMITIVE_2D_RECT:
render_rectangle(&current->rect);
static void render_rectangles(void) { break;
for (size_t i = 0; i < arrlenu(ctx.render_queue_rectangles); ++i) { case PRIMITIVE_2D_CIRCLE:
render_rectangle(&ctx.render_queue_rectangles[i]); render_circle(&current->circle);
} break;
} }
static void render_circles(void) {
for (size_t i = 0; i < arrlenu(ctx.render_queue_circles); ++i) {
render_circle(&ctx.render_queue_circles[i]);
} }
} }
@ -516,7 +457,7 @@ void render(void) {
glDepthFunc(GL_ALWAYS); /* fill depth buffer with ones, 2d view is always in front */ glDepthFunc(GL_ALWAYS); /* fill depth buffer with ones, 2d view is always in front */
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1); glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
@ -525,10 +466,8 @@ void render(void) {
glLoadIdentity(); glLoadIdentity();
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
render_sprites(); render_2d();
render_rectangles();
render_circles();
} }
{ {
@ -537,7 +476,7 @@ void render(void) {
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glDepthFunc(GL_LESS); glDepthFunc(GL_LESS);
glEnable(GL_BLEND); glEnable(GL_BLEND);

View File

@ -9,7 +9,6 @@
typedef struct push_sprite_args { typedef struct push_sprite_args {
char *path; char *path;
int layer;
t_color color; t_color color;
double rotation; double rotation;
SDL_BlendMode blend_mode; SDL_BlendMode blend_mode;