townengine/src/rendering/twn_gl_15_rendering.c

1216 lines
37 KiB
C
Raw Normal View History

2024-10-07 14:53:09 +00:00
#include "twn_draw_c.h"
#include "twn_util.h"
2024-09-26 18:02:56 +00:00
#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>
2024-10-14 17:19:18 +00:00
/* 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 */
2024-10-16 00:31:02 +00:00
Vec2 v0;
Vec2 uv0;
Color c0;
/* bottom-left */
2024-10-16 00:31:02 +00:00
Vec2 v1;
Vec2 uv1;
Color c1;
/* bottom-right */
2024-10-16 00:31:02 +00:00
Vec2 v2;
Vec2 uv2;
Color c2;
/* upper-right */
2024-10-16 00:31:02 +00:00
Vec2 v3;
Vec2 uv3;
Color c3;
} ElementIndexedQuad;
typedef struct ElementIndexedQuadWithoutColor {
/* upper-left */
2024-10-16 00:31:02 +00:00
Vec2 v0;
Vec2 uv0;
/* bottom-left */
2024-10-16 00:31:02 +00:00
Vec2 v1;
Vec2 uv1;
/* bottom-right */
2024-10-16 00:31:02 +00:00
Vec2 v2;
Vec2 uv2;
/* upper-right */
2024-10-16 00:31:02 +00:00
Vec2 v3;
Vec2 uv3;
} ElementIndexedQuadWithoutColor;
typedef struct ElementIndexedQuadWithoutTexture {
/* upper-left */
2024-10-16 00:31:02 +00:00
Vec2 v0;
Color c0;
/* bottom-left */
2024-10-16 00:31:02 +00:00
Vec2 v1;
Color c1;
/* bottom-right */
2024-10-16 00:31:02 +00:00
Vec2 v2;
Color c2;
/* upper-right */
2024-10-16 00:31:02 +00:00
Vec2 v3;
Color c3;
} ElementIndexedQuadWithoutTexture;
typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
/* upper-left */
2024-10-16 00:31:02 +00:00
Vec2 v0;
/* bottom-left */
2024-10-16 00:31:02 +00:00
Vec2 v1;
/* bottom-right */
2024-10-16 00:31:02 +00:00
Vec2 v2;
/* upper-right */
2024-10-16 00:31:02 +00:00
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;
2024-10-19 17:02:39 +00:00
/* could be either `element_count` with supplied `element_buffer`, or this, but not both */
GLsizei primitive_count;
double depth_range_low, depth_range_high;
} DeferredCommandDraw;
typedef struct {
char *paths;
} DeferredCommandDrawSkybox;
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 {
float start, end, density;
Color color;
} DeferredCommandApplyFog;
typedef struct {
enum DeferredCommandType {
DEFERRED_COMMAND_TYPE_DRAW,
DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
DEFERRED_COMMAND_TYPE_CLEAR,
DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
DEFERRED_COMMAND_TYPE_APPLY_FOG,
DEFERRED_COMMAND_TYPE_POP_FOG,
} type;
union {
DeferredCommandDraw draw;
DeferredCommandDrawSkybox draw_skybox;
DeferredCommandClear clear;
DeferredCommandUsePipeline use_pipeline;
DeferredCommandUseTextureMode use_texture_mode;
DeferredCommandDepthRange depth_range;
DeferredCommandApplyFog apply_fog;
};
} DeferredCommand;
static TextureMode texture_mode_last_used = TEXTURE_MODE_UNKNOWN;
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 void deferred_render_skybox(char *paths);
static void deferred_apply_fog(float start, float end, float density, Color color);
static void deferred_pop_fog(void);
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);
2024-10-19 17:02:39 +00:00
SDL_assert((command.element_buffer && command.element_count != 0) || command.primitive_count != 0);
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);
}
2024-10-19 17:02:39 +00:00
if (command.element_buffer) {
SDL_assert(command.element_count != 0);
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);
} else {
SDL_assert(command.primitive_count != 0);
glDrawArrays(GL_TRIANGLES, 0, command.primitive_count);
}
/* 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_DRAW_SKYBOX: {
deferred_render_skybox(deferred_commands[i].draw_skybox.paths);
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;
}
case DEFERRED_COMMAND_TYPE_APPLY_FOG: {
deferred_apply_fog(deferred_commands[i].apply_fog.start,
deferred_commands[i].apply_fog.end,
deferred_commands[i].apply_fog.density,
deferred_commands[i].apply_fog.color);
break;
}
case DEFERRED_COMMAND_TYPE_POP_FOG: {
deferred_pop_fog();
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 == 1) {
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)
glEnable(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;
2024-10-15 16:32:42 +00:00
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) {
command.textured = true;
command.texture_key = batch.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 (ElementIndexedQuadWithoutColorWithoutTexture);
2024-10-22 07:05:53 +00:00
else if (!batch.constant_colored && !batch.textured)
return sizeof (ElementIndexedQuadWithoutTexture);
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 = {
2024-10-16 00:31:02 +00:00
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
2024-10-16 00:31:02 +00:00
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
/* equal for all (flat shaded) */
2024-10-16 00:31:02 +00:00
.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 = {
2024-10-16 00:31:02 +00:00
.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 = {
2024-10-16 00:31:02 +00:00
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
/* equal for all (flat shaded) */
2024-10-16 00:31:02 +00:00
.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 = {
2024-10-16 00:31:02 +00:00
.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);
/* nothing to do */
if (primitives_len == 0)
return;
const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
/* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) {
UncoloredSpaceTriangle *payload =
&((UncoloredSpaceTriangle *)(void *)batch->primitives)[i];
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
}
specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle));
2024-10-19 17:02:39 +00:00
DeferredCommandDraw command = {0};
command.vertices = (AttributeArrayPointer) {
.arity = 3,
.type = GL_FLOAT,
.stride = offsetof(UncoloredSpaceTriangle, v1),
.offset = offsetof(UncoloredSpaceTriangle, v0),
2024-10-19 17:02:39 +00:00
.buffer = buffer
};
command.texcoords = (AttributeArrayPointer) {
.arity = 2,
.type = GL_FLOAT,
.stride = offsetof(UncoloredSpaceTriangle, v1),
.offset = offsetof(UncoloredSpaceTriangle, uv0),
2024-10-19 17:02:39 +00:00
.buffer = buffer
};
2024-10-19 17:02:39 +00:00
command.textured = true;
command.texture_key = texture_key;
command.primitive_count = (GLsizei)(3 * primitives_len);
2024-10-19 17:02:39 +00:00
DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
2024-10-19 17:02:39 +00:00
arrpush(deferred_commands, final_command);
}
bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
VertexBufferBuilder *builder,
stbtt_aligned_quad quad)
{
(void)font_data;
ElementIndexedQuadWithoutColor buffer_element = {
2024-10-16 00:31:02 +00:00
.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);
}
2024-09-26 18:02:56 +00:00
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];
static int prev_num_vertices = 0;
static Vec2 prev_position = {0};
int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);
if (prev_num_vertices != num_vertices) {
create_circle_geometry(circle->position,
circle->radius,
num_vertices,
vertices);
prev_num_vertices = num_vertices;
prev_position = circle->position;
} else {
/* reuse the data, but offset it by difference with previously generated position */
/* no evil cos sin ops this way, if radius is shared in sequential calls */
Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
for (int i = 0; i < num_vertices; ++i)
vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
prev_position = circle->position;
}
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();
2024-10-19 16:16:18 +00:00
command.element_count = (num_vertices - 2) * 3;
command.range_end = (num_vertices - 2) * 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);
}
2024-09-26 18:02:56 +00:00
void finally_render_skybox(char *paths) {
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
.draw_skybox = (DeferredCommandDrawSkybox){
.paths = paths
}
};
arrpush(deferred_commands, command);
}
static void deferred_render_skybox(char *paths) {
2024-09-26 18:02:56 +00:00
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);
2024-09-26 18:02:56 +00:00
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);
2024-09-26 18:02:56 +00:00
SDL_free(expanded);
expanded = expand_asterisk(paths, "down");
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
2024-09-26 18:02:56 +00:00
SDL_free(expanded);
expanded = expand_asterisk(paths, "east");
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X);
2024-09-26 18:02:56 +00:00
SDL_free(expanded);
expanded = expand_asterisk(paths, "north");
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
2024-09-26 18:02:56 +00:00
SDL_free(expanded);
expanded = expand_asterisk(paths, "west");
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
2024-09-26 18:02:56 +00:00
SDL_free(expanded);
expanded = expand_asterisk(paths, "south");
load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
2024-09-26 18:02:56 +00:00
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);
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glEnable(GL_DEPTH_CLAMP);
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();
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
glDepthMask(GL_TRUE);
glDisable(GL_TEXTURE_CUBE_MAP);
} glEndList();
}
2024-09-26 18:02:56 +00:00
glCallList(list);
2024-09-26 18:02:56 +00:00
}
2024-10-01 13:10:36 +00:00
void finally_apply_fog(float start, float end, float density, Color color) {
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_APPLY_FOG,
.apply_fog = (DeferredCommandApplyFog){
.start = start,
.end = end,
.density = density,
.color = color
}
};
arrpush(deferred_commands, command);
}
static void deferred_apply_fog(float start, float end, float density, Color color) {
2024-10-01 13:10:36 +00:00
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) {
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_POP_FOG,
};
arrpush(deferred_commands, command);
}
static void deferred_pop_fog(void) {
2024-10-01 13:10:36 +00:00
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);
}