switched to command based rendering
authorhgn <hgodden00@gmail.com>
Sat, 15 Jan 2022 20:31:24 +0000 (20:31 +0000)
committerhgn <hgodden00@gmail.com>
Sat, 15 Jan 2022 20:31:24 +0000 (20:31 +0000)
12 files changed:
build.sh
fishladder.c
fishladder_resources.h
phoboslab/qoi.h
phoboslab/qoi_old.h [new file with mode: 0644]
sprites_autocombine.h [new file with mode: 0644]
texsheet.c [new file with mode: 0644]
textures_combine/donut.png [new file with mode: 0644]
textures_combine/jack-1.png [new file with mode: 0644]
textures_combine/jack-2.png [new file with mode: 0644]
textures_combine/peg.png [new file with mode: 0644]
vg/vg_tex.h

index aa0d03e0c21f5fa1d6b07d7fa6ac41469bc05692..63f64def69addf806e2e5df32f4bc098604bd94a 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -65,6 +65,7 @@ if [ "$compile_tools" = true ]; then
        mkdir tools -p
        gcc -Wall -Wstrict-aliasing=3 $lib $flags mdlcomp.c gl/glad.c -o tools/mdlcomp $libs $steam_part -Wl,-rpath=./ $defines
        gcc -Wall -Wstrict-aliasing=3 $lib $flags fontcomp.c gl/glad.c -o tools/fontcomp $libs $steam_part -Wl,-rpath=./ $defines
+       gcc -Wall -Wstrict-aliasing=3 $lib $flags texsheet.c gl/glad.c -o tools/texsheet $libs $steam_part -Wl,-rpath=./ $defines
        gcc $lib qoiconv.c -std=c99 -O3 -o tools/qoiconv
 fi
 
@@ -77,13 +78,15 @@ for f in textures/*.png;
        ./tools/qoiconv $f ./_temp_textures/"$(basename "$f" .png).qoi"
 done
 
-#if [ "$compile_models" = true ]; then
-#      echo "Recompiling models"
-#      for f in models/*.obj; 
-#              do echo "Compiling $f..";
-#              ./tools/mdlcomp $f $f.h
-#      done
-#fi
+# Autocombine textures
+auto_combine=""
+cd textures_combine
+for f in *.png;
+       do echo "[combine] $f";
+       auto_combine="$auto_combine $f"
+done
+../tools/texsheet ../_temp_textures/autocombine.qoi ../sprites_autocombine.h sprites_auto_combine $auto_combine
+cd ..
 
 # Main build
 if [ "$do_build" = true ]; then
index 74f972643492666caabd88500d62892708a1f882..e0cfd67458a364ea41326f5c052916e34509662c 100644 (file)
@@ -218,6 +218,15 @@ static struct world
        }
        *data;
 #pragma pack(pop)
+
+       struct render_cmd
+       {
+               struct cell *ptr;
+               v2i pos;
+       } 
+       *cmd_buf_tiles, *cmd_buf_specials;
+
+       u32 tile_count, tile_special_count, max_commands;
        
        int initialzed;
        int sim_run, max_runs;
@@ -275,7 +284,7 @@ static struct world
                v2f physics_v;
                v2f physics_co;
        }
-       fishes[16];
+       fishes[64];
        
        int num_fishes;
        
@@ -351,7 +360,8 @@ static int world_check_pos_ok( v2i co, int dist );
 static int cell_interactive( v2i co );
 
 void vg_update(void);
-static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour );
+static void render_tiles( v4f const regular_colour, v4f const selected_colour );
+static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour );
 
 void vg_render(void);
 void vg_ui(void);
@@ -510,6 +520,8 @@ static void map_free(void)
 {      
        arrfree( world.data );
        arrfree( world.io );
+
+       free( world.cmd_buf_tiles );
        
        world.w = 0;
        world.h = 0;
@@ -1112,6 +1124,10 @@ static int map_load( const char *str, const char *name )
                btn->position[1] = world.h -i -2;
        }
 
+       // Allocate buffers for render commands
+       world.cmd_buf_tiles = malloc( world.w*world.h* sizeof( struct render_cmd ) );
+       world.max_commands = world.w*world.h;
+       
        return 1;
        
 IL_REG_ERROR:
@@ -1797,7 +1813,7 @@ static void vg_update(void)
                                {
                                        cell->state &= ~FLAG_FLIP_ROTATING;
                                }
-                               if( cell->state & FLAG_IS_TRIGGER )
+                               if( cell->state & (FLAG_IS_TRIGGER|FLAG_EMITTER) )
                                        cell->state &= ~FLAG_TRIGGERED;
                        }
                        
@@ -1984,9 +2000,12 @@ static void vg_update(void)
                        {
                                struct fish *fish = &world.fishes[i];
                                
+                               // Apply movement
                                if( fish->state == k_fish_state_alive )
-                               {
                                        v2i_add( fish->pos, fish->dir, fish->pos );
+
+                               if( fish->state == k_fish_state_alive || fish->state == k_fish_state_soon_alive )
+                               {
                                        struct cell *cell_current = pcell( fish->pos );
 
                                        if( cell_current->state & FLAG_IS_TRIGGER )
@@ -1995,7 +2014,8 @@ static void vg_update(void)
                                                
                                                struct cell *target_peice = &world.data[ cell_current->links[trigger_id] ];
                                                
-                                               if( target_peice->state & FLAG_EMITTER )
+                                               // Spawn new marble
+                                               if( (target_peice->state & FLAG_EMITTER) && !(target_peice->state & FLAG_TRIGGERED))
                                                {
                                                        struct fish *fish = &world.fishes[ world.num_fishes ];
                                                        lcell( cell_current->links[trigger_id], fish->pos );
@@ -2317,7 +2337,42 @@ static void vg_update(void)
        }
 }
 
-static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
+static void render_tile( v2i pos, struct cell *ptr, v4f const regular_colour, v4f const selected_colour )
+{
+       int selected = world.selected == pos[1]*world.w + pos[0];
+       
+       int tile_offsets[][2] =
+       {
+               {2, 0}, {0, 3}, {0, 2}, {2, 2},
+               {1, 0}, {2, 3}, {3, 2}, {1, 3},
+               {3, 1}, {0, 1}, {1, 2}, {2, 1},
+               {1, 1}, {3, 3}, {2, 1}, {2, 1}
+       };
+       
+       int uv[2];
+       
+       uv[0] = tile_offsets[ ptr->config ][0];
+       uv[1] = tile_offsets[ ptr->config ][1];
+       
+       glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), 
+               (float)pos[0], 
+               (float)pos[1], 
+               uv[0], 
+               uv[1] 
+       );
+
+       if( selected )
+       {
+               glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
+               draw_mesh( 0, 2 );
+               glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
+       }
+       else                    
+               draw_mesh( 0, 2 );
+}
+
+// Renders specific chunk of tiles
+static void render_tile_block( v2i start, v2i end, v4f const regular_colour, v4f const selected_colour )
 {
        v2i full_start = { 0,0 };
        v2i full_end = { world.w, world.h };
@@ -2327,41 +2382,44 @@ static void render_tiles( v2i start, v2i end, v4f const regular_colour, v4f cons
                start = full_start;
                end = full_end;
        }
-       
-       glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
 
        for( int y = start[1]; y < end[1]; y ++ )
        {
                for( int x = start[0]; x < end[0]; x ++ )
                {
-                       struct cell *cell = pcell((v2i){x,y});
-                       int selected = world.selected == y*world.w + x;
-                       
-                       int tile_offsets[][2] =
-                       {
-                               {2, 0}, {0, 3}, {0, 2}, {2, 2},
-                               {1, 0}, {2, 3}, {3, 2}, {1, 3},
-                               {3, 1}, {0, 1}, {1, 2}, {2, 1},
-                               {1, 1}, {3, 3}, {2, 1}, {2, 1}
-                       };
-                       
-                       int uv[2] = { 3, 0 };
-                       
+                       v2i pos = { x, y };
+                       struct cell *cell = pcell( pos );
+
                        if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
-                       {
-                               uv[0] = tile_offsets[ cell->config ][0];
-                               uv[1] = tile_offsets[ cell->config ][1];
-                       } else continue;
-                       
-                       glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), (float)x, (float)y, uv[0], uv[1] );
-                       if( selected )
-                       {
-                               glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, selected_colour );
-                               draw_mesh( 0, 2 );
-                               glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
-                       }
-                       else                    
-                               draw_mesh( 0, 2 );
+                               render_tile( pos, cell, regular_colour, selected_colour );
+               }
+       }
+}
+
+// Renders all tiles in the command list
+static void render_tiles( v4f const regular_colour, v4f const selected_colour )
+{
+       glUniform4fv( SHADER_UNIFORM( shader_tile_main, "uColour" ), 1, regular_colour );
+
+       struct render_list
+       {
+               struct render_cmd *arr;
+               u32 count;
+       }
+       render_lists[] = { 
+               { world.cmd_buf_tiles, world.tile_count }, 
+               { world.cmd_buf_specials, world.tile_special_count } 
+       };
+
+       for( int i = 0; i < vg_list_size( render_lists ); i ++ )
+       {
+               struct render_list *list = &render_lists[i];
+               for( int j = 0; j < list->count; j ++ )
+               {
+                       struct render_cmd *cmd = &list->arr[j];
+                       struct cell *cell = cmd->ptr;
+
+                       render_tile( cmd->pos, cell, regular_colour, selected_colour );
                }
        }
 }
@@ -2501,6 +2559,34 @@ void vg_render(void)
        
        if( !world.initialzed )
                return;
+
+       // Extract render commands
+       world.tile_count = 0;
+       world.tile_special_count = 0;
+
+       for( int y = 1; y < world.h-1; y ++ )
+       {
+               for( int x = 1; x < world.w-1; x ++ )
+               {
+                       struct cell *cell = pcell((v2i){x,y});
+                       
+                       if( cell->state & (FLAG_CANAL|FLAG_INPUT|FLAG_OUTPUT|FLAG_EMITTER) )
+                       {
+                               struct render_cmd *cmd;
+
+                               if( cell->config == k_cell_type_split || (cell->state & FLAG_EMITTER ) )
+                                       cmd = &world.cmd_buf_tiles[ world.max_commands - (++ world.tile_special_count) ];
+                               else
+                                       cmd = &world.cmd_buf_tiles[ world.tile_count ++ ];
+
+                               cmd->pos[0] = x;
+                               cmd->pos[1] = y;
+                               cmd->ptr = cell;
+                       }
+               }
+       }
+
+       world.cmd_buf_specials = &world.cmd_buf_tiles[ world.max_commands - world.tile_special_count ];
        
        // BACKGROUND
        // ========================================================================================================
@@ -2547,7 +2633,7 @@ void vg_render(void)
        vg_tex2d_bind( &tex_wood, 1 );
        glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
        
-       render_tiles( NULL, NULL, colour_default, colour_default );
+       render_tiles( colour_default, colour_default );
        
        // MARBLES
        // ========================================================================================================
@@ -2607,51 +2693,46 @@ void vg_render(void)
        glUniform1i( SHADER_UNIFORM( shader_tile_main, "uTexWood" ), 1 );
        
        glUniform1f( SHADER_UNIFORM( shader_tile_main, "uForeground" ), 1.0f );
-       render_tiles( NULL, NULL, colour_default, colour_selected  );
+       render_tiles( colour_default, colour_selected );
 
        //      Draw splitters
-       for( int y = 2; y < world.h-2; y ++ )
+       for( int i = 0; i < world.tile_special_count; i ++ )
        {
-               for( int x = 2; x < world.w-2; x ++ )
-               {
-                       struct cell *cell = pcell((v2i){x,y});
+               struct render_cmd *cmd = &world.cmd_buf_specials[i];
+               struct cell *cell = cmd->ptr;
 
-                       if( cell->state & FLAG_CANAL )
+               if( cell->config == k_cell_type_split )
+               {
+                       float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
+                       
+                       if( cell->state & FLAG_FLIP_ROTATING )
                        {
-                               if( cell->config == k_cell_type_split )
+                               if( (world.frame_lerp > curve_7_linear_section) )
                                {
-                                       float rotation = cell->state & FLAG_FLIP_FLOP? vg_rad( -45.0f ): vg_rad( 45.0f );
-                                       
-                                       if( cell->state & FLAG_FLIP_ROTATING )
+                                       float const rotation_speed = 0.4f;
+                                       if( (world.frame_lerp < 1.0f-rotation_speed) )
                                        {
-                                               if( (world.frame_lerp > curve_7_linear_section) )
-                                               {
-                                                       float const rotation_speed = 0.4f;
-                                                       if( (world.frame_lerp < 1.0f-rotation_speed) )
-                                                       {
-                                                               float t = world.frame_lerp - curve_7_linear_section;
-                                                               t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
-                                                               t += 1.0f;
-                                                               
-                                                               rotation *= t;
-                                                       }
-                                                       else
-                                                               rotation *= -1.0f;
-                                               }
+                                               float t = world.frame_lerp - curve_7_linear_section;
+                                               t *= -2.0f * (1.0f/(1.0f-(curve_7_linear_section+rotation_speed)));
+                                               t += 1.0f;
+                                               
+                                               rotation *= t;
                                        }
-
-                                       m2x2_create_rotation( subtransform, rotation );
-
-                                       glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );  
-                                       glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), 
-                                               (float)x, 
-                                               (float)y + 0.125f, 
-                                               cell->state & FLAG_TARGETED? 3.0f: 0.0f, 
-                                               0.0f 
-                                       );
-                                       draw_mesh( 0, 2 );
+                                       else
+                                               rotation *= -1.0f;
                                }
                        }
+
+                       m2x2_create_rotation( subtransform, rotation );
+
+                       glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );  
+                       glUniform4f( SHADER_UNIFORM( shader_tile_main, "uOffset" ), 
+                               (float)cmd->pos[0], 
+                               (float)cmd->pos[1] + 0.125f, 
+                               cell->state & FLAG_TARGETED? 3.0f: 0.0f, 
+                               0.0f 
+                       );
+                       draw_mesh( 0, 2 );
                }
        }
        
@@ -2670,7 +2751,7 @@ void vg_render(void)
                glUniformMatrix2fv( SHADER_UNIFORM( shader_tile_main, "uSubTransform" ), 1, GL_FALSE, (float *)subtransform );  
                glUniform2fv( SHADER_UNIFORM( shader_tile_main, "uMousePos" ), 1, world.tile_pos );
                
-               render_tiles( new_begin, new_end, colour_default, colour_default );
+               render_tile_block( new_begin, new_end, colour_default, colour_default );
                
                world.data[ world.selected ].state ^= FLAG_CANAL;
                map_reclassify( new_begin, new_end, 0 );
@@ -2798,54 +2879,53 @@ void vg_render(void)
        float rp_xa = rp_x1*expf(1.0f-rp_x1)* 0.36f;
        float rp_x2 = 1.0f-rp_xa;
        
-       for( int y = 2; y < world.h-2; y ++ )
+       for( int i = 0; i < world.tile_special_count; i ++ )
        {
-               for( int x = 2; x < world.w-2; x ++ )
-               {
-                       struct cell *cell = pcell((v2i){x,y});
+               struct render_cmd *cmd = &world.cmd_buf_specials[i];
+               struct cell *cell = cmd->ptr;
 
-                       if( cell->state & FLAG_CANAL )
+               if( cell->state & FLAG_TARGETED )
+               {
+                       for( int j = 0; j < 2; j ++ )
                        {
-                               if( cell->state & FLAG_IS_TRIGGER )
-                               {
-                                       int trigger_id = cell->links[0]?0:1;
-                                       struct cell *other_cell = &world.data[ cell->links[ trigger_id ]];
-                                       
-                                       struct cell_description *desc = &cell_descriptions[ cell->config ];
-                                       
-                                       int x2 = cell->links[trigger_id] % world.w;
-                                       int y2 = (cell->links[trigger_id] - x2) / world.w;
-                                       
-                                       v2f startpoint; 
-                                       v2f endpoint;
-                                       
-                                       startpoint[0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
-                                       startpoint[1] = (float)y2 + 0.25f;
-                                       
-                                       endpoint[0] = x;
-                                       endpoint[1] = y;
-                                       
-                                       v2_add( desc->trigger_pos, endpoint, endpoint );
-                                       
-                                       if( other_cell->state & FLAG_EMITTER )
-                                       {
-                                               v4f wire_colour;
-                                               colour_code_v3( other_cell->cc, wire_colour );
-                                               wire_colour[3] = 1.0f;
+                               if( !cell->links[j] )
+                                       continue;
 
-                                               glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
-                                       }
-                                       else
-                                       {
-                                               glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
-                                       }
+                               struct cell *other_cell = &world.data[ cell->links[ j ]];
+                               struct cell_description *desc = &cell_descriptions[ other_cell->config ];
+                               
+                               int x2 = cell->links[j] % world.w;
+                               int y2 = (cell->links[j] - x2) / world.w;
+                               
+                               v2f startpoint; 
+                               v2f endpoint;
+                               
+                               endpoint[0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
+                               endpoint[1] = (float)cmd->pos[1] + 0.25f;
+                               
+                               startpoint[0] = x2;
+                               startpoint[1] = y2;
+                               
+                               v2_add( desc->trigger_pos, startpoint, startpoint );
+                               
+                               if( cmd->ptr->state & FLAG_EMITTER )
+                               {
+                                       v4f wire_colour;
+                                       colour_code_v3( other_cell->cc, wire_colour );
+                                       wire_colour[3] = 1.0f;
 
-                                       glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
-                                       glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
-                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), startpoint[0], startpoint[1], 0.18f );
-                                       glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), endpoint[0], endpoint[1], 0.18f );
-                                       glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
+                                       glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
                                }
+                               else
+                               {
+                                       glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
+                               }
+
+                               glUniform1f( SHADER_UNIFORM( shader_wire, "uCurve" ), other_cell->state & FLAG_TRIGGERED? rp_x2 * 0.4f: 0.4f );
+                               glUniform1f( SHADER_UNIFORM( shader_wire, "uGlow" ), other_cell->state & FLAG_TRIGGERED? rp_xa: 0.0f );
+                               glUniform3f( SHADER_UNIFORM( shader_wire, "uEnd" ), startpoint[0], startpoint[1], 0.18f );
+                               glUniform3f( SHADER_UNIFORM( shader_wire, "uStart" ), endpoint[0], endpoint[1], 0.18f );
+                               glDrawElements( GL_TRIANGLES, world.wire.em, GL_UNSIGNED_SHORT, (void*)(0) );
                        }
                }
        }
@@ -2857,81 +2937,78 @@ void vg_render(void)
        glUniformMatrix3fv( SHADER_UNIFORM( shader_tile_colour, "uPv" ), 1, GL_FALSE, (float *)vg_pv );
        use_mesh( &world.shapes );
        
-       for( int y = 2; y < world.h-2; y ++ )
+       for( int i = 0; i < world.tile_special_count; i ++ )
        {
-               for( int x = 2; x < world.w-2; x ++ )
-               {
-                       struct cell *cell = pcell((v2i){x,y});
+               struct render_cmd *cmd = &world.cmd_buf_specials[i];
+               struct cell *cell = cmd->ptr;
 
-                       if( cell->state & FLAG_CANAL )
+               if( cell->state & FLAG_TARGETED )
+               {
+                       for( int j = 0; j < 2; j ++ )
                        {
-                               if( cell->state & FLAG_IS_TRIGGER )
-                               {
-                                       int trigger_id = cell->links[0]?0:1;
-                                       struct cell *other_cell = &world.data[ cell->links[ trigger_id ]];
-                                       
-                                       struct cell_description *desc = &cell_descriptions[ cell->config ];
+                               if( !cell->links[j] )
+                                       continue;
+
+                               struct cell *other_cell = &world.data[ cell->links[ j ]];
                                
-                                       int x2 = cell->links[trigger_id] % world.w;
-                                       int y2 = (cell->links[trigger_id] - x2) / world.w;
-                                       
-                                       v2f pts[2];
-                                       
-                                       pts[0][0] = (float)x2 + (trigger_id? 0.75f: 0.25f);
-                                       pts[0][1] = (float)y2 + 0.25f;
-                                       
-                                       pts[1][0] = x;
-                                       pts[1][1] = y;
-                                       
-                                       v2_add( desc->trigger_pos, pts[1], pts[1] );
-                                       
-                                       if( other_cell->state & FLAG_EMITTER )
-                                       {
-                                               v4f wire_colour;
-                                               colour_code_v3( other_cell->cc, wire_colour );
+                               struct cell_description *desc = &cell_descriptions[ other_cell->config ];
+                               
+                               int x2 = cell->links[j] % world.w;
+                               int y2 = (cell->links[j] - x2) / world.w;
+                               
+                               v2f pts[2];
+                               
+                               pts[0][0] = (float)cmd->pos[0] + (j? 0.75f: 0.25f);
+                               pts[0][1] = (float)cmd->pos[1] + 0.25f;
+                               
+                               pts[1][0] = x2;
+                               pts[1][1] = y2;
+                               
+                               v2_add( desc->trigger_pos, pts[1], pts[1] );
+                               
+                               if( other_cell->state & FLAG_EMITTER )
+                               {
+                                       v4f wire_colour;
+                                       colour_code_v3( other_cell->cc, wire_colour );
 
-                                               v3_muls( wire_colour, 0.8f, wire_colour );
-                                               wire_colour[3] = 1.0f;
+                                       v3_muls( wire_colour, 0.8f, wire_colour );
+                                       wire_colour[3] = 1.0f;
 
-                                               glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
-                                       }
-                                       else
-                                       {
-                                               glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, trigger_id? wire_right_colour: wire_left_colour );
-                                       }
-                                       //glUniform4fv( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 
-                                       //              1, trigger_id? wire_right_colour: wire_left_colour );
-                                       
-                                       for( int i = 0; i < 2; i ++ )
-                                       {
-                                               glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 
-                                                       pts[i][0],
-                                                       pts[i][1], 
-                                                       0.08f 
-                                               );
-                                               draw_mesh( filled_start, filled_count );        
-                                       }
+                                       glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, wire_colour );
+                               }
+                               else
+                               {
+                                       glUniform4fv( SHADER_UNIFORM( shader_wire, "uColour" ), 1, j? wire_right_colour: wire_left_colour );
+                               }
+                               
+                               for( int i = 0; i < 2; i ++ )
+                               {
+                                       glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), 
+                                               pts[i][0],
+                                               pts[i][1], 
+                                               0.08f 
+                                       );
+                                       draw_mesh( filled_start, filled_count );        
                                }
                        }
                }
        }
-       
+
        // SUB SPLITTER DIRECTION
        // ========================================================================================================
-       
+       // TODO: Make this sprites probably
+
        glUniform4f( SHADER_UNIFORM( shader_tile_colour, "uColour" ), 0.9f, 0.35f, 0.1f, 0.75f );
        
-       for( int y = 2; y < world.h-2; y ++ )
+       for( int i = 0; i < world.tile_special_count; i ++ )
        {
-               for( int x = 2; x < world.w-2; x ++ )
-               {
-                       struct cell *cell = pcell((v2i){x,y});
+               struct render_cmd *cmd = &world.cmd_buf_specials[i];
+               struct cell *cell = cmd->ptr;
 
-                       if( cell->state & FLAG_CANAL && cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
-                       {
-                               glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), x, y, 1.0f );
-                               draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
-                       }
+               if( cell->state & FLAG_TARGETED && cell->config == k_cell_type_split )
+               {
+                       glUniform3f( SHADER_UNIFORM( shader_tile_colour, "uOffset" ), cmd->pos[0], cmd->pos[1], 1.0f );
+                       draw_mesh( cell->state & FLAG_FLIP_FLOP? 5: 4, 1 );
                }
        }
        
index 82efc47100aee05fcf6954141cd26c17cd6816dc..7b12859d44f94bb993c23fab06b9dd5627826e4f 100644 (file)
@@ -124,8 +124,11 @@ vg_tex2d tex_ball_noise =  { .path = "textures/bnoise.qoi" };
 vg_tex2d tex_monofur   =  { .path = "textures/ascii.qoi", .flags = VG_TEXTURE_NO_MIP };
 vg_tex2d tex_unkown            =  { .path = "textures/unkown.qoi" };
 vg_tex2d tex_buttons           =  { .path = "textures/buttons.qoi" };
+vg_tex2d tex_sprites           =  { .path = "textures/autocombine.qoi" };
 
-vg_tex2d *texture_list[] = { &tex_tile_detail, &tex_tile_data, &tex_wood, &tex_ball_noise, &tex_monofur, &tex_unkown, &tex_buttons, &tex_ubuntu };
+vg_tex2d *texture_list[] = { &tex_tile_detail, &tex_tile_data, &tex_wood, &tex_ball_noise, &tex_monofur, &tex_unkown, &tex_buttons, &tex_ubuntu, &tex_sprites };
+
+#include "sprites_autocombine.h"
 
 // AUDIO
 // ===========================================================================================================
@@ -600,7 +603,6 @@ SHADER_DEFINE( shader_sdf,
        "out vec4 FragColor;"
        ""
        "in vec2 aTexCoords;"
-       "in vec4 aColour;"
        ""
        "void main()"
        "{"
@@ -612,6 +614,40 @@ SHADER_DEFINE( shader_sdf,
        UNIFORMS({ "uPv", "uTexGlyphs", "uColour" })
 )
 
+SHADER_DEFINE( shader_sprite,
+
+       // VERTEX
+       "layout (location=0) in vec2 a_co;" // quad mesh
+       "layout (location=1) in vec4 ins_uv;"  // instanced data (uv)
+       "layout (location=2) in vec3 ins_pos;" // position + scale
+       ""
+       "uniform mat3 uPv;"
+       ""
+       "out vec2 aTexCoords;"
+       ""
+       "void main()"
+       "{"
+               "vec2 vertex_world = ins_uv.zw * a_co * ins_pos.z + ins_pos.xy;"
+               "gl_Position = vec4( uPv * vec3( vertex_world, 1.0 ), 1.0 );"
+               "aTexCoords = ins_uv.xy + (a_co+0.5)*ins_uv.zw;"
+       "}",
+       
+       // FRAGMENT
+       "uniform sampler2D uTexMain;"
+       "uniform vec4 uColour;"
+       "out vec4 FragColor;"
+       ""
+       "in vec2 aTexCoords;"
+       ""
+       "void main()"
+       "{"
+               "vec4 glyph = texture( uTexGlyphs, aTexCoords );"
+               "FragColor = glyph;"
+       "}"
+       ,
+       UNIFORMS({ "uPv", "uTexMain", "uColour" })
+)
+
 void vg_register(void)
 {
        SHADER_INIT( shader_tile_colour );
index d70e2e8557dff2e54a6a165d63f9ae54c7c853e0..988f9edcb4fe95935746bfaa53c9b97fa3db1e0d 100644 (file)
@@ -28,12 +28,9 @@ SOFTWARE.
 
 -- About
 
-QOI encodes and decodes images in a lossless format. An encoded QOI image is
-usually around 10--30% larger than a decently optimized PNG image.
-
-QOI outperforms simpler PNG encoders in compression ratio and performance. QOI
-images are typically 20% smaller than PNGs written with stbi_image. Encoding is 
-25-50x faster and decoding is 3-4x faster than stbi_image or libpng.
+QOI encodes and decodes images in a lossless format. Compared to stb_image and
+stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
+20% better compression.
 
 
 -- Synopsis
@@ -48,7 +45,7 @@ images are typically 20% smaller than PNGs written with stbi_image. Encoding is
 // the input pixel data.
 qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
        .width = 1920,
-       .height = 1080, 
+       .height = 1080,
        .channels = 4,
        .colorspace = QOI_SRGB
 });
@@ -77,143 +74,162 @@ QOI_NO_STDIO before including this library.
 This library uses malloc() and free(). To supply your own malloc implementation
 you can define QOI_MALLOC and QOI_FREE before including this library.
 
+This library uses memset() to zero-initialize the index. To supply your own
+implementation you can define QOI_ZEROARR before including this library.
+
 
 -- Data Format
 
-A QOI file has a 14 byte header, followed by any number of data "chunks".
+A QOI file has a 14 byte header, followed by any number of data "chunks" and an
+8-byte end marker.
 
 struct qoi_header_t {
        char     magic[4];   // magic bytes "qoif"
        uint32_t width;      // image width in pixels (BE)
        uint32_t height;     // image height in pixels (BE)
-       uint8_t  channels;   // must be 3 (RGB) or 4 (RGBA)
-       uint8_t  colorspace; // a bitmap 0000rgba where
-                            //   - a zero bit indicates sRGBA, 
-                            //   - a one bit indicates linear (user interpreted)
-                            //   colorspace for each channel
+       uint8_t  channels;   // 3 = RGB, 4 = RGBA
+       uint8_t  colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
 };
 
-The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous
-pixel value. Pixels are either encoded as
+Images are encoded row by row, left to right, top to bottom. The decoder and
+encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
+image is complete when all pixels specified by width * height have been covered.
+
+Pixels are encoded as
  - a run of the previous pixel
- - an index into a previously seen pixel
- - a difference to the previous pixel value in r,g,b,a
- - full r,g,b,a values
+ - an index into an array of previously seen pixels
+ - a difference to the previous pixel value in r,g,b
+ - full r,g,b or r,g,b,a values
+
+The color channels are assumed to not be premultiplied with the alpha channel
+("un-premultiplied alpha").
+
+A running array[64] (zero-initialized) of previously seen pixel values is
+maintained by the encoder and decoder. Each pixel that is seen by the encoder
+and decoder is put into this array at the position formed by a hash function of
+the color value. In the encoder, if the pixel value at the index matches the
+current pixel, this index position is written to the stream as QOI_OP_INDEX.
+The hash function for the index is:
+
+       index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
+
+Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
+bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
+values encoded in these data bits have the most significant bit on the left.
+
+The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
+presence of an 8-bit tag first.
 
-A running array[64] of previously seen pixel values is maintained by the encoder
-and decoder. Each pixel that is seen by the encoder and decoder is put into this
-array at the position (r^g^b^a) % 64. In the encoder, if the pixel value at this
-index matches the current pixel, this index position is written to the stream.
+The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
 
-Each chunk starts with a 2, 3 or 4 bit tag, followed by a number of data bits. 
-The bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned.
-All values encoded in these data bits have the most significant bit (MSB) on the
-left.
 
 The possible chunks are:
 
- - QOI_INDEX -------------
+
+.- QOI_OP_INDEX ----------.
 |         Byte[0]         |
 |  7  6  5  4  3  2  1  0 |
 |-------+-----------------|
 |  0  0 |     index       |
-
+`-------------------------`
 2-bit tag b00
 6-bit index into the color index array: 0..63
 
-
- - QOI_RUN_8 -------------
-|         Byte[0]         |
-|  7  6  5  4  3  2  1  0 |
-|----------+--------------|
-|  0  1  0 |     run      |
-
-3-bit tag b010
-5-bit run-length repeating the previous pixel: 1..32
+A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
+same index. QOI_OP_RUN should be used instead.
 
 
- - QOI_RUN_16 --------------------------------------
-|         Byte[0]         |         Byte[1]         |
-|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
-|----------+----------------------------------------|
-|  0  1  1 |                 run                    |
-
-3-bit tag b011
-13-bit run-length repeating the previous pixel: 33..8224
-
-
- - QOI_DIFF_8 ------------
+.- QOI_OP_DIFF -----------.
 |         Byte[0]         |
 |  7  6  5  4  3  2  1  0 |
 |-------+-----+-----+-----|
-|  1  0 |  dr |  db |  bg |
-
-2-bit tag b10
+|  0  1 |  dr |  dg |  db |
+`-------------------------`
+2-bit tag b01
 2-bit   red channel difference from the previous pixel between -2..1
 2-bit green channel difference from the previous pixel between -2..1
 2-bit  blue channel difference from the previous pixel between -2..1
 
-The difference to the current channel values are using a wraparound operation, 
+The difference to the current channel values are using a wraparound operation,
 so "1 - 2" will result in 255, while "255 + 1" will result in 0.
 
+Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
+0 (b00). 1 is stored as 3 (b11).
+
+The alpha value remains unchanged from the previous pixel.
 
- - QOI_DIFF_16 -------------------------------------
+
+.- QOI_OP_LUMA -------------------------------------.
 |         Byte[0]         |         Byte[1]         |
 |  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
-|----------+--------------|------------ +-----------|
-|  1  1  0 |   red diff   |  green diff | blue diff |
+|-------+-----------------+-------------+-----------|
+|  1  0 |  green diff     |   dr - dg   |  db - dg  |
+`---------------------------------------------------`
+2-bit tag b10
+6-bit green channel difference from the previous pixel -32..31
+4-bit   red channel difference minus green channel difference -8..7
+4-bit  blue channel difference minus green channel difference -8..7
 
-3-bit tag b110
-5-bit   red channel difference from the previous pixel between -16..15
-4-bit green channel difference from the previous pixel between -8..7
-4-bit  blue channel difference from the previous pixel between -8..7
+The green channel is used to indicate the general direction of change and is
+encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
+of the green channel difference and are encoded in 4 bits. I.e.:
+       dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
+       db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
 
-The difference to the current channel values are using a wraparound operation, 
+The difference to the current channel values are using a wraparound operation,
 so "10 - 13" will result in 253, while "250 + 7" will result in 1.
 
+Values are stored as unsigned integers with a bias of 32 for the green channel
+and a bias of 8 for the red and blue channel.
 
- - QOI_DIFF_24 ---------------------------------------------------------------
-|         Byte[0]         |         Byte[1]         |         Byte[2]         |
-|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
-|-------------+----------------+--------------+----------------+--------------|
-|  1  1  1  0 |   red diff     |   green diff |    blue diff   |  alpha diff  |
+The alpha value remains unchanged from the previous pixel.
 
-4-bit tag b1110
-5-bit   red channel difference from the previous pixel between -16..15
-5-bit green channel difference from the previous pixel between -16..15
-5-bit  blue channel difference from the previous pixel between -16..15
-5-bit alpha channel difference from the previous pixel between -16..15
 
-The difference to the current channel values are using a wraparound operation, 
-so "10 - 13" will result in 253, while "250 + 7" will result in 1.
-
-
- - QOI_COLOR -------------
+.- QOI_OP_RUN ------------.
 |         Byte[0]         |
 |  7  6  5  4  3  2  1  0 |
-|-------------+--+--+--+--|
-|  1  1  1  1 |hr|hg|hb|ha|
-
-4-bit tag b1111
-1-bit   red byte follows
-1-bit green byte follows
-1-bit  blue byte follows
-1-bit alpha byte follows
-
-For each set bit hr, hg, hb and ha another byte follows in this order. If such a
-byte follows, it will replace the current color channel value with the value of
-this byte.
-
-
-The byte stream is padded at the end with 4 zero bytes. Size the longest chunk
-we can encounter is 5 bytes (QOI_COLOR with RGBA set), with this padding we just
-have to check for an overrun once per decode loop iteration.
+|-------+-----------------|
+|  1  1 |       run       |
+`-------------------------`
+2-bit tag b11
+6-bit run-length repeating the previous pixel: 1..62
+
+The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
+(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
+QOI_OP_RGBA tags.
+
+
+.- QOI_OP_RGB ------------------------------------------.
+|         Byte[0]         | Byte[1] | Byte[2] | Byte[3] |
+|  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  |
+|-------------------------+---------+---------+---------|
+|  1  1  1  1  1  1  1  0 |   red   |  green  |  blue   |
+`-------------------------------------------------------`
+8-bit tag b11111110
+8-bit   red channel value
+8-bit green channel value
+8-bit  blue channel value
+
+The alpha value remains unchanged from the previous pixel.
+
+
+.- QOI_OP_RGBA ---------------------------------------------------.
+|         Byte[0]         | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
+|  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  | 7 .. 0  |
+|-------------------------+---------+---------+---------+---------|
+|  1  1  1  1  1  1  1  1 |   red   |  green  |  blue   |  alpha  |
+`-----------------------------------------------------------------`
+8-bit tag b11111111
+8-bit   red channel value
+8-bit green channel value
+8-bit  blue channel value
+8-bit alpha channel value
 
 */
 
 
-// -----------------------------------------------------------------------------
-// Header - Public functions
+/* -----------------------------------------------------------------------------
+Header - Public functions */
 
 #ifndef QOI_H
 #define QOI_H
@@ -222,19 +238,20 @@ have to check for an overrun once per decode loop iteration.
 extern "C" {
 #endif
 
-// A pointer to qoi_desc struct has to be supplied to all of qoi's functions. It
-// describes either the input format (for qoi_write, qoi_encode), or is filled
-// with the description read from the file header (for qoi_read, qoi_decode).
+/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
+It describes either the input format (for qoi_write and qoi_encode), or is
+filled with the description read from the file header (for qoi_read and
+qoi_decode).
 
-// The colorspace in this qoi_desc is a bitmap with 0000rgba where a 0-bit 
-// indicates sRGB and a 1-bit indicates linear colorspace for each channel. You 
-// may use one of the predefined constants: QOI_SRGB, QOI_SRGB_LINEAR_ALPHA or 
-// QOI_LINEAR. The colorspace is purely informative. It will be saved to the
-// file header, but does not affect en-/decoding in any way.
+The colorspace in this qoi_desc is an enum where
+       0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
+       1 = all channels are linear
+You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
+informative. It will be saved to the file header, but does not affect
+how chunks are en-/decoded. */
 
-#define QOI_SRGB 0x00
-#define QOI_SRGB_LINEAR_ALPHA 0x01
-#define QOI_LINEAR 0x0f
+#define QOI_SRGB   0
+#define QOI_LINEAR 1
 
 typedef struct {
        unsigned int width;
@@ -245,49 +262,49 @@ typedef struct {
 
 #ifndef QOI_NO_STDIO
 
-// Encode raw RGB or RGBA pixels into a QOI image and write it to the file 
-// system. The qoi_desc struct must be filled with the image width, height, 
-// number of channels (3 = RGB, 4 = RGBA) and the colorspace. 
+/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
+system. The qoi_desc struct must be filled with the image width, height,
+number of channels (3 = RGB, 4 = RGBA) and the colorspace.
 
-// The function returns 0 on failure (invalid parameters, or fopen or malloc 
-// failed) or the number of bytes written on success.
+The function returns 0 on failure (invalid parameters, or fopen or malloc
+failed) or the number of bytes written on success. */
 
 int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
 
 
-// Read and decode a QOI image from the file system. If channels is 0, the
-// number of channels from the file header is used. If channels is 3 or 4 the
-// output format will be forced into this number of channels.
+/* Read and decode a QOI image from the file system. If channels is 0, the
+number of channels from the file header is used. If channels is 3 or 4 the
+output format will be forced into this number of channels.
 
-// The function either returns NULL on failure (invalid data, or malloc or fopen
-// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct 
-// will be filled with the description from the file header.
+The function either returns NULL on failure (invalid data, or malloc or fopen
+failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
+will be filled with the description from the file header.
 
-// The returned pixel data should be free()d after use.
+The returned pixel data should be free()d after use. */
 
 void *qoi_read(const char *filename, qoi_desc *desc, int channels);
 
-#endif // QOI_NO_STDIO
+#endif /* QOI_NO_STDIO */
 
 
-// Encode raw RGB or RGBA pixels into a QOI image in memory.
+/* Encode raw RGB or RGBA pixels into a QOI image in memory.
 
-// The function either returns NULL on failure (invalid parameters or malloc 
-// failed) or a pointer to the encoded data on success. On success the out_len 
-// is set to the size in bytes of the encoded data.
+The function either returns NULL on failure (invalid parameters or malloc
+failed) or a pointer to the encoded data on success. On success the out_len
+is set to the size in bytes of the encoded data.
 
-// The returned qoi data should be free()d after use.
+The returned qoi data should be free()d after use. */
 
 void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
 
 
-// Decode a QOI image from memory.
+/* Decode a QOI image from memory.
 
-// The function either returns NULL on failure (invalid parameters or malloc 
-// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct 
-// is filled with the description from the file header.
+The function either returns NULL on failure (invalid parameters or malloc
+failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
+is filled with the description from the file header.
 
-// The returned pixel data should be free()d after use.
+The returned pixel data should be free()d after use. */
 
 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
 
@@ -295,75 +312,91 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
 #ifdef __cplusplus
 }
 #endif
-#endif // QOI_H
+#endif /* QOI_H */
 
 
-// -----------------------------------------------------------------------------
-// Implementation
+/* -----------------------------------------------------------------------------
+Implementation */
 
 #ifdef QOI_IMPLEMENTATION
 #include <stdlib.h>
+#include <string.h>
 
 #ifndef QOI_MALLOC
        #define QOI_MALLOC(sz) malloc(sz)
        #define QOI_FREE(p)    free(p)
 #endif
+#ifndef QOI_ZEROARR
+       #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
+#endif
 
-#define QOI_INDEX   0x00 // 00xxxxxx
-#define QOI_RUN_8   0x40 // 010xxxxx
-#define QOI_RUN_16  0x60 // 011xxxxx
-#define QOI_DIFF_8  0x80 // 10xxxxxx
-#define QOI_DIFF_16 0xc0 // 110xxxxx
-#define QOI_DIFF_24 0xe0 // 1110xxxx
-#define QOI_COLOR   0xf0 // 1111xxxx
+#define QOI_OP_INDEX  0x00 /* 00xxxxxx */
+#define QOI_OP_DIFF   0x40 /* 01xxxxxx */
+#define QOI_OP_LUMA   0x80 /* 10xxxxxx */
+#define QOI_OP_RUN    0xc0 /* 11xxxxxx */
+#define QOI_OP_RGB    0xfe /* 11111110 */
+#define QOI_OP_RGBA   0xff /* 11111111 */
 
-#define QOI_MASK_2  0xc0 // 11000000
-#define QOI_MASK_3  0xe0 // 11100000
-#define QOI_MASK_4  0xf0 // 11110000
+#define QOI_MASK_2    0xc0 /* 11000000 */
 
-#define QOI_COLOR_HASH(C) (C.rgba.r ^ C.rgba.g ^ C.rgba.b ^ C.rgba.a)
+#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
 #define QOI_MAGIC \
        (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
         ((unsigned int)'i') <<  8 | ((unsigned int)'f'))
 #define QOI_HEADER_SIZE 14
-#define QOI_PADDING 4
+
+/* 2GB is the max file size that this implementation can safely handle. We guard
+against anything larger than that, assuming the worst case with 5 bytes per
+pixel, rounded down to a nice clean value. 400 million pixels ought to be
+enough for anybody. */
+#define QOI_PIXELS_MAX ((unsigned int)400000000)
 
 typedef union {
        struct { unsigned char r, g, b, a; } rgba;
        unsigned int v;
 } qoi_rgba_t;
 
-void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
+static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
+
+static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
        bytes[(*p)++] = (0xff000000 & v) >> 24;
        bytes[(*p)++] = (0x00ff0000 & v) >> 16;
        bytes[(*p)++] = (0x0000ff00 & v) >> 8;
        bytes[(*p)++] = (0x000000ff & v);
 }
 
-unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
+static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
        unsigned int a = bytes[(*p)++];
        unsigned int b = bytes[(*p)++];
        unsigned int c = bytes[(*p)++];
        unsigned int d = bytes[(*p)++];
-       return (a << 24) | (b << 16) | (c << 8) | d;
+       return a << 24 | b << 16 | c << 8 | d;
 }
 
 void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
+       int i, max_size, p, run;
+       int px_len, px_end, px_pos, channels;
+       unsigned char *bytes;
+       const unsigned char *pixels;
+       qoi_rgba_t index[64];
+       qoi_rgba_t px, px_prev;
+
        if (
                data == NULL || out_len == NULL || desc == NULL ||
                desc->width == 0 || desc->height == 0 ||
                desc->channels < 3 || desc->channels > 4 ||
-               (desc->colorspace & 0xf0) != 0
+               desc->colorspace > 1 ||
+               desc->height >= QOI_PIXELS_MAX / desc->width
        ) {
                return NULL;
        }
 
-       int max_size = 
-               desc->width * desc->height * (desc->channels + 1) + 
-               QOI_HEADER_SIZE + QOI_PADDING;
+       max_size =
+               desc->width * desc->height * (desc->channels + 1) +
+               QOI_HEADER_SIZE + sizeof(qoi_padding);
 
-       int p = 0;
-       unsigned char *bytes = QOI_MALLOC(max_size);
+       p = 0;
+       bytes = (unsigned char *) QOI_MALLOC(max_size);
        if (!bytes) {
                return NULL;
        }
@@ -375,105 +408,98 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
        bytes[p++] = desc->colorspace;
 
 
-       const unsigned char *pixels = (const unsigned char *)data;
+       pixels = (const unsigned char *)data;
+
+       QOI_ZEROARR(index);
 
-       qoi_rgba_t index[64] = {0};
+       run = 0;
+       px_prev.rgba.r = 0;
+       px_prev.rgba.g = 0;
+       px_prev.rgba.b = 0;
+       px_prev.rgba.a = 255;
+       px = px_prev;
 
-       int run = 0;
-       qoi_rgba_t px_prev = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}};
-       qoi_rgba_t px = px_prev;
-       
-       int px_len = desc->width * desc->height * desc->channels;
-       int px_end = px_len - desc->channels;
-       int channels = desc->channels;
+       px_len = desc->width * desc->height * desc->channels;
+       px_end = px_len - desc->channels;
+       channels = desc->channels;
 
-       for (int px_pos = 0; px_pos < px_len; px_pos += channels) {
+       for (px_pos = 0; px_pos < px_len; px_pos += channels) {
                if (channels == 4) {
                        px = *(qoi_rgba_t *)(pixels + px_pos);
                }
                else {
-                       px.rgba.r = pixels[px_pos];
-                       px.rgba.g = pixels[px_pos+1];
-                       px.rgba.b = pixels[px_pos+2];
+                       px.rgba.r = pixels[px_pos + 0];
+                       px.rgba.g = pixels[px_pos + 1];
+                       px.rgba.b = pixels[px_pos + 2];
                }
 
                if (px.v == px_prev.v) {
                        run++;
+                       if (run == 62 || px_pos == px_end) {
+                               bytes[p++] = QOI_OP_RUN | (run - 1);
+                               run = 0;
+                       }
                }
+               else {
+                       int index_pos;
 
-               if ( 
-                       run > 0 && 
-                       (run == 0x2020 || px.v != px_prev.v || px_pos == px_end)
-               ) {
-                       if (run < 33) {
-                               run -= 1;
-                               bytes[p++] = QOI_RUN_8 | run;
+                       if (run > 0) {
+                               bytes[p++] = QOI_OP_RUN | (run - 1);
+                               run = 0;
                        }
-                       else {
-                               run -= 33;
-                               bytes[p++] = QOI_RUN_16 | run >> 8;
-                               bytes[p++] = run;
-                       }
-                       run = 0;
-               }
 
-               if (px.v != px_prev.v) {
-                       int index_pos = QOI_COLOR_HASH(px) % 64;
+                       index_pos = QOI_COLOR_HASH(px) % 64;
 
                        if (index[index_pos].v == px.v) {
-                               bytes[p++] = QOI_INDEX | index_pos;
+                               bytes[p++] = QOI_OP_INDEX | index_pos;
                        }
                        else {
                                index[index_pos] = px;
 
-                               int vr = px.rgba.r - px_prev.rgba.r;
-                               int vg = px.rgba.g - px_prev.rgba.g;
-                               int vb = px.rgba.b - px_prev.rgba.b;
-                               int va = px.rgba.a - px_prev.rgba.a;
-                               
-                               if (
-                                       vr > -17 && vr < 16 && 
-                                       vg > -17 && vg < 16 && 
-                                       vb > -17 && vb < 16 && 
-                                       va > -17 && va < 16
-                               ) {
+                               if (px.rgba.a == px_prev.rgba.a) {
+                                       signed char vr = px.rgba.r - px_prev.rgba.r;
+                                       signed char vg = px.rgba.g - px_prev.rgba.g;
+                                       signed char vb = px.rgba.b - px_prev.rgba.b;
+
+                                       signed char vg_r = vr - vg;
+                                       signed char vg_b = vb - vg;
+
                                        if (
-                                               va == 0 && 
                                                vr > -3 && vr < 2 &&
-                                               vg > -3 && vg < 2 && 
+                                               vg > -3 && vg < 2 &&
                                                vb > -3 && vb < 2
                                        ) {
-                                               bytes[p++] = QOI_DIFF_8 | ((vr + 2) << 4) | (vg + 2) << 2 | (vb + 2);
+                                               bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
                                        }
                                        else if (
-                                               va == 0 && 
-                                               vr > -17 && vr < 16 && 
-                                               vg >  -9 && vg <  8 && 
-                                               vb >  -9 && vb <  8
+                                               vg_r >  -9 && vg_r <  8 &&
+                                               vg   > -33 && vg   < 32 &&
+                                               vg_b >  -9 && vg_b <  8
                                        ) {
-                                               bytes[p++] = QOI_DIFF_16   | (vr + 16);
-                                               bytes[p++] = (vg + 8) << 4 | (vb +  8);
+                                               bytes[p++] = QOI_OP_LUMA     | (vg   + 32);
+                                               bytes[p++] = (vg_r + 8) << 4 | (vg_b +  8);
                                        }
                                        else {
-                                               bytes[p++] = QOI_DIFF_24    | (vr + 16) >> 1;
-                                               bytes[p++] = (vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3;
-                                               bytes[p++] = (vb + 16) << 5 | (va + 16);
+                                               bytes[p++] = QOI_OP_RGB;
+                                               bytes[p++] = px.rgba.r;
+                                               bytes[p++] = px.rgba.g;
+                                               bytes[p++] = px.rgba.b;
                                        }
                                }
                                else {
-                                       bytes[p++] = QOI_COLOR | (vr ? 8 : 0) | (vg ? 4 : 0) | (vb ? 2 : 0) | (va ? 1 : 0);
-                                       if (vr) { bytes[p++] = px.rgba.r; }
-                                       if (vg) { bytes[p++] = px.rgba.g; }
-                                       if (vb) { bytes[p++] = px.rgba.b; }
-                                       if (va) { bytes[p++] = px.rgba.a; }
+                                       bytes[p++] = QOI_OP_RGBA;
+                                       bytes[p++] = px.rgba.r;
+                                       bytes[p++] = px.rgba.g;
+                                       bytes[p++] = px.rgba.b;
+                                       bytes[p++] = px.rgba.a;
                                }
                        }
                }
                px_prev = px;
        }
 
-       for (int i = 0; i < QOI_PADDING; i++) {
-               bytes[p++] = 0;
+       for (i = 0; i < (int)sizeof(qoi_padding); i++) {
+               bytes[p++] = qoi_padding[i];
        }
 
        *out_len = p;
@@ -481,27 +507,36 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
 }
 
 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
+       const unsigned char *bytes;
+       unsigned int header_magic;
+       unsigned char *pixels;
+       qoi_rgba_t index[64];
+       qoi_rgba_t px;
+       int px_len, chunks_len, px_pos;
+       int p = 0, run = 0;
+
        if (
                data == NULL || desc == NULL ||
                (channels != 0 && channels != 3 && channels != 4) ||
-               size < QOI_HEADER_SIZE + QOI_PADDING
+               size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
        ) {
                return NULL;
        }
 
-       const unsigned char *bytes = (const unsigned char *)data;
-       int p = 0;
+       bytes = (const unsigned char *)data;
 
-       unsigned int header_magic = qoi_read_32(bytes, &p);
+       header_magic = qoi_read_32(bytes, &p);
        desc->width = qoi_read_32(bytes, &p);
        desc->height = qoi_read_32(bytes, &p);
        desc->channels = bytes[p++];
        desc->colorspace = bytes[p++];
 
        if (
-               desc->width == 0 || desc->height == 0 || 
+               desc->width == 0 || desc->height == 0 ||
                desc->channels < 3 || desc->channels > 4 ||
-               header_magic != QOI_MAGIC
+               desc->colorspace > 1 ||
+               header_magic != QOI_MAGIC ||
+               desc->height >= QOI_PIXELS_MAX / desc->width
        ) {
                return NULL;
        }
@@ -510,70 +545,66 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
                channels = desc->channels;
        }
 
-       int px_len = desc->width * desc->height * channels;
-       unsigned char *pixels = QOI_MALLOC(px_len);
+       px_len = desc->width * desc->height * channels;
+       pixels = (unsigned char *) QOI_MALLOC(px_len);
        if (!pixels) {
                return NULL;
        }
 
-       qoi_rgba_t px = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}};
-       qoi_rgba_t index[64] = {0};
+       QOI_ZEROARR(index);
+       px.rgba.r = 0;
+       px.rgba.g = 0;
+       px.rgba.b = 0;
+       px.rgba.a = 255;
 
-       int run = 0;
-       int chunks_len = size - QOI_PADDING;
-       for (int px_pos = 0; px_pos < px_len; px_pos += channels) {
+       chunks_len = size - (int)sizeof(qoi_padding);
+       for (px_pos = 0; px_pos < px_len; px_pos += channels) {
                if (run > 0) {
                        run--;
                }
                else if (p < chunks_len) {
                        int b1 = bytes[p++];
 
-                       if ((b1 & QOI_MASK_2) == QOI_INDEX) {
-                               px = index[b1 ^ QOI_INDEX];
+                       if (b1 == QOI_OP_RGB) {
+                               px.rgba.r = bytes[p++];
+                               px.rgba.g = bytes[p++];
+                               px.rgba.b = bytes[p++];
                        }
-                       else if ((b1 & QOI_MASK_3) == QOI_RUN_8) {
-                               run = (b1 & 0x1f);
+                       else if (b1 == QOI_OP_RGBA) {
+                               px.rgba.r = bytes[p++];
+                               px.rgba.g = bytes[p++];
+                               px.rgba.b = bytes[p++];
+                               px.rgba.a = bytes[p++];
                        }
-                       else if ((b1 & QOI_MASK_3) == QOI_RUN_16) {
-                               int b2 = bytes[p++];
-                               run = (((b1 & 0x1f) << 8) | (b2)) + 32;
+                       else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
+                               px = index[b1];
                        }
-                       else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) {
+                       else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
                                px.rgba.r += ((b1 >> 4) & 0x03) - 2;
                                px.rgba.g += ((b1 >> 2) & 0x03) - 2;
                                px.rgba.b += ( b1       & 0x03) - 2;
                        }
-                       else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) {
-                               int b2 = bytes[p++];
-                               px.rgba.r += (b1 & 0x1f) - 16;
-                               px.rgba.g += (b2 >> 4)   -  8;
-                               px.rgba.b += (b2 & 0x0f) -  8;
-                       }
-                       else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) {
+                       else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
                                int b2 = bytes[p++];
-                               int b3 = bytes[p++];
-                               px.rgba.r += (((b1 & 0x0f) << 1) | (b2 >> 7)) - 16;
-                               px.rgba.g +=  ((b2 & 0x7c) >> 2) - 16;
-                               px.rgba.b += (((b2 & 0x03) << 3) | ((b3 & 0xe0) >> 5)) - 16;
-                               px.rgba.a +=   (b3 & 0x1f) - 16;
+                               int vg = (b1 & 0x3f) - 32;
+                               px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
+                               px.rgba.g += vg;
+                               px.rgba.b += vg - 8 +  (b2       & 0x0f);
                        }
-                       else if ((b1 & QOI_MASK_4) == QOI_COLOR) {
-                               if (b1 & 8) { px.rgba.r = bytes[p++]; }
-                               if (b1 & 4) { px.rgba.g = bytes[p++]; }
-                               if (b1 & 2) { px.rgba.b = bytes[p++]; }
-                               if (b1 & 1) { px.rgba.a = bytes[p++]; }
+                       else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
+                               run = (b1 & 0x3f);
                        }
 
                        index[QOI_COLOR_HASH(px) % 64] = px;
                }
 
-               if (channels == 4) { 
+               if (channels == 4) {
                        *(qoi_rgba_t*)(pixels + px_pos) = px;
                }
                else {
-                       pixels[px_pos] = px.rgba.r;
-                       pixels[px_pos+1] = px.rgba.g;
-                       pixels[px_pos+2] = px.rgba.b;
+                       pixels[px_pos + 0] = px.rgba.r;
+                       pixels[px_pos + 1] = px.rgba.g;
+                       pixels[px_pos + 2] = px.rgba.b;
                }
        }
 
@@ -585,48 +616,56 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
 
 int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
        FILE *f = fopen(filename, "wb");
+       int size;
+       void *encoded;
+
        if (!f) {
                return 0;
        }
 
-       int size;
-       void *encoded = qoi_encode(data, desc, &size);
+       encoded = qoi_encode(data, desc, &size);
        if (!encoded) {
                fclose(f);
                return 0;
-       }       
-       
+       }
+
        fwrite(encoded, 1, size, f);
        fclose(f);
-       
+
        QOI_FREE(encoded);
        return size;
 }
 
 void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
        FILE *f = fopen(filename, "rb");
+       int size, bytes_read;
+       void *pixels, *data;
+
        if (!f) {
                return NULL;
        }
 
        fseek(f, 0, SEEK_END);
-       int size = ftell(f);
+       size = ftell(f);
+       if (size <= 0) {
+               fclose(f);
+               return NULL;
+       }
        fseek(f, 0, SEEK_SET);
 
-       void *data = QOI_MALLOC(size);
+       data = QOI_MALLOC(size);
        if (!data) {
                fclose(f);
                return NULL;
        }
 
-       int bytes_read = fread(data, 1, size, f);
+       bytes_read = fread(data, 1, size, f);
        fclose(f);
 
-       void *pixels = qoi_decode(data, bytes_read, desc, channels);
+       pixels = qoi_decode(data, bytes_read, desc, channels);
        QOI_FREE(data);
        return pixels;
 }
 
