1087 lines
32 KiB
C
1087 lines
32 KiB
C
#include "twn_draw_c.h"
|
|
#include "twn_util.h"
|
|
#include "twn_util_c.h"
|
|
#include "twn_engine_context_c.h"
|
|
#include "twn_text_c.h"
|
|
#include "twn_types.h"
|
|
|
|
#include <glad/glad.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 */
|
|
typedef struct ElementIndexedQuad {
|
|
/* upper-left */
|
|
Vec2 v0;
|
|
Vec2 uv0;
|
|
Color c0;
|
|
/* bottom-left */
|
|
Vec2 v1;
|
|
Vec2 uv1;
|
|
Color c1;
|
|
/* bottom-right */
|
|
Vec2 v2;
|
|
Vec2 uv2;
|
|
Color c2;
|
|
/* upper-right */
|
|
Vec2 v3;
|
|
Vec2 uv3;
|
|
Color c3;
|
|
} ElementIndexedQuad;
|
|
|
|
typedef struct ElementIndexedQuadWithoutColor {
|
|
/* upper-left */
|
|
Vec2 v0;
|
|
Vec2 uv0;
|
|
/* bottom-left */
|
|
Vec2 v1;
|
|
Vec2 uv1;
|
|
/* bottom-right */
|
|
Vec2 v2;
|
|
Vec2 uv2;
|
|
/* upper-right */
|
|
Vec2 v3;
|
|
Vec2 uv3;
|
|
} 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 {
|
|
PIPELINE_NO,
|
|
PIPELINE_SPACE,
|
|
PIPELINE_2D,
|
|
} 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;
|
|
|
|
|
|
/* 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) {
|
|
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)
|
|
return;
|
|
|
|
static GLuint list = 0;
|
|
if (!list) {
|
|
list = glGenLists(1);
|
|
|
|
glNewList(list, GL_COMPILE); {
|
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
|
glShadeModel(GL_SMOOTH);
|
|
|
|
if (GLAD_GL_ARB_depth_clamp)
|
|
glDisable(GL_DEPTH_CLAMP);
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
glDepthRange(0, 1);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
/* solid white, no modulation */
|
|
glColor4ub(255, 255, 255, 255);
|
|
} glEndList();
|
|
}
|
|
|
|
glCallList(list);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadMatrixf(&camera_projection_matrix.row[0].x);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
|
|
|
|
texture_mode_last_used = -1;
|
|
pipeline_last_used = PIPELINE_SPACE;
|
|
}
|
|
|
|
|
|
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) {
|
|
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
glFlush();
|
|
}
|
|
|
|
if (pipeline_last_used == PIPELINE_2D)
|
|
return;
|
|
|
|
static GLuint list = 0;
|
|
if (!list) {
|
|
list = glGenLists(1);
|
|
|
|
glNewList(list, GL_COMPILE); {
|
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
|
|
glShadeModel(GL_FLAT);
|
|
|
|
/* removes near/far plane comparison and discard */
|
|
if (GLAD_GL_ARB_depth_clamp)
|
|
glDisable(GL_DEPTH_CLAMP);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glDisable(GL_CULL_FACE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
} glEndList();
|
|
}
|
|
|
|
glCallList(list);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, 0, 1);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
texture_mode_last_used = -1;
|
|
pipeline_last_used = PIPELINE_2D;
|
|
}
|
|
|
|
|
|
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;
|
|
if (!lists) {
|
|
lists = glGenLists(3);
|
|
|
|
/* ghostly */
|
|
glNewList(lists + 0, GL_COMPILE); {
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glDepthFunc(GL_LESS);
|
|
glDepthMask(GL_FALSE);
|
|
glDisable(GL_ALPHA_TEST);
|
|
} glEndList();
|
|
|
|
/* seethrough */
|
|
glNewList(lists + 1, GL_COMPILE); {
|
|
glDisable(GL_BLEND);
|
|
glDepthFunc(GL_LESS);
|
|
glDepthMask(GL_TRUE);
|
|
glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_EQUAL, 1.0f);
|
|
} glEndList();
|
|
|
|
/* opaque */
|
|
glNewList(lists + 2, GL_COMPILE); {
|
|
glDisable(GL_BLEND);
|
|
glDepthFunc(GL_LESS);
|
|
glDepthMask(GL_TRUE);
|
|
glDisable(GL_ALPHA_TEST);
|
|
} glEndList();
|
|
}
|
|
|
|
if (mode == TEXTURE_MODE_GHOSTLY) {
|
|
glCallList(lists + 0);
|
|
} else if (mode == TEXTURE_MODE_SEETHROUGH) {
|
|
glCallList(lists + 1);
|
|
} else {
|
|
glCallList(lists + 2);
|
|
}
|
|
|
|
texture_mode_last_used = mode;
|
|
}
|
|
|
|
|
|
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
|
|
SDL_assert(bytes != 0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW);
|
|
void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
|
|
if (!mapping)
|
|
CRY("build_vertex_buffer", "Error mapping a vertex array buffer");
|
|
|
|
return (VertexBufferBuilder) {
|
|
.mapping = mapping,
|
|
.bytes_left = bytes,
|
|
};
|
|
}
|
|
|
|
|
|
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
|
void const *bytes, size_t size) {
|
|
if (builder->bytes_left == 0)
|
|
return false;
|
|
|
|
memcpy(builder->mapping, bytes, size);
|
|
builder->bytes_left -= size;
|
|
|
|
/* trigger data send */
|
|
if (builder->bytes_left == 0) {
|
|
glUnmapBuffer(GL_ARRAY_BUFFER);
|
|
return false;
|
|
}
|
|
|
|
builder->mapping = (void *)((uintptr_t)builder->mapping + size);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void finally_render_quads(const Primitive2D primitives[],
|
|
const struct QuadBatch batch,
|
|
const VertexBuffer buffer)
|
|
{
|
|
DeferredCommandDraw command = {0};
|
|
|
|
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
|
|
|
|
if (!batch.constant_colored && batch.textured) {
|
|
off = offsetof(ElementIndexedQuad, v1);
|
|
voff = offsetof(ElementIndexedQuad, v0);
|
|
uvoff = offsetof(ElementIndexedQuad, uv0);
|
|
coff = offsetof(ElementIndexedQuad, c0);
|
|
} else if (batch.constant_colored && batch.textured) {
|
|
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
|
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
|
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);
|
|
}
|
|
|
|
command.vertices = (AttributeArrayPointer) {
|
|
.arity = 2,
|
|
.type = GL_FLOAT,
|
|
.stride = off,
|
|
.offset = voff,
|
|
.buffer = buffer
|
|
};
|
|
|
|
if (batch.textured)
|
|
command.texcoords = (AttributeArrayPointer) {
|
|
.arity = 2,
|
|
.type = GL_FLOAT,
|
|
.stride = off,
|
|
.offset = uvoff,
|
|
.buffer = buffer
|
|
};
|
|
|
|
if (!batch.constant_colored) {
|
|
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) {
|
|
/* TODO: bad, don't */
|
|
command.textured = true;
|
|
command.texture_key = primitives->sprite.texture_key;
|
|
command.texture_repeat = batch.repeat;
|
|
}
|
|
|
|
command.element_buffer = get_quad_element_buffer();
|
|
command.element_count = 6 * (GLsizei)batch.size;
|
|
command.range_end = 6 * (GLsizei)batch.size;
|
|
|
|
use_texture_mode(batch.mode);
|
|
|
|
DeferredCommand final_command = {
|
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
.draw = command
|
|
};
|
|
|
|
arrpush(deferred_commands, final_command);
|
|
}
|
|
|
|
|
|
size_t get_quad_payload_size(struct QuadBatch batch) {
|
|
if (batch.constant_colored && batch.textured)
|
|
return sizeof (ElementIndexedQuadWithoutColor);
|
|
else if (!batch.constant_colored && batch.textured)
|
|
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_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
|
VertexBufferBuilder *builder,
|
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
|
Color color)
|
|
{
|
|
if (!batch.constant_colored && batch.textured) {
|
|
ElementIndexedQuad const buffer_element = {
|
|
.v0 = v0,
|
|
.v1 = v1,
|
|
.v2 = v2,
|
|
.v3 = v3,
|
|
|
|
.uv0 = uv0,
|
|
.uv1 = uv1,
|
|
.uv2 = uv2,
|
|
.uv3 = uv3,
|
|
|
|
/* 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) {
|
|
ElementIndexedQuadWithoutColor const buffer_element = {
|
|
.v0 = v0,
|
|
.v1 = v1,
|
|
.v2 = v2,
|
|
.v3 = v3,
|
|
|
|
.uv0 = uv0,
|
|
.uv1 = uv1,
|
|
.uv2 = uv2,
|
|
.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);
|
|
}
|
|
|
|
SDL_assert(false);
|
|
return false;
|
|
}
|
|
|
|
|
|
void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch,
|
|
const TextureKey texture_key,
|
|
const VertexBuffer buffer)
|
|
{
|
|
const size_t primitives_len = arrlenu(batch->primitives);
|
|
|
|
textures_bind(&ctx.texture_cache, texture_key);
|
|
|
|
use_texture_mode(textures_get_mode(&ctx.texture_cache, texture_key));
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
|
|
|
/* vertex specification*/
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glVertexPointer(3,
|
|
GL_FLOAT,
|
|
offsetof(struct UncoloredSpaceTrianglePayload, v1),
|
|
(void *)offsetof(struct UncoloredSpaceTrianglePayload, v0));
|
|
|
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glClientActiveTexture(GL_TEXTURE0);
|
|
glTexCoordPointer(2,
|
|
GL_FLOAT,
|
|
offsetof(struct UncoloredSpaceTrianglePayload, v1),
|
|
(void *)offsetof(struct UncoloredSpaceTrianglePayload, uv0));
|
|
|
|
/* commit for drawing */
|
|
glDrawArrays(GL_TRIANGLES, 0, 3 * (GLint)primitives_len);
|
|
|
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
|
/* invalidate the buffer immediately */
|
|
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
|
|
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
|
|
VertexBufferBuilder *builder,
|
|
stbtt_aligned_quad quad)
|
|
{
|
|
(void)font_data;
|
|
|
|
ElementIndexedQuadWithoutColor buffer_element = {
|
|
.v0 = (Vec2){ quad.x0, quad.y0 },
|
|
.v1 = (Vec2){ quad.x1, quad.y0 },
|
|
.v2 = (Vec2){ quad.x1, quad.y1 },
|
|
.v3 = (Vec2){ quad.x0, quad.y1 },
|
|
|
|
.uv0 = (Vec2){ quad.s0, quad.t0 },
|
|
.uv1 = (Vec2){ quad.s1, quad.t0 },
|
|
.uv2 = (Vec2){ quad.s1, quad.t1 },
|
|
.uv3 = (Vec2){ quad.s0, quad.t1 },
|
|
};
|
|
|
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
|
}
|
|
|
|
|
|
void finally_draw_text(FontData const *font_data,
|
|
size_t len,
|
|
Color color,
|
|
VertexBuffer buffer)
|
|
{
|
|
DeferredCommandDraw command = {0};
|
|
|
|
command.vertices = (AttributeArrayPointer) {
|
|
.arity = 2,
|
|
.type = GL_FLOAT,
|
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
|
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
|
.buffer = buffer
|
|
};
|
|
|
|
command.texcoords = (AttributeArrayPointer) {
|
|
.arity = 2,
|
|
.type = GL_FLOAT,
|
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
|
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
|
|
.buffer = 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);
|
|
|
|
DeferredCommand final_command = {
|
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
|
.draw = command
|
|
};
|
|
|
|
arrpush(deferred_commands, final_command);
|
|
|
|
/* TODO: why doesn't it get restored if not placed here? */
|
|
// glDepthMask(GL_TRUE);
|
|
}
|
|
|
|
|
|
size_t get_text_payload_size(void) {
|
|
return sizeof (ElementIndexedQuadWithoutColor);
|
|
}
|
|
|
|
static void load_cubemap_side(const char *path, GLenum target) {
|
|
SDL_Surface *surface = textures_load_surface(path);
|
|
/* TODO: sanity check whether all of them have same dimensions? */
|
|
glTexImage2D(target,
|
|
0,
|
|
GL_RGBA8,
|
|
surface->w, surface->h,
|
|
0,
|
|
surface->format->BytesPerPixel == 4 ? GL_RGBA : GL_RGB,
|
|
GL_UNSIGNED_BYTE,
|
|
surface->pixels);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
|
|
SDL_free(surface->pixels);
|
|
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) {
|
|
static GLuint cubemap = 0;
|
|
static char *paths_cache = NULL;
|
|
|
|
bool loading_needed = false;
|
|
|
|
/* drop it */
|
|
if (!paths_cache || (SDL_strcmp(paths_cache, paths) != 0)) {
|
|
if (cubemap)
|
|
glDeleteTextures(1, &cubemap);
|
|
glGenTextures(1, &cubemap);
|
|
if (paths_cache)
|
|
SDL_free(paths_cache);
|
|
paths_cache = paths;
|
|
loading_needed = true;
|
|
} else
|
|
SDL_free(paths);
|
|
|
|
Matrix4 camera_look_at_matrix_solipsist = camera_look_at_matrix;
|
|
camera_look_at_matrix_solipsist.row[3].x = 0;
|
|
camera_look_at_matrix_solipsist.row[3].y = 0;
|
|
camera_look_at_matrix_solipsist.row[3].z = 0;
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(&camera_look_at_matrix_solipsist.row[0].x);
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
glEnable(GL_TEXTURE_CUBE_MAP);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
|
|
|
|
if (loading_needed) {
|
|
/* load all the sides */
|
|
char *expanded = expand_asterisk(paths, "up");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Y);
|
|
SDL_free(expanded);
|
|
|
|
expanded = expand_asterisk(paths, "down");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
|
|
SDL_free(expanded);
|
|
|
|
expanded = expand_asterisk(paths, "east");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X);
|
|
SDL_free(expanded);
|
|
|
|
expanded = expand_asterisk(paths, "north");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
|
|
SDL_free(expanded);
|
|
|
|
expanded = expand_asterisk(paths, "west");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
|
|
SDL_free(expanded);
|
|
|
|
expanded = expand_asterisk(paths, "south");
|
|
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
|
|
SDL_free(expanded);
|
|
}
|
|
|
|
/* TODO: figure out which coordinates to use to not have issues with far z */
|
|
/* TODO: recalculate the list if far z requirement changes */
|
|
|
|
static GLuint list = 0;
|
|
if (!list) {
|
|
list = glGenLists(1);
|
|
|
|
glNewList(list, GL_COMPILE); {
|
|
/* note: assumes that space pipeline is applied already */
|
|
glDisable(GL_ALPHA_TEST);
|
|
glDepthMask(GL_FALSE);
|
|
|
|
glBegin(GL_QUADS); {
|
|
/* up */
|
|
glTexCoord3f(50.f, 50.f, 50.f);
|
|
glVertex3f(50.f, 50.f, 50.f);
|
|
glTexCoord3f(-50.f, 50.f, 50.f);
|
|
glVertex3f(-50.f, 50.f, 50.f);
|
|
glTexCoord3f(-50.f, 50.f, -50.f);
|
|
glVertex3f(-50.f, 50.f, -50.f);
|
|
glTexCoord3f(50.f, 50.f, -50.f);
|
|
glVertex3f(50.f, 50.f, -50.f);
|
|
|
|
/* down */
|
|
glTexCoord3f(50.f, -50.f, 50.f);
|
|
glVertex3f(50.f, -50.f, 50.f);
|
|
glTexCoord3f(50.f, -50.f, -50.f);
|
|
glVertex3f(50.f, -50.f, -50.f);
|
|
glTexCoord3f(-50.f, -50.f, -50.f);
|
|
glVertex3f(-50.f, -50.f, -50.f);
|
|
glTexCoord3f(-50.f, -50.f, 50.f);
|
|
glVertex3f(-50.f, -50.f, 50.f);
|
|
|
|
/* east */
|
|
glTexCoord3f(50.f, -50.f, 50.f);
|
|
glVertex3f(50.f, -50.f, 50.f);
|
|
glTexCoord3f(50.f, 50.f, 50.f);
|
|
glVertex3f(50.f, 50.f, 50.f);
|
|
glTexCoord3f(50.f, 50.f, -50.f);
|
|
glVertex3f(50.f, 50.f, -50.f);
|
|
glTexCoord3f(50.f, -50.f, -50.f);
|
|
glVertex3f(50.f, -50.f, -50.f);
|
|
|
|
/* west */
|
|
glTexCoord3f(-50.f, -50.f, 50.f);
|
|
glVertex3f(-50.f, -50.f, 50.f);
|
|
glTexCoord3f(-50.f, -50.f, -50.f);
|
|
glVertex3f(-50.f, -50.f, -50.f);
|
|
glTexCoord3f(-50.f, 50.f, -50.f);
|
|
glVertex3f(-50.f, 50.f, -50.f);
|
|
glTexCoord3f(-50.f, 50.f, 50.f);
|
|
glVertex3f(-50.f, 50.f, 50.f);
|
|
|
|
/* north */
|
|
glTexCoord3f(-50.f, -50.f, 50.f);
|
|
glVertex3f(-50.f, -50.f, 50.f);
|
|
glTexCoord3f(-50.f, 50.f, 50.f);
|
|
glVertex3f(-50.f, 50.f, 50.f);
|
|
glTexCoord3f(50.f, 50.f, 50.f);
|
|
glVertex3f(50.f, 50.f, 50.f);
|
|
glTexCoord3f(50.f, -50.f, 50.f);
|
|
glVertex3f(50.f, -50.f, 50.f);
|
|
|
|
/* south */
|
|
glTexCoord3f(-50.f, -50.f, -50.f);
|
|
glVertex3f(-50.f, -50.f, -50.f);
|
|
glTexCoord3f(50.f, -50.f, -50.f);
|
|
glVertex3f(50.f, -50.f, -50.f);
|
|
glTexCoord3f(50.f, 50.f, -50.f);
|
|
glVertex3f(50.f, 50.f, -50.f);
|
|
glTexCoord3f(-50.f, 50.f, -50.f);
|
|
glVertex3f(-50.f, 50.f, -50.f);
|
|
} glEnd();
|
|
|
|
glDepthMask(GL_TRUE);
|
|
glDisable(GL_TEXTURE_CUBE_MAP);
|
|
|
|
} glEndList();
|
|
}
|
|
|
|
glCallList(list);
|
|
}
|
|
|
|
|
|
void finally_apply_fog(float start, float end, float density, Color color) {
|
|
if (density < 0.0f || density > 1.0f)
|
|
log_warn("Invalid fog density given, should be in range [0..1]");
|
|
|
|
/* TODO: cache it for constant parameters, which is a common case */
|
|
|
|
glEnable(GL_FOG);
|
|
|
|
glFogf(GL_FOG_DENSITY, density);
|
|
glFogf(GL_FOG_START, start);
|
|
glFogf(GL_FOG_END, end);
|
|
|
|
float color_conv[4];
|
|
color_conv[0] = (float)color.r / UINT8_MAX;
|
|
color_conv[1] = (float)color.g / UINT8_MAX;
|
|
color_conv[2] = (float)color.b / UINT8_MAX;
|
|
color_conv[3] = (float)color.a / UINT8_MAX;
|
|
|
|
glFogfv(GL_FOG_COLOR, color_conv);
|
|
}
|
|
|
|
|
|
void finally_pop_fog(void) {
|
|
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);
|
|
}
|