generalization of deferred commands and any_gl rendering where appropriate
This commit is contained in:
		@@ -109,4 +109,7 @@ typedef struct {
 | 
			
		||||
    };
 | 
			
		||||
} DeferredCommand;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern DeferredCommand *deferred_commands;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
#include "twn_camera_c.h"
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_vec.h"
 | 
			
		||||
#include "twn_deferred_commands.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
@@ -18,6 +19,8 @@
 | 
			
		||||
#include <tgmath.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DeferredCommand *deferred_commands;
 | 
			
		||||
 | 
			
		||||
/* TODO: have a default initialized one */
 | 
			
		||||
Matrix4 camera_projection_matrix;
 | 
			
		||||
Matrix4 camera_look_at_matrix;
 | 
			
		||||
@@ -401,3 +404,125 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position,
 | 
			
		||||
        .up = camera.up,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 use_texture_mode(TextureMode mode) {
 | 
			
		||||
    DeferredCommand const command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
 | 
			
		||||
        .use_texture_mode = { mode }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void use_2d_pipeline(void) {
 | 
			
		||||
    DeferredCommand const command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
 | 
			
		||||
        .use_pipeline = { PIPELINE_2D }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void use_space_pipeline(void) {
 | 
			
		||||
    DeferredCommand const command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
 | 
			
		||||
        .use_pipeline = { PIPELINE_SPACE }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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: {
 | 
			
		||||
                finally_set_depth_range(deferred_commands[i].depth_range);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case DEFERRED_COMMAND_TYPE_CLEAR: {
 | 
			
		||||
                finally_clear_draw_buffer(deferred_commands[i].clear);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case DEFERRED_COMMAND_TYPE_DRAW: {
 | 
			
		||||
                finally_draw_command(deferred_commands[i].draw);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case DEFERRED_COMMAND_TYPE_DRAW_SKYBOX: {
 | 
			
		||||
                finally_render_skybox(deferred_commands[i].draw_skybox);
 | 
			
		||||
                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: {
 | 
			
		||||
                finally_apply_fog(deferred_commands[i].apply_fog);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            case DEFERRED_COMMAND_TYPE_POP_FOG: {
 | 
			
		||||
                finally_pop_fog();
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                SDL_assert(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
#ifndef TWN_DRAW_C_H
 | 
			
		||||
#define TWN_DRAW_C_H
 | 
			
		||||
 | 
			
		||||
/* TODO: structure more categorically */
 | 
			
		||||
 | 
			
		||||
#include "twn_textures_c.h"
 | 
			
		||||
#include "twn_text_c.h"
 | 
			
		||||
#include "twn_option.h"
 | 
			
		||||
#include "twn_deferred_commands.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_truetype.h>
 | 
			
		||||
@@ -173,10 +176,12 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
 | 
			
		||||
void setup_viewport(int x, int y, int width, int height);
 | 
			
		||||
 | 
			
		||||
void clear_draw_buffer(void);
 | 
			
		||||
void finally_clear_draw_buffer(DeferredCommandClear command);
 | 
			
		||||
 | 
			
		||||
void swap_buffers(void);
 | 
			
		||||
 | 
			
		||||
void set_depth_range(double low, double high);
 | 
			
		||||
void finally_set_depth_range(DeferredCommandDepthRange command);
 | 
			
		||||
 | 
			
		||||
VertexBuffer get_quad_element_buffer(void);
 | 
			
		||||
 | 
			
		||||
@@ -187,10 +192,13 @@ void render_circle(const CirclePrimitive *circle);
 | 
			
		||||
void render_rectangle(const RectPrimitive *rectangle);
 | 
			
		||||
 | 
			
		||||
void use_space_pipeline(void);
 | 
			
		||||
void finally_use_space_pipeline(void);
 | 
			
		||||
 | 
			
		||||
void use_2d_pipeline(void);
 | 
			
		||||
void finally_use_2d_pipeline(void);
 | 
			
		||||
 | 
			
		||||
void use_texture_mode(TextureMode mode);
 | 
			
		||||
void finally_use_texture_mode(TextureMode mode);
 | 
			
		||||
 | 
			
		||||
void finally_render_quads(Primitive2D const primitives[],
 | 
			
		||||
                          struct QuadBatch batch,
 | 
			
		||||
@@ -220,19 +228,18 @@ void finally_draw_text(FontData const *font_data,
 | 
			
		||||
                       VertexBuffer buffer);
 | 
			
		||||
 | 
			
		||||
void render_skybox(void);
 | 
			
		||||
 | 
			
		||||
void finally_render_skybox(char *paths_in_use);
 | 
			
		||||
void finally_render_skybox(DeferredCommandDrawSkybox);
 | 
			
		||||
 | 
			
		||||
void apply_fog(void);
 | 
			
		||||
 | 
			
		||||
void finally_apply_fog(float start, float end, float density, Color color);
 | 
			
		||||
 | 
			
		||||
void finally_apply_fog(DeferredCommandApplyFog);
 | 
			
		||||
void pop_fog(void);
 | 
			
		||||
 | 
			
		||||
void finally_pop_fog(void);
 | 
			
		||||
 | 
			
		||||
void start_render_frame(void);
 | 
			
		||||
 | 
			
		||||
void end_render_frame(void);
 | 
			
		||||
 | 
			
		||||
void finally_draw_command(DeferredCommandDraw command);
 | 
			
		||||
 | 
			
		||||
void issue_deferred_draw_commands(void);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
#include "twn_draw.h"
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
static float start_cache, end_cache, density_cache;
 | 
			
		||||
@@ -21,7 +23,17 @@ void apply_fog(void) {
 | 
			
		||||
    if (!fog_used)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    finally_apply_fog(start_cache, end_cache, density_cache, color_cache);
 | 
			
		||||
    DeferredCommand command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_APPLY_FOG,
 | 
			
		||||
        .apply_fog = (DeferredCommandApplyFog){
 | 
			
		||||
            .start = start_cache,
 | 
			
		||||
            .end = end_cache,
 | 
			
		||||
            .density = density_cache,
 | 
			
		||||
            .color = color_cache
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -29,5 +41,9 @@ void pop_fog(void) {
 | 
			
		||||
    if (!fog_used)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    finally_pop_fog();
 | 
			
		||||
    DeferredCommand command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_POP_FOG,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
#include "twn_text_c.h"
 | 
			
		||||
#include "twn_types.h"
 | 
			
		||||
#include "twn_deferred_commands.h"
 | 
			
		||||
#include "twn_gl_any_rendering_c.h"
 | 
			
		||||
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
@@ -81,219 +82,6 @@ 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);
 | 
			
		||||
                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);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                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();
 | 
			
		||||
}
 | 
			
		||||
@@ -318,17 +106,7 @@ void end_render_frame(void) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
void finally_use_space_pipeline(void) {
 | 
			
		||||
    if (pipeline_last_used == PIPELINE_SPACE)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
@@ -366,17 +144,7 @@ static void finally_use_space_pipeline(void) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
void finally_use_2d_pipeline(void) {
 | 
			
		||||
    if (pipeline_last_used == PIPELINE_SPACE) {
 | 
			
		||||
        glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 | 
			
		||||
        glFlush();
 | 
			
		||||
@@ -418,17 +186,7 @@ static void finally_use_2d_pipeline(void) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
void finally_use_texture_mode(TextureMode mode) {
 | 
			
		||||
    if (texture_mode_last_used == mode)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
@@ -815,6 +573,7 @@ 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? */
 | 
			
		||||
@@ -891,35 +650,23 @@ void render_circle(const CirclePrimitive *circle) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
void finally_render_skybox(DeferredCommandDrawSkybox command) {
 | 
			
		||||
    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 (!paths_cache || (SDL_strcmp(paths_cache, command.paths) != 0)) {
 | 
			
		||||
        if (cubemap)
 | 
			
		||||
            glDeleteTextures(1, &cubemap);
 | 
			
		||||
        glGenTextures(1, &cubemap);
 | 
			
		||||
        if (paths_cache)
 | 
			
		||||
            SDL_free(paths_cache);
 | 
			
		||||
        paths_cache = paths;
 | 
			
		||||
        paths_cache = command.paths;
 | 
			
		||||
        loading_needed = true;
 | 
			
		||||
    } else
 | 
			
		||||
        SDL_free(paths);
 | 
			
		||||
        SDL_free(command.paths);
 | 
			
		||||
 | 
			
		||||
    Matrix4 camera_look_at_matrix_solipsist = camera_look_at_matrix;
 | 
			
		||||
    camera_look_at_matrix_solipsist.row[3].x = 0;
 | 
			
		||||
@@ -935,27 +682,27 @@ static void deferred_render_skybox(char *paths) {
 | 
			
		||||
 | 
			
		||||
    if (loading_needed) {
 | 
			
		||||
        /* load all the sides */
 | 
			
		||||
        char *expanded = expand_asterisk(paths, "up");
 | 
			
		||||
        char *expanded = expand_asterisk(command.paths, "up");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Y);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
 | 
			
		||||
        expanded = expand_asterisk(paths, "down");
 | 
			
		||||
        expanded = expand_asterisk(command.paths, "down");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
 | 
			
		||||
        expanded = expand_asterisk(paths, "east");
 | 
			
		||||
        expanded = expand_asterisk(command.paths, "east");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
 | 
			
		||||
        expanded = expand_asterisk(paths, "north");
 | 
			
		||||
        expanded = expand_asterisk(command.paths, "north");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
 | 
			
		||||
        expanded = expand_asterisk(paths, "west");
 | 
			
		||||
        expanded = expand_asterisk(command.paths, "west");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
 | 
			
		||||
        expanded = expand_asterisk(paths, "south");
 | 
			
		||||
        expanded = expand_asterisk(command.paths, "south");
 | 
			
		||||
        load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
 | 
			
		||||
        SDL_free(expanded);
 | 
			
		||||
    }
 | 
			
		||||
@@ -1051,65 +798,132 @@ static void deferred_render_skybox(char *paths) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
    if (density < 0.0f || density > 1.0f)
 | 
			
		||||
void finally_apply_fog(DeferredCommandApplyFog command) {
 | 
			
		||||
    if (command.density < 0.0f || command.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);
 | 
			
		||||
    glFogf(GL_FOG_DENSITY, command.density);
 | 
			
		||||
    glFogf(GL_FOG_START, command.start);
 | 
			
		||||
    glFogf(GL_FOG_END, command.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;
 | 
			
		||||
    color_conv[0] = (float)command.color.r / UINT8_MAX;
 | 
			
		||||
    color_conv[1] = (float)command.color.g / UINT8_MAX;
 | 
			
		||||
    color_conv[2] = (float)command.color.b / UINT8_MAX;
 | 
			
		||||
    color_conv[3] = (float)command.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) {
 | 
			
		||||
    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
 | 
			
		||||
void finally_set_depth_range(DeferredCommandDepthRange command) {
 | 
			
		||||
    glDepthRange(command.low, command.high);
 | 
			
		||||
}
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
 | 
			
		||||
void finally_clear_draw_buffer(DeferredCommandClear command) {
 | 
			
		||||
    glClearColor((1.0f / 255) * command.color.r,
 | 
			
		||||
                 (1.0f / 255) * command.color.g,
 | 
			
		||||
                 (1.0f / 255) * command.color.b,
 | 
			
		||||
                 (1.0f / 255) * command.color.a);
 | 
			
		||||
 | 
			
		||||
    /* needed as we might mess with it */
 | 
			
		||||
    glDepthRange(0.0, 1.0);
 | 
			
		||||
    glDepthMask(GL_TRUE);
 | 
			
		||||
 | 
			
		||||
    glClear((command.clear_color   ? GL_COLOR_BUFFER_BIT   : 0) |
 | 
			
		||||
            (command.clear_depth   ? GL_DEPTH_BUFFER_BIT   : 0) |
 | 
			
		||||
            (command.clear_stencil ? GL_STENCIL_BUFFER_BIT : 0) );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void finally_draw_command(DeferredCommandDraw command) {
 | 
			
		||||
    /* TODO: don't assume a single vertex array ? */
 | 
			
		||||
    SDL_assert(command.vertices.arity != 0);
 | 
			
		||||
    SDL_assert(command.vertices.buffer);
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,8 @@
 | 
			
		||||
#include "twn_gl_any_rendering_c.h"
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
#include "twn_engine_context_c.h"
 | 
			
		||||
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
#include <GLES2/gl2.h>
 | 
			
		||||
@@ -84,3 +88,35 @@ VertexBuffer get_circle_element_buffer(void) {
 | 
			
		||||
 | 
			
		||||
    return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 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;
 | 
			
		||||
 | 
			
		||||
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];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								src/rendering/twn_gl_any_rendering_c.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/rendering/twn_gl_any_rendering_c.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
#ifndef TWN_GL_ANY_RENDERING_C_H
 | 
			
		||||
#define TWN_GL_ANY_RENDERING_C_H
 | 
			
		||||
 | 
			
		||||
#ifdef EMSCRIPTEN
 | 
			
		||||
#include <GLES2/gl2.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <glad/glad.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
void restart_scratch_vertex_arrays(void);
 | 
			
		||||
 | 
			
		||||
GLuint get_scratch_vertex_array(void);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#include "twn_draw_c.h"
 | 
			
		||||
 | 
			
		||||
#include <SDL2/SDL.h>
 | 
			
		||||
#include <stb_ds.h>
 | 
			
		||||
 | 
			
		||||
static char *paths_in_use;
 | 
			
		||||
 | 
			
		||||
@@ -21,6 +22,14 @@ void render_skybox(void) {
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    /* note: ownership of 'paths_in_use' goes there */
 | 
			
		||||
    finally_render_skybox(paths_in_use);
 | 
			
		||||
    DeferredCommand command = {
 | 
			
		||||
        .type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX,
 | 
			
		||||
        .draw_skybox = (DeferredCommandDrawSkybox){
 | 
			
		||||
            .paths = paths_in_use
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    arrpush(deferred_commands, command);
 | 
			
		||||
 | 
			
		||||
    paths_in_use = NULL;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user