partial implementation of double buffered render

This commit is contained in:
veclav talica 2024-10-15 15:29:45 +03:00
parent 446402c2e0
commit 139394c6de
10 changed files with 294 additions and 96 deletions

View File

@ -374,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

@ -165,6 +165,8 @@ 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 const *data, size_t bytes); void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
@ -187,7 +189,7 @@ 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);
void render_circle(const CirclePrimitive *circle); void render_circle(const CirclePrimitive *circle);
@ -238,4 +240,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

@ -95,6 +95,58 @@ typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
} ElementIndexedQuadWithoutColorWithoutTexture; } 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;
TextureKey texture;
TextureMode texture_mode;
GLuint element_buffer;
GLsizei element_count;
GLsizei range_start, range_end;
} DeferredCommandDrawElements;
typedef struct {
Color color;
bool clear_color;
bool clear_depth;
bool clear_stencil;
} DeferredCommandClear;
typedef struct {
enum DeferredCommandType {
DEFERRED_COMMAND_TYPE_DRAW_ELEMENTS,
DEFERRED_COMMAND_TYPE_CLEAR,
} type;
union {
DeferredCommandDrawElements draw_elements;
DeferredCommandClear clear;
};
} DeferredCommand;
typedef enum { typedef enum {
PIPELINE_NO, PIPELINE_NO,
PIPELINE_SPACE, PIPELINE_SPACE,
@ -108,6 +160,186 @@ static Pipeline pipeline_last_used = PIPELINE_NO;
#define CIRCLE_VERTICES_MAX 2048 #define CIRCLE_VERTICES_MAX 2048
/* 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)[arrlenu(*current_scratch_vertex_array) - 1];
}
static DeferredCommand *deferred_commands;
static void issue_deferred_draw_commands(void) {
use_2d_pipeline();
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
switch (deferred_commands[i].type) {
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_ELEMENTS: {
DeferredCommandDrawElements const command = deferred_commands[i].draw_elements;
/* 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);
use_texture_mode(command.texture_mode);
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.texture_repeat)
textures_bind_repeating(&ctx.texture_cache, command.texture);
else
textures_bind(&ctx.texture_cache, command.texture);
}
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;
}
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) {
restart_scratch_vertex_arrays();
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);
} 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();
glFlush();
arrsetlen(deferred_commands, 0);
}
}
void use_space_pipeline(void) { void use_space_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) if (pipeline_last_used == PIPELINE_SPACE)
return; return;
@ -301,7 +533,7 @@ void finally_render_quads(const Primitive2D primitives[],
const struct QuadBatch batch, const struct QuadBatch batch,
const VertexBuffer buffer) const VertexBuffer buffer)
{ {
(void)buffer; DeferredCommandDrawElements command = {0};
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0; GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
@ -323,63 +555,55 @@ void finally_render_quads(const Primitive2D primitives[],
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0); 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
};
if (batch.textured) { if (batch.textured)
glEnableClientState(GL_TEXTURE_COORD_ARRAY); command.texcoords = (AttributeArrayPointer) {
glClientActiveTexture(GL_TEXTURE0); .arity = 2,
glTexCoordPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = off,
off, .offset = uvoff,
(void *)(size_t)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 *)(size_t)coff); .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.textured) { if (batch.textured) {
if (!batch.repeat) /* TODO: bad, don't */
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key); command.textured = true;
else command.texture = primitives->sprite.texture_key;
textures_bind_repeating(&ctx.texture_cache, 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); command.texture_mode = batch.mode;
/* clear the state */ DeferredCommand final_command = {
{ .type = DEFERRED_COMMAND_TYPE_DRAW_ELEMENTS,
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); .draw_elements = command
glBindBuffer(GL_ARRAY_BUFFER, 0); };
if (batch.textured) arrpush(deferred_commands, final_command);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
if (!batch.constant_colored)
glDisableClientState(GL_COLOR_ARRAY);
if (batch.textured)
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
} }
@ -553,7 +777,7 @@ void finally_draw_text(FontData const *font_data,
offsetof(ElementIndexedQuadWithoutColor, v1), offsetof(ElementIndexedQuadWithoutColor, v1),
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0)); (void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0));
bind_quad_element_buffer(); get_quad_element_buffer();
use_texture_mode(TEXTURE_MODE_GHOSTLY); use_texture_mode(TEXTURE_MODE_GHOSTLY);

View File

@ -32,7 +32,7 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes)
} }
void bind_quad_element_buffer(void) { VertexBuffer get_quad_element_buffer(void) {
static GLuint buffer = 0; static GLuint buffer = 0;
/* it's only generated once at runtime */ /* it's only generated once at runtime */
@ -62,32 +62,8 @@ void bind_quad_element_buffer(void) {
} else } else
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
}
return buffer;
void clear_draw_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glDepthRange(0.0, 1.0);
glDepthMask(GL_TRUE);
/* TODO: don't clear color when skybox is applied? */
/* for that window should match framebuffer */
/* also it is driver dependent, from what i can gather */
/* INFO: also, based on below, driver might prefer it staying this way */
/* https://gamedev.stackexchange.com/questions/90344/render-with-const-depth-value */
/* we could optionally load ARB_invalidate_subdata extension if it's available instead */
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
}
void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window);
} }

View File

@ -72,9 +72,7 @@ void render_rect_batch(const Primitive2D primitives[],
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT); SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */ /* single vertex array is used for every batch with NULL glBufferData() trick at the end */
static VertexBuffer vertex_array = 0; VertexBuffer const vertex_array = get_scratch_vertex_array();
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode); use_texture_mode(batch.mode);

View File

@ -125,12 +125,7 @@ void render_sprite_batch(const Primitive2D primitives[],
SDL_assert(primitives && batch.size != 0); SDL_assert(primitives && batch.size != 0);
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE); SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */ VertexBuffer const vertex_array = get_scratch_vertex_array();
static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode);
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);

View File

@ -171,9 +171,7 @@ static void text_destroy_font_data(FontData *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);

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

@ -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: