generalization of deferred commands and any_gl rendering where appropriate
This commit is contained in:
		| @@ -109,4 +109,7 @@ typedef struct { | |||||||
|     }; |     }; | ||||||
| } DeferredCommand; | } DeferredCommand; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | extern DeferredCommand *deferred_commands; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| #include "twn_camera_c.h" | #include "twn_camera_c.h" | ||||||
| #include "twn_types.h" | #include "twn_types.h" | ||||||
| #include "twn_vec.h" | #include "twn_vec.h" | ||||||
|  | #include "twn_deferred_commands.h" | ||||||
|  |  | ||||||
| #include <SDL2/SDL.h> | #include <SDL2/SDL.h> | ||||||
| #include <stb_ds.h> | #include <stb_ds.h> | ||||||
| @@ -18,6 +19,8 @@ | |||||||
| #include <tgmath.h> | #include <tgmath.h> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DeferredCommand *deferred_commands; | ||||||
|  |  | ||||||
| /* TODO: have a default initialized one */ | /* TODO: have a default initialized one */ | ||||||
| Matrix4 camera_projection_matrix; | Matrix4 camera_projection_matrix; | ||||||
| Matrix4 camera_look_at_matrix; | Matrix4 camera_look_at_matrix; | ||||||
| @@ -401,3 +404,125 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, | |||||||
|         .up = camera.up, |         .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 | #ifndef TWN_DRAW_C_H | ||||||
| #define TWN_DRAW_C_H | #define TWN_DRAW_C_H | ||||||
|  |  | ||||||
|  | /* TODO: structure more categorically */ | ||||||
|  |  | ||||||
| #include "twn_textures_c.h" | #include "twn_textures_c.h" | ||||||
| #include "twn_text_c.h" | #include "twn_text_c.h" | ||||||
| #include "twn_option.h" | #include "twn_option.h" | ||||||
|  | #include "twn_deferred_commands.h" | ||||||
|  |  | ||||||
| #include <SDL2/SDL.h> | #include <SDL2/SDL.h> | ||||||
| #include <stb_truetype.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 setup_viewport(int x, int y, int width, int height); | ||||||
|  |  | ||||||
| void clear_draw_buffer(void); | void clear_draw_buffer(void); | ||||||
|  | void finally_clear_draw_buffer(DeferredCommandClear command); | ||||||
|  |  | ||||||
| void swap_buffers(void); | void swap_buffers(void); | ||||||
|  |  | ||||||
| void set_depth_range(double low, double high); | void set_depth_range(double low, double high); | ||||||
|  | void finally_set_depth_range(DeferredCommandDepthRange command); | ||||||
|  |  | ||||||
| VertexBuffer get_quad_element_buffer(void); | VertexBuffer get_quad_element_buffer(void); | ||||||
|  |  | ||||||
| @@ -187,10 +192,13 @@ void render_circle(const CirclePrimitive *circle); | |||||||
| void render_rectangle(const RectPrimitive *rectangle); | void render_rectangle(const RectPrimitive *rectangle); | ||||||
|  |  | ||||||
| void use_space_pipeline(void); | void use_space_pipeline(void); | ||||||
|  | void finally_use_space_pipeline(void); | ||||||
|  |  | ||||||
| void use_2d_pipeline(void); | void use_2d_pipeline(void); | ||||||
|  | void finally_use_2d_pipeline(void); | ||||||
|  |  | ||||||
| void use_texture_mode(TextureMode mode); | void use_texture_mode(TextureMode mode); | ||||||
|  | void finally_use_texture_mode(TextureMode mode); | ||||||
|  |  | ||||||
| void finally_render_quads(Primitive2D const primitives[], | void finally_render_quads(Primitive2D const primitives[], | ||||||
|                           struct QuadBatch batch, |                           struct QuadBatch batch, | ||||||
| @@ -220,19 +228,18 @@ void finally_draw_text(FontData const *font_data, | |||||||
|                        VertexBuffer buffer); |                        VertexBuffer buffer); | ||||||
|  |  | ||||||
| void render_skybox(void); | void render_skybox(void); | ||||||
|  | void finally_render_skybox(DeferredCommandDrawSkybox); | ||||||
| void finally_render_skybox(char *paths_in_use); |  | ||||||
|  |  | ||||||
| void apply_fog(void); | void apply_fog(void); | ||||||
|  | void finally_apply_fog(DeferredCommandApplyFog); | ||||||
| void finally_apply_fog(float start, float end, float density, Color color); |  | ||||||
|  |  | ||||||
| void pop_fog(void); | void pop_fog(void); | ||||||
|  |  | ||||||
| void finally_pop_fog(void); | void finally_pop_fog(void); | ||||||
|  |  | ||||||
| void start_render_frame(void); | void start_render_frame(void); | ||||||
|  |  | ||||||
| void end_render_frame(void); | void end_render_frame(void); | ||||||
|  |  | ||||||
|  | void finally_draw_command(DeferredCommandDraw command); | ||||||
|  |  | ||||||
|  | void issue_deferred_draw_commands(void); | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #include "twn_draw.h" | #include "twn_draw.h" | ||||||
| #include "twn_draw_c.h" | #include "twn_draw_c.h" | ||||||
|  |  | ||||||
|  | #include <stb_ds.h> | ||||||
|  |  | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  |  | ||||||
| static float start_cache, end_cache, density_cache; | static float start_cache, end_cache, density_cache; | ||||||
| @@ -21,7 +23,17 @@ void apply_fog(void) { | |||||||
|     if (!fog_used) |     if (!fog_used) | ||||||
|         return; |         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) |     if (!fog_used) | ||||||
|         return; |         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_text_c.h" | ||||||
| #include "twn_types.h" | #include "twn_types.h" | ||||||
| #include "twn_deferred_commands.h" | #include "twn_deferred_commands.h" | ||||||
|  | #include "twn_gl_any_rendering_c.h" | ||||||
|  |  | ||||||
| #include <glad/glad.h> | #include <glad/glad.h> | ||||||
| #include <stb_ds.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; | 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) { | void start_render_frame(void) { | ||||||
|     clear_draw_buffer(); |     clear_draw_buffer(); | ||||||
| } | } | ||||||
| @@ -318,17 +106,7 @@ void end_render_frame(void) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void use_space_pipeline(void) { | void finally_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) |     if (pipeline_last_used == PIPELINE_SPACE) | ||||||
|         return; |         return; | ||||||
|  |  | ||||||
| @@ -366,17 +144,7 @@ static void finally_use_space_pipeline(void) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void use_2d_pipeline(void) { | void finally_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) { |     if (pipeline_last_used == PIPELINE_SPACE) { | ||||||
|         glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |         glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | ||||||
|         glFlush(); |         glFlush(); | ||||||
| @@ -418,17 +186,7 @@ static void finally_use_2d_pipeline(void) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void use_texture_mode(TextureMode mode) { | void finally_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) |     if (texture_mode_last_used == mode) | ||||||
|         return; |         return; | ||||||
|  |  | ||||||
| @@ -815,6 +573,7 @@ size_t get_text_payload_size(void) { | |||||||
|     return sizeof (ElementIndexedQuadWithoutColor); |     return sizeof (ElementIndexedQuadWithoutColor); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| static void load_cubemap_side(const char *path, GLenum target) { | static void load_cubemap_side(const char *path, GLenum target) { | ||||||
|     SDL_Surface *surface = textures_load_surface(path); |     SDL_Surface *surface = textures_load_surface(path); | ||||||
|     /* TODO: sanity check whether all of them have same dimensions? */ |     /* 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) { | void finally_render_skybox(DeferredCommandDrawSkybox command) { | ||||||
|     DeferredCommand command = { |  | ||||||
|         .type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX, |  | ||||||
|         .draw_skybox = (DeferredCommandDrawSkybox){ |  | ||||||
|             .paths = paths |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     arrpush(deferred_commands, command); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| static void deferred_render_skybox(char *paths) { |  | ||||||
|     static GLuint cubemap = 0; |     static GLuint cubemap = 0; | ||||||
|     static char *paths_cache = NULL; |     static char *paths_cache = NULL; | ||||||
|  |  | ||||||
|     bool loading_needed = false; |     bool loading_needed = false; | ||||||
|  |  | ||||||
|     /* drop it */ |     /* drop it */ | ||||||
|     if (!paths_cache || (SDL_strcmp(paths_cache, paths) != 0)) { |     if (!paths_cache || (SDL_strcmp(paths_cache, command.paths) != 0)) { | ||||||
|         if (cubemap) |         if (cubemap) | ||||||
|             glDeleteTextures(1, &cubemap); |             glDeleteTextures(1, &cubemap); | ||||||
|         glGenTextures(1, &cubemap); |         glGenTextures(1, &cubemap); | ||||||
|         if (paths_cache) |         if (paths_cache) | ||||||
|             SDL_free(paths_cache); |             SDL_free(paths_cache); | ||||||
|         paths_cache = paths; |         paths_cache = command.paths; | ||||||
|         loading_needed = true; |         loading_needed = true; | ||||||
|     } else |     } else | ||||||
|         SDL_free(paths); |         SDL_free(command.paths); | ||||||
|  |  | ||||||
|     Matrix4 camera_look_at_matrix_solipsist = camera_look_at_matrix; |     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].x = 0; | ||||||
| @@ -935,27 +682,27 @@ static void deferred_render_skybox(char *paths) { | |||||||
|  |  | ||||||
|     if (loading_needed) { |     if (loading_needed) { | ||||||
|         /* load all the sides */ |         /* 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); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Y); | ||||||
|         SDL_free(expanded); |         SDL_free(expanded); | ||||||
|  |  | ||||||
|         expanded = expand_asterisk(paths, "down"); |         expanded = expand_asterisk(command.paths, "down"); | ||||||
|         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y); | ||||||
|         SDL_free(expanded); |         SDL_free(expanded); | ||||||
|  |  | ||||||
|         expanded = expand_asterisk(paths, "east"); |         expanded = expand_asterisk(command.paths, "east"); | ||||||
|         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X); | ||||||
|         SDL_free(expanded); |         SDL_free(expanded); | ||||||
|  |  | ||||||
|         expanded = expand_asterisk(paths, "north"); |         expanded = expand_asterisk(command.paths, "north"); | ||||||
|         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z); | ||||||
|         SDL_free(expanded); |         SDL_free(expanded); | ||||||
|  |  | ||||||
|         expanded = expand_asterisk(paths, "west"); |         expanded = expand_asterisk(command.paths, "west"); | ||||||
|         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X); | ||||||
|         SDL_free(expanded); |         SDL_free(expanded); | ||||||
|  |  | ||||||
|         expanded = expand_asterisk(paths, "south"); |         expanded = expand_asterisk(command.paths, "south"); | ||||||
|         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); |         load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); | ||||||
|         SDL_free(expanded); |         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) { | void finally_apply_fog(DeferredCommandApplyFog command) { | ||||||
|     DeferredCommand command = { |     if (command.density < 0.0f || command.density > 1.0f) | ||||||
|         .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) |  | ||||||
|         log_warn("Invalid fog density given, should be in range [0..1]"); |         log_warn("Invalid fog density given, should be in range [0..1]"); | ||||||
|  |  | ||||||
|     /* TODO: cache it for constant parameters, which is a common case */ |     /* TODO: cache it for constant parameters, which is a common case */ | ||||||
|  |  | ||||||
|     glEnable(GL_FOG); |     glEnable(GL_FOG); | ||||||
|  |  | ||||||
|     glFogf(GL_FOG_DENSITY, density); |     glFogf(GL_FOG_DENSITY, command.density); | ||||||
|     glFogf(GL_FOG_START, start); |     glFogf(GL_FOG_START, command.start); | ||||||
|     glFogf(GL_FOG_END, end); |     glFogf(GL_FOG_END, command.end); | ||||||
|  |  | ||||||
|     float color_conv[4]; |     float color_conv[4]; | ||||||
|     color_conv[0] = (float)color.r / UINT8_MAX; |     color_conv[0] = (float)command.color.r / UINT8_MAX; | ||||||
|     color_conv[1] = (float)color.g / UINT8_MAX; |     color_conv[1] = (float)command.color.g / UINT8_MAX; | ||||||
|     color_conv[2] = (float)color.b / UINT8_MAX; |     color_conv[2] = (float)command.color.b / UINT8_MAX; | ||||||
|     color_conv[3] = (float)color.a / UINT8_MAX; |     color_conv[3] = (float)command.color.a / UINT8_MAX; | ||||||
|  |  | ||||||
|     glFogfv(GL_FOG_COLOR, color_conv); |     glFogfv(GL_FOG_COLOR, color_conv); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void finally_pop_fog(void) { | 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); |     glDisable(GL_FOG); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| void set_depth_range(double low, double high) { | void finally_set_depth_range(DeferredCommandDepthRange command) { | ||||||
|     DeferredCommand const command = { |     glDepthRange(command.low, command.high); | ||||||
|         .type = DEFERRED_COMMAND_TYPE_DEPTH_RANGE, | } | ||||||
|         .depth_range = { |  | ||||||
|             .low = low, |  | ||||||
|             .high = high | 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, | ||||||
|     arrpush(deferred_commands, command); |                  (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_draw_c.h" | ||||||
|  | #include "twn_engine_context_c.h" | ||||||
|  |  | ||||||
|  | #include <stb_ds.h> | ||||||
|  |  | ||||||
| #ifdef EMSCRIPTEN | #ifdef EMSCRIPTEN | ||||||
| #include <GLES2/gl2.h> | #include <GLES2/gl2.h> | ||||||
| @@ -84,3 +88,35 @@ VertexBuffer get_circle_element_buffer(void) { | |||||||
|  |  | ||||||
|     return buffer; |     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 "twn_draw_c.h" | ||||||
|  |  | ||||||
| #include <SDL2/SDL.h> | #include <SDL2/SDL.h> | ||||||
|  | #include <stb_ds.h> | ||||||
|  |  | ||||||
| static char *paths_in_use; | static char *paths_in_use; | ||||||
|  |  | ||||||
| @@ -21,6 +22,14 @@ void render_skybox(void) { | |||||||
|         return; |         return; | ||||||
|  |  | ||||||
|     /* note: ownership of 'paths_in_use' goes there */ |     /* 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; |     paths_in_use = NULL; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user