partial implementation of double buffered render
This commit is contained in:
parent
446402c2e0
commit
139394c6de
@ -374,11 +374,11 @@ void render(void) {
|
||||
}
|
||||
}
|
||||
|
||||
render_space();
|
||||
render_skybox(); /* after space, as to use depth buffer for early rejection */
|
||||
render_2d();
|
||||
swap_buffers();
|
||||
clear_draw_buffer();
|
||||
start_render_frame(); {
|
||||
render_space();
|
||||
render_skybox(); /* after space, as to use depth buffer for early z rejection */
|
||||
render_2d();
|
||||
} end_render_frame();
|
||||
}
|
||||
|
||||
|
||||
|
@ -165,6 +165,8 @@ void text_cache_reset_arena(TextCache *cache);
|
||||
|
||||
VertexBuffer create_vertex_buffer(void);
|
||||
|
||||
VertexBuffer get_scratch_vertex_array(void);
|
||||
|
||||
void delete_vertex_buffer(VertexBuffer buffer);
|
||||
|
||||
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 bind_quad_element_buffer(void);
|
||||
VertexBuffer get_quad_element_buffer(void);
|
||||
|
||||
void render_circle(const CirclePrimitive *circle);
|
||||
|
||||
@ -238,4 +240,8 @@ void pop_fog(void);
|
||||
|
||||
void finally_pop_fog(void);
|
||||
|
||||
void start_render_frame(void);
|
||||
|
||||
void end_render_frame(void);
|
||||
|
||||
#endif
|
||||
|
@ -95,6 +95,58 @@ typedef struct 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 {
|
||||
PIPELINE_NO,
|
||||
PIPELINE_SPACE,
|
||||
@ -108,6 +160,186 @@ static Pipeline pipeline_last_used = PIPELINE_NO;
|
||||
#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) {
|
||||
if (pipeline_last_used == PIPELINE_SPACE)
|
||||
return;
|
||||
@ -301,7 +533,7 @@ void finally_render_quads(const Primitive2D primitives[],
|
||||
const struct QuadBatch batch,
|
||||
const VertexBuffer buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
DeferredCommandDrawElements command = {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);
|
||||
}
|
||||
|
||||
/* vertex specification */
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2,
|
||||
GL_FLOAT,
|
||||
off,
|
||||
(void *)(size_t)voff);
|
||||
command.vertices = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = off,
|
||||
.offset = voff,
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
if (batch.textured) {
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glClientActiveTexture(GL_TEXTURE0);
|
||||
glTexCoordPointer(2,
|
||||
GL_FLOAT,
|
||||
off,
|
||||
(void *)(size_t)uvoff);
|
||||
}
|
||||
if (batch.textured)
|
||||
command.texcoords = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = off,
|
||||
.offset = uvoff,
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
if (!batch.constant_colored) {
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glColorPointer(4,
|
||||
GL_UNSIGNED_BYTE,
|
||||
off,
|
||||
(void *)(size_t)coff);
|
||||
} else
|
||||
glColor4ub(primitives[0].sprite.color.r,
|
||||
primitives[0].sprite.color.g,
|
||||
primitives[0].sprite.color.b,
|
||||
primitives[0].sprite.color.a);
|
||||
command.colors = (AttributeArrayPointer) {
|
||||
.arity = 4,
|
||||
.type = GL_UNSIGNED_BYTE,
|
||||
.stride = off,
|
||||
.offset = coff,
|
||||
.buffer = buffer
|
||||
};
|
||||
} else {
|
||||
command.constant_colored = true;
|
||||
command.color = primitives[0].sprite.color;
|
||||
}
|
||||
|
||||
if (batch.textured) {
|
||||
if (!batch.repeat)
|
||||
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
else
|
||||
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
/* TODO: bad, don't */
|
||||
command.textured = true;
|
||||
command.texture = 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 */
|
||||
{
|
||||
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
DeferredCommand final_command = {
|
||||
.type = DEFERRED_COMMAND_TYPE_DRAW_ELEMENTS,
|
||||
.draw_elements = command
|
||||
};
|
||||
|
||||
if (batch.textured)
|
||||
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);
|
||||
}
|
||||
arrpush(deferred_commands, final_command);
|
||||
}
|
||||
|
||||
|
||||
@ -553,7 +777,7 @@ void finally_draw_text(FontData const *font_data,
|
||||
offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0));
|
||||
|
||||
bind_quad_element_buffer();
|
||||
get_quad_element_buffer();
|
||||
|
||||
use_texture_mode(TEXTURE_MODE_GHOSTLY);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/* it's only generated once at runtime */
|
||||
@ -62,32 +62,8 @@ void bind_quad_element_buffer(void) {
|
||||
|
||||
} else
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,9 +72,7 @@ void render_rect_batch(const Primitive2D primitives[],
|
||||
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
|
||||
|
||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||
static VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
use_texture_mode(batch.mode);
|
||||
|
||||
|
@ -125,12 +125,7 @@ void render_sprite_batch(const Primitive2D primitives[],
|
||||
SDL_assert(primitives && batch.size != 0);
|
||||
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
|
||||
|
||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||
static VertexBuffer vertex_array = 0;
|
||||
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 =
|
||||
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
|
@ -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) {
|
||||
VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
const size_t len = SDL_strlen(text);
|
||||
|
||||
|
@ -45,9 +45,7 @@ void draw_triangle(const char *path,
|
||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
||||
TextureKey texture_key)
|
||||
{
|
||||
static VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
const size_t primitives_len = arrlenu(batch->primitives);
|
||||
|
||||
|
@ -77,6 +77,7 @@ typedef struct EngineContext {
|
||||
bool window_size_has_changed;
|
||||
bool resync_flag;
|
||||
bool was_successful;
|
||||
bool render_double_buffered;
|
||||
} EngineContext;
|
||||
|
||||
/* TODO: does it need to be marked with TWN_API? */
|
||||
|
@ -658,6 +658,8 @@ static bool initialize(void) {
|
||||
}
|
||||
*/
|
||||
|
||||
ctx.render_double_buffered = true;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
|
Loading…
Reference in New Issue
Block a user