a-game

2D platformer written from scratch.
git clone git://git.amin.space/a-game.git
Log | Files | Refs | README | LICENSE

render.c (11572B)


      1 internal void renderer_init(struct RendererState *renderer, struct Image *images, size_t num_images, struct StackAllocator *allocator)
      2 {
      3     GLfloat quad_vertices[] = {
      4          0.5f,  0.5f, 1.0f, 0.0f,
      5          0.5f, -0.5f, 1.0f, 1.0f,
      6         -0.5f, -0.5f, 0.0f, 1.0f,
      7         -0.5f,  0.5f, 0.0f, 0.0f,
      8     };
      9 
     10     GLuint quad_elements[] = {0, 1, 3, 1, 2, 3};
     11     GLuint rect_elements[] = {0, 1, 1, 2, 2, 3, 3, 0};
     12 
     13     GLuint vao;
     14     GLuint quad_vbo;
     15     GLuint quad_ebo;
     16     GLuint rect_ebo;
     17     glGenVertexArrays(1, &vao);
     18     glGenBuffers(1, &quad_vbo);
     19     glGenBuffers(1, &quad_ebo);
     20     glGenBuffers(1, &rect_ebo);
     21 
     22     // NOTE(amin): We will leave this bound for the duration of the game.
     23     // There should not be any calls to glBindVertexArray(0).
     24     glBindVertexArray(vao);
     25 
     26     glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
     27     glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW);
     28     glVertexAttribPointer(
     29         0,                   // Data location
     30         2,                   // Number of values
     31         GL_FLOAT,            // Data type
     32         GL_FALSE,            // Normalize data
     33         4 * sizeof(GLfloat), // Stride
     34         (GLvoid*)0           // Position data offset (0)
     35     );
     36     glEnableVertexAttribArray(0);
     37 
     38     // texture coordinates
     39     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
     40     glEnableVertexAttribArray(1);
     41 
     42     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_ebo);
     43     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_elements), quad_elements, GL_STATIC_DRAW);
     44 
     45     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, rect_ebo);
     46     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(rect_elements), rect_elements, GL_STATIC_DRAW);
     47 
     48     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_ebo);
     49 
     50     for (u32 i = 0; i < num_images; i++)
     51     {
     52         struct Image img = images[i];
     53         u8 bytes_per_texel = 4;
     54         v2u tile_dim = {64, 64};
     55         v2u tileset_dim = {img.dim.x / tile_dim.x, img.dim.y / tile_dim.y};
     56 
     57         u32 texture_id;
     58         glGenTextures(1, &texture_id);
     59         glBindTexture(GL_TEXTURE_2D_ARRAY, texture_id);
     60         u32 mip_map_levels = 4;
     61         // NOTE(amin): This allocates space for the 2D array texture. If we
     62         // required OpenGL 4.2, we could instead use glTexStorage3D and no
     63         // loop:
     64         //     glTexStorage3D(GL_TEXTURE_2D_ARRAY, mip_map_levels, GL_RGBA8, tile_dim.x, tile_dim.y, tileset_dim.x * tileset_dim.y);
     65         for (u32 level = 0; level < mip_map_levels; level++)
     66         {
     67             v2u mip_dim = {tile_dim.x / math_pow_of_2(level), tile_dim.y / math_pow_of_2(level)};
     68             assert(!math_v2u_eq(mip_dim, (v2u) {0, 0}));
     69             glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGBA8, mip_dim.x, mip_dim.y, tileset_dim.x * tileset_dim.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
     70         }
     71 
     72         size_t marker = mem_st_get_marker(allocator);
     73         {
     74             u8 *tile_data = mem_st_alloc_buffer(allocator, u8, tile_dim.x * tile_dim.y * bytes_per_texel);
     75             for (u32 tile_y = 0; tile_y < tileset_dim.y; tile_y++)
     76             {
     77                 for (u32 tile_x = 0; tile_x < tileset_dim.x; tile_x++)
     78                 {
     79                     // NOTE(amin): We must copy the disparate interleaved
     80                     // texels associated with a given tile in the image to a
     81                     // contiguous memory region.
     82                     for (u32 texel_y = 0; texel_y < tile_dim.y; texel_y++)
     83                     {
     84                         for (u32 texel_x = 0; texel_x < tile_dim.x; texel_x++)
     85                         {
     86                             v2u texel_index_in_image = {
     87                                 (tile_x * tile_dim.x) + texel_x,
     88                                 (tile_y * tile_dim.y) + texel_y,
     89                             };
     90                             assert(texel_index_in_image.x < tileset_dim.x * tile_dim.x);
     91                             assert(texel_index_in_image.y < tileset_dim.y * tile_dim.y);
     92                             u32 linearized_texel_i = texel_index_in_image.x + (texel_index_in_image.y * tileset_dim.x * tile_dim.x);
     93                             u32 *texel = (u32 *)(img.data + (linearized_texel_i * bytes_per_texel));
     94                             ((u32 *)tile_data)[texel_x + (texel_y * tile_dim.x)] = *texel;
     95                         }
     96                     }
     97                     u32 tile_id = tile_x + (tile_y * tileset_dim.x);
     98                     glTexSubImage3D(
     99                         GL_TEXTURE_2D_ARRAY,
    100                         0, 0, 0,
    101                         tile_id,
    102                         tile_dim.x,
    103                         tile_dim.y,
    104                         1,
    105                         GL_RGBA,
    106                         GL_UNSIGNED_BYTE,
    107                         tile_data);
    108                 }
    109             }
    110         }
    111         mem_st_free_to_marker(allocator, marker);
    112 
    113         glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
    114         glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    115         glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    116         glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    117         glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    118 
    119         glActiveTexture(GL_TEXTURE0);
    120     }
    121 
    122     renderer->vao = vao;
    123     renderer->quad_vbo = quad_vbo;
    124     renderer->quad_ebo = quad_ebo;
    125     renderer->rect_ebo = rect_ebo;
    126     renderer->queue = mem_st_alloc_buffer(allocator, struct RenderJob, RENDER_QUEUE_SIZE);
    127     renderer->queue_count = 0;
    128 
    129     glEnable(GL_BLEND);
    130     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    131 }
    132 
    133 internal void renderer_jobs_draw(struct RendererState *renderer, v2u framebuffer)
    134 {
    135     v2 viewport_min = {0};
    136     v2 viewport_dim = {0};
    137     // renderer_update_viewport
    138     {
    139         f32 ppm = 0.0f;
    140         f32 screen_aspect_ratio = (f32)framebuffer.width / (f32)framebuffer.height;
    141         if (screen_aspect_ratio < ROOM_ASPECT_RATIO)
    142         {
    143             ppm = (f32)framebuffer.width / (f32)ROOM_DIM_X;
    144         }
    145         else
    146         {
    147             ppm = (f32)framebuffer.height / (f32)ROOM_DIM_Y;
    148         }
    149 
    150         viewport_dim = (v2) {
    151             ppm * (f32)ROOM_DIM_X,
    152             ppm * (f32)ROOM_DIM_Y,
    153         };
    154 
    155         f32 h_pad_size = (framebuffer.width - viewport_dim.width) / 2.0f;
    156         f32 v_pad_size = (framebuffer.height - viewport_dim.height) / 2.0f;
    157 
    158         viewport_min = (v2) {h_pad_size, v_pad_size};
    159 
    160         m4 view = math_m4_init_id();
    161         view = math_translate(view, (v3) {viewport_min.x, viewport_min.y, 0.0f});
    162         view = math_scale(view, (v3) {ppm, ppm, 1.0f});
    163         renderer->view = view;
    164 
    165         renderer->projection = math_projection_ortho(0.0f, framebuffer.width, 0.0f, framebuffer.height, -1.0f, 0.0f);
    166     }
    167 
    168     glViewport(0, 0, framebuffer.width, framebuffer.height);
    169     glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    170 
    171     glDisable(GL_SCISSOR_TEST);
    172     glClear(GL_COLOR_BUFFER_BIT);
    173     glEnable(GL_SCISSOR_TEST);
    174 
    175     glScissor(
    176         viewport_min.x,
    177         viewport_min.y,
    178         viewport_dim.x,
    179         viewport_dim.y);
    180 
    181 #if 0
    182     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    183 #else
    184     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    185 #endif
    186 
    187     shader_use(&renderer->shader);
    188     shader_setm4(&renderer->shader, SHADER_UNIFORM_PROJECTION, &renderer->projection);
    189     shader_setm4(&renderer->shader, SHADER_UNIFORM_VIEW, &renderer->view);
    190 
    191     GLuint current_ebo = renderer->quad_ebo;
    192     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, current_ebo);
    193     for (size_t i = 0; i < renderer->queue_count; i++)
    194     {
    195         struct RenderJob j = renderer->queue[i];
    196 
    197         if (j.layer == RENDER_LAYER_DEBUG)
    198         {
    199             if (!renderer->scissor_test_is_disabled)
    200             {
    201                 glDisable(GL_SCISSOR_TEST);
    202                 renderer->scissor_test_is_disabled = true;
    203             }
    204         }
    205         else if (renderer->scissor_test_is_disabled)
    206         {
    207             glEnable(GL_SCISSOR_TEST);
    208             renderer->scissor_test_is_disabled = false;
    209         }
    210 
    211         if (current_ebo != j.ebo)
    212         {
    213             current_ebo = j.ebo;
    214             glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, current_ebo);
    215         }
    216         shader_setv3(&renderer->shader, SHADER_UNIFORM_COLOR, &j.color);
    217         shader_setm4(&renderer->shader, SHADER_UNIFORM_MODEL, &j.model);
    218         shader_setf(&renderer->shader, SHADER_UNIFORM_TEX_INTERP, j.tex_interp);
    219         shader_setf(&renderer->shader, SHADER_UNIFORM_TILE_ID, j.tile_id);
    220         if (j.ebo == renderer->quad_ebo)
    221         {
    222             glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    223         }
    224         else if (j.ebo == renderer->rect_ebo)
    225         {
    226             glDrawElements(GL_LINES, 8, GL_UNSIGNED_INT, 0);
    227         }
    228         else
    229         {
    230             assert(false);
    231         }
    232 
    233         assert(j.layer < NUM_RENDER_LAYERS);
    234     }
    235 
    236     renderer->queue_count = 0;
    237 }
    238 
    239 internal void renderer_jobs_sort(struct RendererState *renderer, struct StackAllocator *allocator)
    240 {
    241     size_t free_marker = mem_st_get_marker(allocator);
    242 
    243     struct RenderJob *job_queue = renderer->queue;
    244 
    245     size_t layer_pop_count[NUM_RENDER_LAYERS] = {0};
    246     for (size_t i = 0; i < renderer->queue_count; i++)
    247     {
    248         struct RenderJob j = job_queue[i];
    249         assert(j.layer < NUM_RENDER_LAYERS);
    250         layer_pop_count[j.layer] += 1;
    251     }
    252 
    253     {
    254         size_t sum = 0;
    255         for (size_t i = 0; i < NUM_RENDER_LAYERS; i++)
    256         {
    257             sum += layer_pop_count[i];
    258         }
    259         assert(sum == renderer->queue_count);
    260     }
    261 
    262     struct RenderJob *sorted = mem_st_alloc_buffer(allocator, struct RenderJob, renderer->queue_count);
    263     size_t layer_pop_count_sorted[NUM_RENDER_LAYERS] = {0};
    264     for (size_t i = 0; i < renderer->queue_count; i++)
    265     {
    266         struct RenderJob j = job_queue[i];
    267         assert(j.layer < NUM_RENDER_LAYERS);
    268 
    269         size_t insertion_index = 0;
    270         for (enum RenderLayer l = 0; l < j.layer; l++)
    271         {
    272             insertion_index += layer_pop_count[l];
    273         }
    274         insertion_index += layer_pop_count_sorted[j.layer];
    275 
    276         assert(layer_pop_count_sorted[j.layer] < layer_pop_count[j.layer]);
    277         sorted[insertion_index] = j;
    278         layer_pop_count_sorted[j.layer] += 1;
    279     }
    280 
    281     mem_move_buffer(job_queue, sorted, struct RenderJob, renderer->queue_count);
    282     mem_st_free_to_marker(allocator, free_marker);
    283 }
    284 
    285 internal void renderer_job_enqueue(struct RendererState *renderer, struct RenderJob job)
    286 {
    287     assert(renderer);
    288     assert(renderer->queue);
    289     assert(renderer->queue_count + 1 < RENDER_QUEUE_SIZE);
    290     renderer->queue[renderer->queue_count] = job;
    291     renderer->queue_count++;
    292 }
    293 
    294 internal void renderer_debug_quad_draw(struct RendererState *renderer, rect r, v3 color)
    295 {
    296     v2 dim = {r.max.x - r.min.x, r.max.y - r.min.y};
    297 
    298     m4 model = math_m4_init_id();
    299     model = math_translate(model, (v3) {dim.x * 0.5f, dim.y * 0.5f, 0.0f});
    300     model = math_translate(model, (v3) {r.min.x, r.min.y, 0.0f});
    301     model = math_scale(model, (v3) {dim.width, dim.height, 1.0f});
    302 
    303     renderer_job_enqueue(
    304         renderer,
    305         (struct RenderJob) {
    306             .ebo = renderer->rect_ebo,
    307             .color = color,
    308             .model = model,
    309             .layer = RENDER_LAYER_DEBUG,
    310         });
    311 }
    312 
    313 internal void renderer_cleanup(struct RendererState *renderer)
    314 {
    315     glDeleteVertexArrays(1, &renderer->vao);
    316     glDeleteBuffers(1, &renderer->quad_vbo);
    317     glDeleteBuffers(1, &renderer->quad_ebo);
    318     glDeleteBuffers(1, &renderer->rect_ebo);
    319 }