-#endif // QOI_NO_STDIO
-#endif // QOI_IMPLEMENTATION
-
+#endif /* QOI_NO_STDIO */
+#endif /* QOI_IMPLEMENTATION */
diff --git a/phoboslab/qoi_old.h b/phoboslab/qoi_old.h
new file mode 100644 (file)
index 0000000..d70e2e8
--- /dev/null
@@ -0,0 +1,632 @@
+/*
+
+QOI - The "Quite OK Image" format for fast, lossless image compression
+
+Dominic Szablewski - https://phoboslab.org
+
+
+-- LICENSE: The MIT License(MIT)
+
+Copyright(c) 2021 Dominic Szablewski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files(the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions :
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+-- About
+
+QOI encodes and decodes images in a lossless format. An encoded QOI image is
+usually around 10--30% larger than a decently optimized PNG image.
+
+QOI outperforms simpler PNG encoders in compression ratio and performance. QOI
+images are typically 20% smaller than PNGs written with stbi_image. Encoding is 
+25-50x faster and decoding is 3-4x faster than stbi_image or libpng.
+
+
+-- Synopsis
+
+// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
+// library to create the implementation.
+
+#define QOI_IMPLEMENTATION
+#include "qoi.h"
+
+// Encode and store an RGBA buffer to the file system. The qoi_desc describes
+// the input pixel data.
+qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
+       .width = 1920,
+       .height = 1080, 
+       .channels = 4,
+       .colorspace = QOI_SRGB
+});
+
+// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
+// The qoi_desc struct will be filled with the width, height, number of channels
+// and colorspace read from the file header.
+qoi_desc desc;
+void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
+
+
+
+-- Documentation
+
+This library provides the following functions;
+- qoi_read    -- read and decode a QOI file
+- qoi_decode  -- decode the raw bytes of a QOI image from memory
+- qoi_write   -- encode and write a QOI file
+- qoi_encode  -- encode an rgba buffer into a QOI image in memory
+
+See the function declaration below for the signature and more information.
+
+If you don't want/need the qoi_read and qoi_write functions, you can define
+QOI_NO_STDIO before including this library.
+
+This library uses malloc() and free(). To supply your own malloc implementation
+you can define QOI_MALLOC and QOI_FREE before including this library.
+
+
+-- Data Format
+
+A QOI file has a 14 byte header, followed by any number of data "chunks".
+
+struct qoi_header_t {
+       char     magic[4];   // magic bytes "qoif"
+       uint32_t width;      // image width in pixels (BE)
+       uint32_t height;     // image height in pixels (BE)
+       uint8_t  channels;   // must be 3 (RGB) or 4 (RGBA)
+       uint8_t  colorspace; // a bitmap 0000rgba where
+                            //   - a zero bit indicates sRGBA, 
+                            //   - a one bit indicates linear (user interpreted)
+                            //   colorspace for each channel
+};
+
+The decoder and encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous
+pixel value. Pixels are either encoded as
+ - a run of the previous pixel
+ - an index into a previously seen pixel
+ - a difference to the previous pixel value in r,g,b,a
+ - full r,g,b,a values
+
+A running array[64] of previously seen pixel values is maintained by the encoder
+and decoder. Each pixel that is seen by the encoder and decoder is put into this
+array at the position (r^g^b^a) % 64. In the encoder, if the pixel value at this
+index matches the current pixel, this index position is written to the stream.
+
+Each chunk starts with a 2, 3 or 4 bit tag, followed by a number of data bits. 
+The bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned.
+All values encoded in these data bits have the most significant bit (MSB) on the
+left.
+
+The possible chunks are:
+
+ - QOI_INDEX -------------
+|         Byte[0]         |
+|  7  6  5  4  3  2  1  0 |
+|-------+-----------------|
+|  0  0 |     index       |
+
+2-bit tag b00
+6-bit index into the color index array: 0..63
+
+
+ - QOI_RUN_8 -------------
+|         Byte[0]         |
+|  7  6  5  4  3  2  1  0 |
+|----------+--------------|
+|  0  1  0 |     run      |
+
+3-bit tag b010
+5-bit run-length repeating the previous pixel: 1..32
+
+
+ - QOI_RUN_16 --------------------------------------
+|         Byte[0]         |         Byte[1]         |
+|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
+|----------+----------------------------------------|
+|  0  1  1 |                 run                    |
+
+3-bit tag b011
+13-bit run-length repeating the previous pixel: 33..8224
+
+
+ - QOI_DIFF_8 ------------
+|         Byte[0]         |
+|  7  6  5  4  3  2  1  0 |
+|-------+-----+-----+-----|
+|  1  0 |  dr |  db |  bg |
+
+2-bit tag b10
+2-bit   red channel difference from the previous pixel between -2..1
+2-bit green channel difference from the previous pixel between -2..1
+2-bit  blue channel difference from the previous pixel between -2..1
+
+The difference to the current channel values are using a wraparound operation, 
+so "1 - 2" will result in 255, while "255 + 1" will result in 0.
+
+
+ - QOI_DIFF_16 -------------------------------------
+|         Byte[0]         |         Byte[1]         |
+|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
+|----------+--------------|------------ +-----------|
+|  1  1  0 |   red diff   |  green diff | blue diff |
+
+3-bit tag b110
+5-bit   red channel difference from the previous pixel between -16..15
+4-bit green channel difference from the previous pixel between -8..7
+4-bit  blue channel difference from the previous pixel between -8..7
+
+The difference to the current channel values are using a wraparound operation, 
+so "10 - 13" will result in 253, while "250 + 7" will result in 1.
+
+
+ - QOI_DIFF_24 ---------------------------------------------------------------
+|         Byte[0]         |         Byte[1]         |         Byte[2]         |
+|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
+|-------------+----------------+--------------+----------------+--------------|
+|  1  1  1  0 |   red diff     |   green diff |    blue diff   |  alpha diff  |
+
+4-bit tag b1110
+5-bit   red channel difference from the previous pixel between -16..15
+5-bit green channel difference from the previous pixel between -16..15
+5-bit  blue channel difference from the previous pixel between -16..15
+5-bit alpha channel difference from the previous pixel between -16..15
+
+The difference to the current channel values are using a wraparound operation, 
+so "10 - 13" will result in 253, while "250 + 7" will result in 1.
+
+
+ - QOI_COLOR -------------
+|         Byte[0]         |
+|  7  6  5  4  3  2  1  0 |
+|-------------+--+--+--+--|
+|  1  1  1  1 |hr|hg|hb|ha|
+
+4-bit tag b1111
+1-bit   red byte follows
+1-bit green byte follows
+1-bit  blue byte follows
+1-bit alpha byte follows
+
+For each set bit hr, hg, hb and ha another byte follows in this order. If such a
+byte follows, it will replace the current color channel value with the value of
+this byte.
+
+
+The byte stream is padded at the end with 4 zero bytes. Size the longest chunk
+we can encounter is 5 bytes (QOI_COLOR with RGBA set), with this padding we just
+have to check for an overrun once per decode loop iteration.
+
+*/
+
+
+// -----------------------------------------------------------------------------
+// Header - Public functions
+
+#ifndef QOI_H
+#define QOI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A pointer to qoi_desc struct has to be supplied to all of qoi's functions. It
+// describes either the input format (for qoi_write, qoi_encode), or is filled
+// with the description read from the file header (for qoi_read, qoi_decode).
+
+// The colorspace in this qoi_desc is a bitmap with 0000rgba where a 0-bit 
+// indicates sRGB and a 1-bit indicates linear colorspace for each channel. You 
+// may use one of the predefined constants: QOI_SRGB, QOI_SRGB_LINEAR_ALPHA or 
+// QOI_LINEAR. The colorspace is purely informative. It will be saved to the
+// file header, but does not affect en-/decoding in any way.
+
+#define QOI_SRGB 0x00
+#define QOI_SRGB_LINEAR_ALPHA 0x01
+#define QOI_LINEAR 0x0f
+
+typedef struct {
+       unsigned int width;
+       unsigned int height;
+       unsigned char channels;
+       unsigned char colorspace;
+} qoi_desc;
+
+#ifndef QOI_NO_STDIO
+
+// Encode raw RGB or RGBA pixels into a QOI image and write it to the file 
+// system. The qoi_desc struct must be filled with the image width, height, 
+// number of channels (3 = RGB, 4 = RGBA) and the colorspace. 
+
+// The function returns 0 on failure (invalid parameters, or fopen or malloc 
+// failed) or the number of bytes written on success.
+
+int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
+
+
+// Read and decode a QOI image from the file system. If channels is 0, the
+// number of channels from the file header is used. If channels is 3 or 4 the
+// output format will be forced into this number of channels.
+
+// The function either returns NULL on failure (invalid data, or malloc or fopen
+// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct 
+// will be filled with the description from the file header.
+
+// The returned pixel data should be free()d after use.
+
+void *qoi_read(const char *filename, qoi_desc *desc, int channels);
+
+#endif // QOI_NO_STDIO
+
+
+// Encode raw RGB or RGBA pixels into a QOI image in memory.
+
+// The function either returns NULL on failure (invalid parameters or malloc 
+// failed) or a pointer to the encoded data on success. On success the out_len 
+// is set to the size in bytes of the encoded data.
+
+// The returned qoi data should be free()d after use.
+
+void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
+
+
+// Decode a QOI image from memory.
+
+// The function either returns NULL on failure (invalid parameters or malloc 
+// failed) or a pointer to the decoded pixels. On success, the qoi_desc struct 
+// is filled with the description from the file header.
+
+// The returned pixel data should be free()d after use.
+
+void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif // QOI_H
+
+
+// -----------------------------------------------------------------------------
+// Implementation
+
+#ifdef QOI_IMPLEMENTATION
+#include <stdlib.h>
+
+#ifndef QOI_MALLOC
+       #define QOI_MALLOC(sz) malloc(sz)
+       #define QOI_FREE(p)    free(p)
+#endif
+
+#define QOI_INDEX   0x00 // 00xxxxxx
+#define QOI_RUN_8   0x40 // 010xxxxx
+#define QOI_RUN_16  0x60 // 011xxxxx
+#define QOI_DIFF_8  0x80 // 10xxxxxx
+#define QOI_DIFF_16 0xc0 // 110xxxxx
+#define QOI_DIFF_24 0xe0 // 1110xxxx
+#define QOI_COLOR   0xf0 // 1111xxxx
+
+#define QOI_MASK_2  0xc0 // 11000000
+#define QOI_MASK_3  0xe0 // 11100000
+#define QOI_MASK_4  0xf0 // 11110000
+
+#define QOI_COLOR_HASH(C) (C.rgba.r ^ C.rgba.g ^ C.rgba.b ^ C.rgba.a)
+#define QOI_MAGIC \
+       (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
+        ((unsigned int)'i') <<  8 | ((unsigned int)'f'))
+#define QOI_HEADER_SIZE 14
+#define QOI_PADDING 4
+
+typedef union {
+       struct { unsigned char r, g, b, a; } rgba;
+       unsigned int v;
+} qoi_rgba_t;
+
+void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
+       bytes[(*p)++] = (0xff000000 & v) >> 24;
+       bytes[(*p)++] = (0x00ff0000 & v) >> 16;
+       bytes[(*p)++] = (0x0000ff00 & v) >> 8;
+       bytes[(*p)++] = (0x000000ff & v);
+}
+
+unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
+       unsigned int a = bytes[(*p)++];
+       unsigned int b = bytes[(*p)++];
+       unsigned int c = bytes[(*p)++];
+       unsigned int d = bytes[(*p)++];
+       return (a << 24) | (b << 16) | (c << 8) | d;
+}
+
+void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
+       if (
+               data == NULL || out_len == NULL || desc == NULL ||
+               desc->width == 0 || desc->height == 0 ||
+               desc->channels < 3 || desc->channels > 4 ||
+               (desc->colorspace & 0xf0) != 0
+       ) {
+               return NULL;
+       }
+
+       int max_size = 
+               desc->width * desc->height * (desc->channels + 1) + 
+               QOI_HEADER_SIZE + QOI_PADDING;
+
+       int p = 0;
+       unsigned char *bytes = QOI_MALLOC(max_size);
+       if (!bytes) {
+               return NULL;
+       }
+
+       qoi_write_32(bytes, &p, QOI_MAGIC);
+       qoi_write_32(bytes, &p, desc->width);
+       qoi_write_32(bytes, &p, desc->height);
+       bytes[p++] = desc->channels;
+       bytes[p++] = desc->colorspace;
+
+
+       const unsigned char *pixels = (const unsigned char *)data;
+
+       qoi_rgba_t index[64] = {0};
+
+       int run = 0;
+       qoi_rgba_t px_prev = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}};
+       qoi_rgba_t px = px_prev;
+       
+       int px_len = desc->width * desc->height * desc->channels;
+       int px_end = px_len - desc->channels;
+       int channels = desc->channels;
+
+       for (int px_pos = 0; px_pos < px_len; px_pos += channels) {
+               if (channels == 4) {
+                       px = *(qoi_rgba_t *)(pixels + px_pos);
+               }
+               else {
+                       px.rgba.r = pixels[px_pos];
+                       px.rgba.g = pixels[px_pos+1];
+                       px.rgba.b = pixels[px_pos+2];
+               }
+
+               if (px.v == px_prev.v) {
+                       run++;
+               }
+
+               if ( 
+                       run > 0 && 
+                       (run == 0x2020 || px.v != px_prev.v || px_pos == px_end)
+               ) {
+                       if (run < 33) {
+                               run -= 1;
+                               bytes[p++] = QOI_RUN_8 | run;
+                       }
+                       else {
+                               run -= 33;
+                               bytes[p++] = QOI_RUN_16 | run >> 8;
+                               bytes[p++] = run;
+                       }
+                       run = 0;
+               }
+
+               if (px.v != px_prev.v) {
+                       int index_pos = QOI_COLOR_HASH(px) % 64;
+
+                       if (index[index_pos].v == px.v) {
+                               bytes[p++] = QOI_INDEX | index_pos;
+                       }
+                       else {
+                               index[index_pos] = px;
+
+                               int vr = px.rgba.r - px_prev.rgba.r;
+                               int vg = px.rgba.g - px_prev.rgba.g;
+                               int vb = px.rgba.b - px_prev.rgba.b;
+                               int va = px.rgba.a - px_prev.rgba.a;
+                               
+                               if (
+                                       vr > -17 && vr < 16 && 
+                                       vg > -17 && vg < 16 && 
+                                       vb > -17 && vb < 16 && 
+                                       va > -17 && va < 16
+                               ) {
+                                       if (
+                                               va == 0 && 
+                                               vr > -3 && vr < 2 &&
+                                               vg > -3 && vg < 2 && 
+                                               vb > -3 && vb < 2
+                                       ) {
+                                               bytes[p++] = QOI_DIFF_8 | ((vr + 2) << 4) | (vg + 2) << 2 | (vb + 2);
+                                       }
+                                       else if (
+                                               va == 0 && 
+                                               vr > -17 && vr < 16 && 
+                                               vg >  -9 && vg <  8 && 
+                                               vb >  -9 && vb <  8
+                                       ) {
+                                               bytes[p++] = QOI_DIFF_16   | (vr + 16);
+                                               bytes[p++] = (vg + 8) << 4 | (vb +  8);
+                                       }
+                                       else {
+                                               bytes[p++] = QOI_DIFF_24    | (vr + 16) >> 1;
+                                               bytes[p++] = (vr + 16) << 7 | (vg + 16) << 2 | (vb + 16) >> 3;
+                                               bytes[p++] = (vb + 16) << 5 | (va + 16);
+                                       }
+                               }
+                               else {
+                                       bytes[p++] = QOI_COLOR | (vr ? 8 : 0) | (vg ? 4 : 0) | (vb ? 2 : 0) | (va ? 1 : 0);
+                                       if (vr) { bytes[p++] = px.rgba.r; }
+                                       if (vg) { bytes[p++] = px.rgba.g; }
+                                       if (vb) { bytes[p++] = px.rgba.b; }
+                                       if (va) { bytes[p++] = px.rgba.a; }
+                               }
+                       }
+               }
+               px_prev = px;
+       }
+
+       for (int i = 0; i < QOI_PADDING; i++) {
+               bytes[p++] = 0;
+       }
+
+       *out_len = p;
+       return bytes;
+}
+
+void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
+       if (
+               data == NULL || desc == NULL ||
+               (channels != 0 && channels != 3 && channels != 4) ||
+               size < QOI_HEADER_SIZE + QOI_PADDING
+       ) {
+               return NULL;
+       }
+
+       const unsigned char *bytes = (const unsigned char *)data;
+       int p = 0;
+
+       unsigned int header_magic = qoi_read_32(bytes, &p);
+       desc->width = qoi_read_32(bytes, &p);
+       desc->height = qoi_read_32(bytes, &p);
+       desc->channels = bytes[p++];
+       desc->colorspace = bytes[p++];
+
+       if (
+               desc->width == 0 || desc->height == 0 || 
+               desc->channels < 3 || desc->channels > 4 ||
+               header_magic != QOI_MAGIC
+       ) {
+               return NULL;
+       }
+
+       if (channels == 0) {
+               channels = desc->channels;
+       }
+
+       int px_len = desc->width * desc->height * channels;
+       unsigned char *pixels = QOI_MALLOC(px_len);
+       if (!pixels) {
+               return NULL;
+       }
+
+       qoi_rgba_t px = {.rgba = {.r = 0, .g = 0, .b = 0, .a = 255}};
+       qoi_rgba_t index[64] = {0};
+
+       int run = 0;
+       int chunks_len = size - QOI_PADDING;
+       for (int px_pos = 0; px_pos < px_len; px_pos += channels) {
+               if (run > 0) {
+                       run--;
+               }
+               else if (p < chunks_len) {
+                       int b1 = bytes[p++];
+
+                       if ((b1 & QOI_MASK_2) == QOI_INDEX) {
+                               px = index[b1 ^ QOI_INDEX];
+                       }
+                       else if ((b1 & QOI_MASK_3) == QOI_RUN_8) {
+                               run = (b1 & 0x1f);
+                       }
+                       else if ((b1 & QOI_MASK_3) == QOI_RUN_16) {
+                               int b2 = bytes[p++];
+                               run = (((b1 & 0x1f) << 8) | (b2)) + 32;
+                       }
+                       else if ((b1 & QOI_MASK_2) == QOI_DIFF_8) {
+                               px.rgba.r += ((b1 >> 4) & 0x03) - 2;
+                               px.rgba.g += ((b1 >> 2) & 0x03) - 2;
+                               px.rgba.b += ( b1       & 0x03) - 2;
+                       }
+                       else if ((b1 & QOI_MASK_3) == QOI_DIFF_16) {
+                               int b2 = bytes[p++];
+                               px.rgba.r += (b1 & 0x1f) - 16;
+                               px.rgba.g += (b2 >> 4)   -  8;
+                               px.rgba.b += (b2 & 0x0f) -  8;
+                       }
+                       else if ((b1 & QOI_MASK_4) == QOI_DIFF_24) {
+                               int b2 = bytes[p++];
+                               int b3 = bytes[p++];
+                               px.rgba.r += (((b1 & 0x0f) << 1) | (b2 >> 7)) - 16;
+                               px.rgba.g +=  ((b2 & 0x7c) >> 2) - 16;
+                               px.rgba.b += (((b2 & 0x03) << 3) | ((b3 & 0xe0) >> 5)) - 16;
+                               px.rgba.a +=   (b3 & 0x1f) - 16;
+                       }
+                       else if ((b1 & QOI_MASK_4) == QOI_COLOR) {
+                               if (b1 & 8) { px.rgba.r = bytes[p++]; }
+                               if (b1 & 4) { px.rgba.g = bytes[p++]; }
+                               if (b1 & 2) { px.rgba.b = bytes[p++]; }
+                               if (b1 & 1) { px.rgba.a = bytes[p++]; }
+                       }
+
+                       index[QOI_COLOR_HASH(px) % 64] = px;
+               }
+
+               if (channels == 4) { 
+                       *(qoi_rgba_t*)(pixels + px_pos) = px;
+               }
+               else {
+                       pixels[px_pos] = px.rgba.r;
+                       pixels[px_pos+1] = px.rgba.g;
+                       pixels[px_pos+2] = px.rgba.b;
+               }
+       }
+
+       return pixels;
+}
+
+#ifndef QOI_NO_STDIO
+#include <stdio.h>
+
+int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
+       FILE *f = fopen(filename, "wb");
+       if (!f) {
+               return 0;
+       }
+
+       int size;
+       void *encoded = qoi_encode(data, desc, &size);
+       if (!encoded) {
+               fclose(f);
+               return 0;
+       }       
+       
+       fwrite(encoded, 1, size, f);
+       fclose(f);
+       
+       QOI_FREE(encoded);
+       return size;
+}
+
+void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
+       FILE *f = fopen(filename, "rb");
+       if (!f) {
+               return NULL;
+       }
+
+       fseek(f, 0, SEEK_END);
+       int size = ftell(f);
+       fseek(f, 0, SEEK_SET);
+
+       void *data = QOI_MALLOC(size);
+       if (!data) {
+               fclose(f);
+               return NULL;
+       }
+
+       int bytes_read = fread(data, 1, size, f);
+       fclose(f);
+
+       void *pixels = qoi_decode(data, bytes_read, desc, channels);
+       QOI_FREE(data);
+       return pixels;
+}
+
+#endif // QOI_NO_STDIO
+#endif // QOI_IMPLEMENTATION
+
diff --git a/sprites_autocombine.h b/sprites_autocombine.h
new file mode 100644 (file)
index 0000000..2808eb0
--- /dev/null
@@ -0,0 +1,15 @@
+static enum sprites_auto_combine_index
+{
+       k_sprite_donut,
+       k_sprite_jack_1,
+       k_sprite_jack_2,
+       k_sprite_peg,
+};
+
+static struct vg_sprite sprites_auto_combine[] = 
+{
+       { 0, 0, 8192, 8192 },
+       { 8192, 0, 4096, 4096 },
+       { 12288, 0, 4096, 4096 },
+       { 16384, 0, 4096, 4096 },
+};
\ No newline at end of file
diff --git a/texsheet.c b/texsheet.c
new file mode 100644 (file)
index 0000000..f420866
--- /dev/null
@@ -0,0 +1,214 @@
+// Copyright (C) 2021 Harry Godden (hgn) - All Rights Reserved
+
+#define VG_TOOLS
+#include "vg/vg.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb/stb_image.h"
+
+struct image_src
+{
+       int x,y,ch;
+
+       u8 *data;
+};
+
+int image_sort( const void* a, const void* b)
+{
+       struct image_src *p_a = (struct image_src *)a;
+       struct image_src *p_b = (struct image_src *)b;
+
+       if( p_a->x == p_b->x )
+               return 0;
+       else if ( p_a->x < p_b->x )
+               return 1;
+       else 
+               return -1;
+}
+
+int main( int argc, const char *argv[] )
+{
+       struct image_src *source_images = malloc( sizeof( struct image_src ) * argc );
+
+       u32 num_images = 0;
+       
+       if( argc < 4 )
+       {
+               vg_error( "Missing output file paths\n" );
+               return 0;
+       }
+
+       // Open header handle
+       // ------------------
+       FILE *fp = fopen( argv[2], "w" );
+       if( !fp )
+       {
+               vg_error( "Could not open file for writing\n" );
+               return 0;
+       }
+
+       fprintf( fp, "static enum %s_index\n{\n", argv[3] );
+
+       // Load images
+       // -----------
+       stbi_set_flip_vertically_on_load(1);
+
+       for( int i = 4; i < argc; i ++ )
+       {
+               struct image_src *src = &source_images[ num_images ];
+               src->data = (u8 *)stbi_load( argv[i], &src->x, &src->y, &src->ch, 4 );
+               
+               char name[ 256 ];
+               int j = 0; int ext = 0;
+               for( ; j < vg_list_size( name )-1; j ++ )
+               {
+                       if( argv[i][j] )
+                       {
+                               name[j] = argv[i][j];
+
+                               if( name[j] == '.' )
+                                       ext = j;
+
+                               if( name[j] == '.' || name[j] == '-' )
+                                       name[j] = '_';
+                       }
+                       else
+                               break;
+               }
+
+               if( ext )
+                       name[ext] = 0x00;
+               else
+                       name[j] = 0x00;
+
+               fprintf( fp, "\tk_sprite_%s,\n", name );
+
+               if( src->data )
+               {
+                       if( src->x != src->y )
+                       {
+                               vg_error( "Non-square images are currently not supported ('%s')\n", argv[i] );
+                               free( src->data );
+                       }
+                       else
+                               num_images ++;
+               }
+               else
+                       vg_error( "Could not decode '%s'\n", argv[i] );
+       }
+       
+       fprintf( fp, "};\n\n" );
+
+       // Sort by size
+       // ------------
+       qsort( source_images, num_images, sizeof(struct image_src), image_sort );
+
+       // Process images
+       // --------------
+       fprintf( fp, "static struct vg_sprite %s[] = \n{\n", argv[3] );
+
+       u8 *dest = (u8 *)malloc( 1024*1024*4 );
+       
+       // Clear (temp)
+       for( int i = 0; i < 1024*1024; i ++ )
+       {
+               dest[ i*4 + 0 ] = 0;
+               dest[ i*4 + 1 ] = 0;
+               dest[ i*4 + 2 ] = 128;
+               dest[ i*4 + 3 ] = 255;
+       }
+
+       struct region
+       {
+               v2i p0;
+               v2i p1;
+       }
+       region_stack[ 32 ] =
+       {
+               {
+                       .p0 = { 0, 0 },
+                       .p1 = { 1024, 1024 }
+               }
+       };
+       int stack_h = 0;
+
+       int sf = 64;
+
+       for( int i = 0; i < num_images; i ++ )
+       {
+               struct image_src *psrc = &source_images[ i ];
+
+               // Region checks
+               while( 1 )
+               {
+                       struct region *pregion = &region_stack[ stack_h ];
+
+                       if( (pregion->p0[ 0 ] + psrc->x <= pregion->p1[0]) && (pregion->p0[ 1 ] + psrc->y <= pregion->p1[1]) )
+                       {
+                               // Passed, add image and create subdivisions
+                               fprintf( fp, "\t{ %hu, %hu, %hu, %hu },\n", 
+                                       (u16)(sf * pregion->p0[0]), 
+                                       (u16)(sf * pregion->p0[1]), 
+                                       (u16)(sf * psrc->x),
+                                       (u16)(sf * psrc->y)
+                               );
+
+                               // Write image
+                               for( int y = 0; y < psrc->y; y ++ )
+                               {
+                                       int px = pregion->p0[0];
+                                       int py = pregion->p0[1] + y;
+
+                                       memcpy( &dest[ (py*1024+px) * 4 ], &psrc->data[ y*psrc->x*4 ], psrc->x*4 );
+                               }
+
+                               // Subdivisions
+                               stack_h ++;
+                               struct region *new_region = &region_stack[ stack_h ];
+
+                               new_region->p0[0] = pregion->p0[0] + psrc->x;
+                               new_region->p0[1] = pregion->p0[1];
+                               new_region->p1[0] = pregion->p1[0];
+                               new_region->p1[1] = pregion->p0[1] + psrc->y;
+
+                               pregion->p0[ 1 ] += psrc->y;
+                               break;
+                       }
+                       else
+                       {
+                               // Failed, loop up to next region if can
+                               if( stack_h == 0 )
+                               {
+                                       vg_error( "Could not fit image %d. Pack failed\n", i );
+
+                                       goto IL_END_ERR;
+                               }
+                               else
+                                       stack_h --;
+                       }
+               }
+       }
+
+IL_END_ERR:
+       fprintf( fp, "};" );
+       fclose( fp );
+
+       // Write output
+       // ------------
+       qoi_write( argv[1], dest, &(qoi_desc){
+               .width = 1024,
+               .height = 1024,
+               .channels = 4,
+               .colorspace = QOI_SRGB
+       });
+
+       // Free
+       // ----
+       for( int i = 0; i < num_images; i ++ )
+               free( source_images[ i ].data );
+       free( dest );
+       free( source_images );
+
+       vg_success( "Processed %u images\n", num_images );
+
+}
diff --git a/textures_combine/donut.png b/textures_combine/donut.png
new file mode 100644 (file)
index 0000000..a585c5e
Binary files /dev/null and b/textures_combine/donut.png differ
diff --git a/textures_combine/jack-1.png b/textures_combine/jack-1.png
new file mode 100644 (file)
index 0000000..7d88b87
Binary files /dev/null and b/textures_combine/jack-1.png differ
diff --git a/textures_combine/jack-2.png b/textures_combine/jack-2.png
new file mode 100644 (file)
index 0000000..92488da
Binary files /dev/null and b/textures_combine/jack-2.png differ
diff --git a/textures_combine/peg.png b/textures_combine/peg.png
new file mode 100644 (file)
index 0000000..83b366b
Binary files /dev/null and b/textures_combine/peg.png differ
index e97900fed12054673ab7d5a67abe3d85661ecfa4..c757b1826a42996aa643fe51259195e5d7d66c3f 100644 (file)
@@ -12,6 +12,13 @@ struct vg_tex2d
        GLuint name;
 };
 
+#pragma pack(push,1)
+struct vg_sprite
+{
+       u16 uvx, uvy, w, h;
+};
+#pragma pack(pop)
+
 static void vg_tex2d_bind( vg_tex2d *tex, u32 id )
 {
        glActiveTexture( GL_TEXTURE0 + id );