hooks and shaders into metacompiler tool
authorhgn <hgodden00@gmail.com>
Sat, 4 Oct 2025 22:21:38 +0000 (22:21 +0000)
committerhgn <hgodden00@gmail.com>
Sat, 4 Oct 2025 22:21:38 +0000 (22:21 +0000)
foundation.kv
include/common_api.h
include/engine_interface.h
shaders/blit_tex.fs
source/engine/main.c
source/engine/shader.c
source/foundation/logging.c
source/foundation/stream.c
source/foundation/string.c
source/tools/metacompiler.c

index f375ddc5edf3d40300b3cf388e0dbbd0461d3654..fb1c376617b2dc6f7e5e70321a3d293265e71087 100644 (file)
@@ -217,8 +217,24 @@ add source/foundation/async.c
    shader
    {
       name blit
-      fragment shaders/blit.vs
-      vertex shaders/blit.fs
+
+      subshader
+      {
+         type vertex
+         add shaders/blit.vs
+         
+         uniform
+         {
+            type sampler2D
+            alias uTexMain
+         }
+      }
+
+      subshader
+      {
+         type fragment
+         add shaders/blit_tex.fs
+      }
    }
 
    cvar
@@ -232,17 +248,27 @@ add source/foundation/async.c
 
    event
    {
-      name TEST_HOOK
+      name ENGINE_INIT
+      prototype "void"
    }
-
    event
    {
-      name WORLD_RENDER
+      name ENGINE_FIXED_UPDATE
+      prototype "void"
    }
-
-   hook
+   event
+   {
+      name ENGINE_UPDATE
+      prototype "void"
+   }
+   event
+   {
+      name ENGINE_RENDER
+      prototype "void"
+   }
+   event
    {
-      event TEST_HOOK
-      function _run_on_hook
+      name ENGINE_UI
+      prototype "void"
    }
 }
index a38dca21ebdfdc9f41f53be4ae2935308842a0d8..2c3c7b72c39babcf39bd37a8a0f4599d9f575f96 100644 (file)
@@ -169,6 +169,7 @@ enum stream_flag
    k_stream_procedural = 0x20,
    k_stream_stack = 0x40,
    k_stream_posix = 0x80,
+   k_stream_auto = 0x100,
 
    /* modes */
    k_stream_write = 0x1,
@@ -193,6 +194,7 @@ struct stream
    };
 };
 
+void stream_open_auto( struct stream *stream, u32 flags );
 void stream_open_stack( struct stream *stream, struct stack_allocator *stack, u32 flags );
 void stream_open_buffer_write( struct stream *stream, void *buffer, u32 buffer_length, u32 flags );
 void stream_open_buffer_read( struct stream *stream, const void *buffer, u32 buffer_length, u32 flags );
@@ -362,3 +364,8 @@ const c8 *directory_entry_name( struct directory *directory );
 bool directory_next_entry( struct directory *directory );
 enum directory_entry_type directory_entry_type( struct directory *directory );
 void directory_close( struct directory *directory );
+
+#define EVENT_CALL( NAME, ... ) \
+   for( u32 ev_iter=0; _event_##NAME##_subscribers[ ev_iter ]; ev_iter ++ ) \
+      _event_##NAME##_subscribers[ ev_iter ]( __VA_ARGS__ );
+
index c0dd5bcdce6cbdea4f4fb20d19f99a49c4995193..be2c03e40b3718882ba0f5c5a6deb09426f409a1 100644 (file)
@@ -17,14 +17,6 @@ struct _engine
 }
 extern _engine;
 
-IMPL void _engine_init(void);
-IMPL void _engine_fixed_update(void);
-IMPL void _engine_update(void);
-IMPL void _engine_render(void);
-IMPL void _engine_ui(void);
-
-API  void _engine_something(void);
-
 enum input_type
 {
    k_input_type_action,
index 634e84fa2908e992bb701d7dbb90135a66bae201..0d39a1b503a36effb6ed17b80da349dbb30748a5 100644 (file)
@@ -1,41 +1,7 @@
 out vec4 FragColor;
-uniform sampler2D uTexMain;
-
 in vec2 aUv;
 
-float kPi = 3.14159265358979;
-
-vec2 fisheye_distort(vec2 xy)
-{
-   float aperture = 1350.0;
-   float apertureHalf = 0.5 * aperture * (kPi / 180.0);
-   float maxFactor = sin(apertureHalf);
-
-   vec2 uv;
-   float d = length(xy);
-   if(d < (2.0-maxFactor))
-   {
-      d = length(xy * maxFactor);
-      float z = sqrt(1.0 - d * d);
-      float r = atan(d, z) / kPi;
-      float phi = atan(xy.y, xy.x);
-
-      uv.x = r * cos(phi) + 0.5;
-      uv.y = r * sin(phi) + 0.5;
-   }
-   else
-   {
-      uv = 0.5*xy + 0.5;
-   }
-   
-   return uv;
-}
-
-
 void main()
 {
-   vec2 vwarp = 2.0*aUv - 1.0;
-   vwarp = fisheye_distort( vwarp );
-
    FragColor = texture( uTexMain, aUv );
 }
index e7da0d3cf185498badc8fb2eabb5ff42df043387..8f4d93878daaeb38624b36166d27456b80370ec3 100644 (file)
@@ -7,6 +7,7 @@
 
 // TODO: temp
 #include "console_core.h"
+#include "generated/hooks.h"
 
 struct _engine _engine;
 
@@ -53,7 +54,7 @@ i32 main( i32 argc, const c8 *argv[] )
    //if( vg_long_opt( "no-steam", "Disable Steam integration (Good idea for pirating)" ) )
    //   _steam_api.disabled = 1;
    
-   _engine_init();
+   EVENT_CALL( ENGINE_INIT );
    _options_check_end();
 
    ASSERT_CRITICAL( glfwInit() );
@@ -128,7 +129,7 @@ L_new_frame:;
          fixed_accumulator -= 1.0/60.0;
          fixed_time += _engine.time_delta;
          _engine.time = fixed_time;
-         _engine_fixed_update();
+         EVENT_CALL( ENGINE_FIXED_UPDATE );
       }
       else break;
    }
@@ -137,12 +138,12 @@ L_new_frame:;
    _engine.time_fixed_extrapolate = fixed_accumulator / (1.0/60.0);
 
    /* normal update */
-   _engine_update();
+   EVENT_CALL( ENGINE_UPDATE );
 
    glfwGetFramebufferSize( _engine.window_handle, &_engine.w, &_engine.h );
-   _engine_render();
+   EVENT_CALL( ENGINE_RENDER );
    _engine_ui_pre_render();
-   _engine_ui();
+   EVENT_CALL( ENGINE_UI );
    if( _engine_backend.console_open )
       _engine_console_ui();
    _engine_ui_post_render();
index d778c43162e25f6917369bfabcf6d81597e9aa44..2d2b3b2fbbee25d0393e863dc923639b72255126 100644 (file)
@@ -1,4 +1,7 @@
 #include "opengl.h"
+#include "generated/shaders.h"
+
+GLuint _uniform_locations[ k_shader_uniform_count ];
 
 GLuint compile_opengl_subshader( GLint type, const c8 *src, bool critical, const c8 *debug_path )
 {
@@ -66,3 +69,27 @@ GLuint compile_opengl_shader( const c8 *vs, const c8 *fs )
        glDeleteShader( frag );
    return program;
 }
+
+struct shader
+{
+   const c8 *name;
+   u32 subshader_count, uniform_start, uniform_count;
+
+   struct subshader 
+   {
+      enum subshader_type
+      {
+         k_subshader_vertex,
+         k_subshader_fragment,
+         k_subshader_geometry
+      }
+      type;
+      u32 source_count;
+      const c8 **source_list;
+      const c8 *source_static;
+      const c8 *source_uniforms;
+   }
+   *subshaders;
+};
+
+#include "generated/shaders.c"
index b3eee609b27f69abb3f775d3d6a525bb488eab16..a90917dfed99e29063787b2485f7c60f578842d1 100644 (file)
@@ -36,6 +36,8 @@ void _log_add_listener( void (*fn)(const c8 *line, u32 length, u32 type), u32 fi
 
 static u32 _log_stream_passthrough( struct stream *stream, const void *buffer, u32 length )
 {
+   // TODO: Allow colour codes into the line buffer, but those dont count towards line length.
+   //       the AFTERWARDS, filter out the codes and send those to the listeners.
    for( u32 i=0; i<length; i ++ )
    {
       u8 c = ((u8 *)buffer)[i];
@@ -73,7 +75,7 @@ static u32 _log_stream_passthrough( struct stream *stream, const void *buffer, u
 
             if( _writing_event && (nl||wrap) )
             {
-               while( _listeners.line_length < 23 )
+               while( _listeners.line_length < 33 )
                   _listeners.line[ _listeners.line_length ++ ] = ' ';
                _listeners.line[ _listeners.line_length ++ ] = '.';
             }
@@ -112,7 +114,7 @@ struct stream *_log_event( u32 type, const c8 *code_location )
    }
    string_append( output, code_location, 0 );
 
-   while( (output->offset-line_start) < 20 ) string_append_c8( output, ' ' );
+   while( (output->offset-line_start) < 30 ) string_append_c8( output, ' ' );
         if( type == $error )   string_append( output, KRED "ERR|", 0 );
    else if( type == $warning ) string_append( output, KYEL "WRN|", 0 );
    else if( type == $ok )      string_append( output, KGRN "OK |", 0 );
index 156969515292bccfc8149916ccd11268bf062c77..b4c8d8a4ebd575b5fe206e0c00998e3f5b3baa74 100644 (file)
@@ -17,6 +17,20 @@ void stream_open_stack( struct stream *stream, struct stack_allocator *stack, u3
    }
 }
 
+void stream_open_auto( struct stream *stream, u32 flags )
+{
+   stream->flags = flags | k_stream_write | k_stream_auto;
+   stream->buffer_length = 256;
+   stream->buffer_write = _heap_allocate( 256 );
+   stream->offset = 0;
+
+   if( flags & k_stream_null_terminate )
+   {
+      stream->buffer_length --;
+      ((u8 *)stream->buffer_write)[0] = 0;
+   }
+}
+
 static void stream_open_buffer_all( struct stream *stream, u32 buffer_length, u32 flags )
 {
    stream->flags = flags | k_stream_buffer;
@@ -57,7 +71,7 @@ bool stream_open_file( struct stream *stream, const c8 *path, u32 flags )
       return 1;
    else
    {
-      $log( $error, {"Failure to open file stream "}, $errno() );
+      $log( $error, {"Failure to open file stream '"}, {path}, {"' "}, $errno() );
       return 0;
    }
 }
@@ -142,6 +156,15 @@ u32 stream_write( struct stream *stream, const void *buffer, u32 length )
       write_length = length;
    }
 
+   while( (stream->flags & k_stream_auto) && (write_length < length) )
+   {
+      u32 new_size = stream->buffer_length + 2048;
+      u32 alloc_size = (stream->flags & k_stream_null_terminate)? new_size+1: new_size;
+      stream->buffer_write = _heap_reallocate( stream->buffer_write, alloc_size );
+      stream->buffer_length = new_size;
+      write_length = stream_usable_length( stream, length );
+   }
+
    if( stream->flags & k_stream_posix )
    {
       u64 l = fwrite( buffer, 1, write_length, stream->posix_stream );
index 256d601917cbc392e97d1ae232105d5eb8df961d..9a9a0632cc92584dacee8f8a25af6be1cea7062b 100644 (file)
@@ -3,7 +3,7 @@
 
 const c8 *string_get( struct stream *string )
 {
-   ASSERT_CRITICAL( string->flags & (k_stream_stack|k_stream_buffer) );
+   ASSERT_CRITICAL( string->flags & (k_stream_stack|k_stream_buffer|k_stream_auto) );
    ASSERT_CRITICAL( string->flags & k_stream_null_terminate );
    return (const c8 *)string->buffer_write;
 }
index f86c734bd6a385147e29ea291d553e3f3ddba4f8..680c6807e700bfd5edd6db544b9ce9e287a61a46 100644 (file)
@@ -23,6 +23,22 @@ struct
 }
 static _threads;
 
+struct hook_event
+{
+   struct stream alias, returns, proto;
+   struct stretchy_allocator users;
+};
+struct hook_user
+{
+   struct stream function_name;
+};
+struct
+{
+   bool using;
+   struct stretchy_allocator events;
+}
+static _hooks;
+
 struct
 {
    bool using;
@@ -31,6 +47,35 @@ struct
 }
 static _console;
 
+enum subshader_type
+{
+   k_subshader_vertex,
+   k_subshader_fragment,
+   k_subshader_geometry
+};
+struct shader
+{
+   struct stream name;
+   struct subshader
+   {
+      enum subshader_type type;
+      struct stream debug_source_list, static_source, uniform_source;
+      u32 debug_source_count;
+   }
+   subshaders[3];
+   u32 subshader_count;
+
+   u32 uniform_start, uniform_count;
+};
+struct
+{
+   bool using;
+   struct stream shader_enum, uniform_enum, uniform_aliases, uniform_func_protos, uniform_funcs;
+   u32 uniform_count;
+   struct stretchy_allocator shaders;
+}
+static _shaders;
+
 struct block_context
 {
    enum target_type 
@@ -41,7 +86,9 @@ struct block_context
       k_target_shader,
       k_target_cvar,
       k_target_ccmd,
-      k_target_thread
+      k_target_thread,
+      k_target_hook,
+      k_target_subshader
    }
    target;
    u32 feature_count;
@@ -54,92 +101,254 @@ void _parse_kv_block( struct keyvalues *kvs, u32 block, struct block_context con
    const c8 *block_key = keyvalues_key( kvs, block, NULL );
    if( block_key != NULL )
    {
-      ASSERT_CRITICAL( context.target == k_target_main );
-      if( compare_buffers( block_key, 0, "input_layer", 0 )) 
+      if( context.target == k_target_shader )
+      {
+         if( compare_buffers( block_key, 0, "subshader", 0 )) 
+         {
+            context.target = k_target_subshader;
+
+            struct shader *shader = stretchy_get( &_shaders.shaders, stretchy_count( &_shaders.shaders ) -1 );
+            struct subshader *subshader = &shader->subshaders[ shader->subshader_count ++ ];
+
+            const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
+            ASSERT_CRITICAL( type );
+
+            if( compare_buffers( type, 0, "vertex", 0 ) )
+               subshader->type = k_subshader_vertex;
+            else if( compare_buffers( type, 0, "fragment", 0 ) )
+               subshader->type = k_subshader_fragment;
+            else if( compare_buffers( type, 0, "geometry", 0 ) )
+               subshader->type = k_subshader_geometry;
+            else
+            {
+               $log( $warning, {"Invalid subshader type '"}, {type}, {"'"} );
+               return;
+            }
+
+            subshader->debug_source_count = 0;
+            stream_open_auto( &subshader->debug_source_list, k_stream_null_terminate );
+            stream_open_auto( &subshader->static_source, k_stream_null_terminate );
+            stream_open_auto( &subshader->uniform_source, k_stream_null_terminate );
+         }
+         else
+         {
+            $log( $warning, {"We don't have a compiler:shader definition for block '"}, {block_key}, {"'"} );
+            return;
+         }
+      }
+      else if( context.target == k_target_subshader )
       {
-         _input.using = 1;
-         context.target = k_target_input_layer;
-         const c8 *layer_name = keyvalues_read_string( kvs, block, "name", NULL );
-         ASSERT_CRITICAL( layer_name );
+         struct shader *shader = stretchy_get( &_shaders.shaders, stretchy_count( &_shaders.shaders ) -1 );
+         struct subshader *subshader = &shader->subshaders[ shader->subshader_count -1 ];
 
-         $v_string( &_input.layer_enums, {"   k_input_layer_"}, {layer_name}, {",\n"} );
+         if( compare_buffers( block_key, 0, "uniform", 0 )) 
+         {
+            const c8 *alias = keyvalues_read_string( kvs, block, "alias", NULL );
+            const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
+            ASSERT_CRITICAL( type && alias );
+            
+            struct glsl_trans
+            {
+               const c8 *type, *args, *call, *end;
+            }
+            type_list[] =
+            {
+               { "int",          "i32 b",    "glUniform1i",            "b" },
+               { "vec2",         "v2f v",    "glUniform2fv",           "1,v" },
+               { "vec3",         "v3f v",    "glUniform3fv",           "1,v" },
+               { "vec4",         "v4f v",    "glUniform4fv",           "1,v" },
+               { "bool",         "i32 b",    "glUniform1i",            "b" },
+               { "mat2",         "m2x2f m",  "glUniformMatrix2fv",     "1,GL_FALSE,(f32*)m" },
+               { "mat3",         "m3x3f m",  "glUniformMatrix3fv",     "1,GL_FALSE,(f32*)m" },
+               { "mat4",         "m4x4f m",  "glUniformMatrix4fv",     "1,GL_FALSE,(f32*)m" },
+               { "float",        "f32 f",    "glUniform1f",            "f" },
+               { "mat4x3",       "m4x3f m",  "glUniformMatrix4x3fv",   "1,GL_FALSE,(f32*)m" },
+               { "sampler2D",    "i32 i",    "glUniform1i",            "i" },
+               { "usampler3D",   "i32 i",    "glUniform1i",            "i" },
+               { "samplerCube",  "i32 i",    "glUniform1i",            "i" },
+               { "samplerBuffer","i32 i",    "glUniform1i",            "i" },
+            },
+            *trans = NULL;
+
+            for( u32 i=0; i<ARRAY_COUNT( type_list ); i ++ )
+            {
+               if( compare_buffers( type_list[i].type, 0, type, 0 ) )
+               {
+                  trans = &type_list[i];
+                  break;
+               }
+            }
+            ASSERT_CRITICAL( trans );
+
+            $v_string( &_shaders.uniform_enum, {"   k_shader_"}, {string_get(&shader->name)}, {"_"}, {alias}, {",\n"} );
+            $v_string( &_shaders.uniform_aliases, {"   [k_shader_"}, {string_get(&shader->name)}, {"_"}, {alias}, {"] = \""},
+                  {alias},{"\",\n"} );
+            $v_string( &subshader->uniform_source, {"uniform "}, {type}, {" "}, {alias}, {"\\n"} );
+
+            $v_string( &_shaders.uniform_func_protos, {"void _shader"}, {string_get(&shader->name)}, {"_"}, {alias}, {"( "},{trans->args},{" );\n"} );
+            $v_string( &_shaders.uniform_funcs, {"void _shader"}, {string_get(&shader->name)}, {"_"}, {alias}, {"( "},{trans->args},{" )"},
+                     {"{ "},{trans->call},{"( _uniform_locations["}, 
+                        {"k_shader_"}, {string_get(&shader->name)}, {"_"}, {alias},
+                     {"], "},{trans->end},{" ); }\n"});
+
+            shader->uniform_count ++;
+            _shaders.uniform_count ++;
+         }
+         else
+         {
+            $log( $warning, {"We don't have a compiler:subshader definition for block '"}, {block_key}, {"'"} );
+            return;
+         }
       }
-      else if( compare_buffers( block_key, 0, "input", 0 )) 
+      else
       {
-         _input.using = 1;
-         context.target = k_target_input;
+         ASSERT_CRITICAL( context.target == k_target_main );
+         if( compare_buffers( block_key, 0, "input_layer", 0 )) 
+         {
+            _input.using = 1;
+            context.target = k_target_input_layer;
+            const c8 *layer_name = keyvalues_read_string( kvs, block, "name", NULL );
+            ASSERT_CRITICAL( layer_name );
+
+            $v_string( &_input.layer_enums, {"   k_input_layer_"}, {layer_name}, {",\n"} );
+         }
+         else if( compare_buffers( block_key, 0, "input", 0 )) 
+         {
+            _input.using = 1;
+            context.target = k_target_input;
 
-         const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
-         ASSERT_CRITICAL( name );
+            const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
+            ASSERT_CRITICAL( name );
 
-         const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
-         ASSERT_CRITICAL( type );
+            const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
+            ASSERT_CRITICAL( type );
 
-         const c8 *layer_mask = keyvalues_read_string( kvs, block, "layer_mask", NULL );
-         
-         $v_string( &_input.input_enums, {"   k_input_"},{type},{"_"}, {name}, {",\n"} );
-         $v_string( &_input.input_structures, {"   [k_input_"},{type},{"_"},{name},{"]=\n   {\n"} );
-         $v_string( &_input.input_structures, {"      .name = \""},{name},{"\",\n"} );
-         $v_string( &_input.input_structures, {"      .type = k_input_type_"},{type},{",\n"} );
+            const c8 *layer_mask = keyvalues_read_string( kvs, block, "layer_mask", NULL );
+            
+            $v_string( &_input.input_enums, {"   k_input_"},{type},{"_"}, {name}, {",\n"} );
+            $v_string( &_input.input_structures, {"   [k_input_"},{type},{"_"},{name},{"]=\n   {\n"} );
+            $v_string( &_input.input_structures, {"      .name = \""},{name},{"\",\n"} );
+            $v_string( &_input.input_structures, {"      .type = k_input_type_"},{type},{",\n"} );
 
-         if( layer_mask )
-            $v_string( &_input.input_structures, {"      .layer_mask = 1<<k_input_layer_"}, {layer_mask}, {",\n"} );
+            if( layer_mask )
+               $v_string( &_input.input_structures, {"      .layer_mask = 1<<k_input_layer_"}, {layer_mask}, {",\n"} );
 #if 0
-         const c8 *keyboard_key = keyvalues_read_string( kvs, block, "keyboard", NULL );
-         if( keyboard_key )
-            $v_string( &_input.button_structures, {"      .key_id = GLFW_KEY_"}, {keyboard_key}, {",\n"} );
+            const c8 *keyboard_key = keyvalues_read_string( kvs, block, "keyboard", NULL );
+            if( keyboard_key )
+               $v_string( &_input.button_structures, {"      .key_id = GLFW_KEY_"}, {keyboard_key}, {",\n"} );
 #endif
-         $v_string( &_input.input_structures, {"   },\n"} );
-      }
-      else if( compare_buffers( block_key, 0, "cvar", 0 )) 
-      {
-         _console.using = 1;
-         context.target = k_target_cvar;
-         const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
-         const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
-         const c8 *default_value = keyvalues_read_string( kvs, block, "default", NULL );
-         ASSERT_CRITICAL( name && type && default_value );
-
-         $v_string( &_console.cvar_header, {"extern "}, {type}, {" cvar_"}, {name}, {";\n"} );
-         $v_string( &_console.cvar_definitions, {type}, {" cvar_"}, {name}, {" = "}, {default_value}, {";\n"} );
-      }
-      else if( compare_buffers( block_key, 0, "ccmd", 0 )) 
-      {
-         _console.using = 1;
-         _console.command_count ++;
-         context.target = k_target_ccmd;
-         const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
-         const c8 *function = keyvalues_read_string( kvs, block, "function", NULL );
-         ASSERT_CRITICAL( name && function );
-         $v_string( &_console.command_definitions, {"   {\n      .alias = \""}, {name}, {"\",\n      .fn = "}, 
-                                                   {function}, {"\n   },\n"} );
-         $v_string( &_console.command_prototypes, {"i32 "}, {function}, {"( struct console_arguments *args );\n"} );
-         // TODO: process parameter descriptions
-         return;
-      }
-      else if( compare_buffers( block_key, 0, "shader", 0 )) 
-         context.target = k_target_shader;
-      else if( compare_buffers( block_key, 0, "thread", 0 )) 
-      {
-         _threads.using = 1;
-         context.target = k_target_thread;
-         const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
-         ASSERT_CRITICAL( name );
+            $v_string( &_input.input_structures, {"   },\n"} );
+         }
+         else if( compare_buffers( block_key, 0, "cvar", 0 )) 
+         {
+            _console.using = 1;
+            context.target = k_target_cvar;
+            const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
+            const c8 *type = keyvalues_read_string( kvs, block, "type", NULL );
+            const c8 *default_value = keyvalues_read_string( kvs, block, "default", NULL );
+            ASSERT_CRITICAL( name && type && default_value );
+
+            $v_string( &_console.cvar_header, {"extern "}, {type}, {" cvar_"}, {name}, {";\n"} );
+            $v_string( &_console.cvar_definitions, {type}, {" cvar_"}, {name}, {" = "}, {default_value}, {";\n"} );
+         }
+         else if( compare_buffers( block_key, 0, "ccmd", 0 )) 
+         {
+            _console.using = 1;
+            _console.command_count ++;
+            context.target = k_target_ccmd;
+            const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
+            const c8 *function = keyvalues_read_string( kvs, block, "function", NULL );
+            ASSERT_CRITICAL( name && function );
+            $v_string( &_console.command_definitions, {"   {\n      .alias = \""}, {name}, {"\",\n      .fn = "}, 
+                                                      {function}, {"\n   },\n"} );
+            $v_string( &_console.command_prototypes, {"i32 "}, {function}, {"( struct console_arguments *args );\n"} );
+            // TODO: process parameter descriptions
+            return;
+         }
+         else if( compare_buffers( block_key, 0, "shader", 0 )) 
+         {
+            _shaders.using = 1;
+            context.target = k_target_shader;
+            struct shader *shader = stretchy_append( &_shaders.shaders );
+            shader->subshader_count = 0;
+            shader->uniform_start = _shaders.uniform_count;
+            shader->uniform_count = 0;
+
+            stream_open_auto( &shader->name, k_stream_null_terminate );
+            $v_string( &shader->name, {keyvalues_read_string( kvs, block, "name", NULL )} );
+            $v_string( &_shaders.shader_enum, {"   k_shader_"}, {string_get(&shader->name)}, {",\n"} );
+         }
+         else if( compare_buffers( block_key, 0, "thread", 0 )) 
+         {
+            _threads.using = 1;
+            context.target = k_target_thread;
+            const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
+            ASSERT_CRITICAL( name );
 
-         $v_string( &_threads.enums, {"   k_thread_"},{name}, {",\n"} );
-         $v_string( &_threads.structures, {"   [k_thread_"},{name},{"]=\n   {\n"} );
-         $v_string( &_threads.structures, {"      .name = \""}, {name}, {"\",\n"} );
+            $v_string( &_threads.enums, {"   k_thread_"},{name}, {",\n"} );
+            $v_string( &_threads.structures, {"   [k_thread_"},{name},{"]=\n   {\n"} );
+            $v_string( &_threads.structures, {"      .name = \""}, {name}, {"\",\n"} );
 
-         u32 queue_size_m = 0;
-         if( keyvalues_read_u32s( kvs, block, "queue_size_m", NULL, &queue_size_m, 1 ) )
-            $v_string( &_threads.structures, {"      .queue_size_m = "}, $unsigned(queue_size_m), {",\n"} );
+            u32 queue_size_m = 0;
+            if( keyvalues_read_u32s( kvs, block, "queue_size_m", NULL, &queue_size_m, 1 ) )
+               $v_string( &_threads.structures, {"      .queue_size_m = "}, $unsigned(queue_size_m), {",\n"} );
 
-         $v_string( &_threads.structures, {"   },\n"} );
-      }
-      else
-      {
-         $log( $warning, {"We don't have a compiler definition for block '"}, {block_key}, {"'"} );
-         return;
+            $v_string( &_threads.structures, {"   },\n"} );
+         }
+         else if( compare_buffers( block_key, 0, "event", 0 )) 
+         {
+            _hooks.using = 1;
+            context.target = k_target_hook;
+            const c8 *name = keyvalues_read_string( kvs, block, "name", NULL );
+            const c8 *prototype = keyvalues_read_string( kvs, block, "prototype", NULL );
+            const c8 *returns = keyvalues_read_string( kvs, block, "returns", "void" );
+            ASSERT_CRITICAL( name && prototype && returns );
+
+            struct hook_event *event = stretchy_append( &_hooks.events );
+
+            stream_open_auto( &event->alias, k_stream_null_terminate );
+            $v_string( &event->alias, {name} );
+            stream_open_auto( &event->returns, k_stream_null_terminate );
+            $v_string( &event->returns, {returns} );
+            stream_open_auto( &event->proto, k_stream_null_terminate );
+            $v_string( &event->proto, {prototype} );
+
+            stretchy_init( &event->users, sizeof( struct hook_user ) );
+         }
+         else if( compare_buffers( block_key, 0, "hook", 0 ))
+         {
+            _hooks.using = 1;
+            const c8 *event_alias = keyvalues_read_string( kvs, block, "event", NULL );
+            const c8 *function = keyvalues_read_string( kvs, block, "function", NULL );
+
+            ASSERT_CRITICAL( event_alias && function );
+
+            bool found = 0;
+
+            for( u32 i=0; i<stretchy_count( &_hooks.events ); i ++ )
+            {
+               struct hook_event *event = stretchy_get( &_hooks.events, i );
+               if( compare_buffers( string_get( &event->alias ), 0, event_alias, 0 ) )
+               {
+                  struct hook_user *user = stretchy_append( &event->users );
+                  stream_open_auto( &user->function_name, k_stream_null_terminate );
+                  $v_string( &user->function_name, {function} );
+                  found = 1;
+                  break;
+               }
+            }
+
+            if( !found )
+            {
+               $log( $fatal, {"We don't have an event registered called '"}, {event_alias}, {"'"} );
+               _fatal_exit();
+            }
+         }
+         else
+         {
+            $log( $warning, {"We don't have a compiler definition for block '"}, {block_key}, {"'"} );
+            return;
+         }
       }
    }
 
@@ -207,6 +416,39 @@ void _parse_kv_block( struct keyvalues *kvs, u32 block, struct block_context con
          if( compare_buffers( key, 0, "define", 0 ) )
             $v_string( &_metacompiler.include_path_list, {"      -D"}, {value}, {" \\\n"} );
       }
+      else if( context.target == k_target_subshader )
+      {
+         struct shader *shader = stretchy_get( &_shaders.shaders, stretchy_count( &_shaders.shaders ) -1 );
+         struct subshader *subshader = &shader->subshaders[ shader->subshader_count -1 ];
+
+         if( compare_buffers( key, 0, "add", 0 ) )
+         {
+            u32 temp_frame = _start_temporary_frame();
+            {
+               struct stream path_string;
+               stream_open_stack( &path_string, _temporary_stack_allocator(), k_stream_null_terminate );
+               $v_string( &path_string, {context.folder}, {"/"}, {value} );
+
+               struct stream source;
+               ASSERT_CRITICAL( stream_open_file( &source, string_get( &path_string ), k_stream_read ) );
+               
+               u32 block_size = BYTES_KB(128);
+               c8 *temp = _temporary_allocate( block_size, 4 );
+               while(1)
+               {
+                  u32 l = stream_read( &source, temp, block_size );
+                  $v_string( &subshader->static_source, {temp, .length = l} );
+                  if( l != block_size )
+                     break;
+               }
+
+               stream_close( &source );
+               $v_string( &subshader->debug_source_list, {"               \""}, {string_get(&path_string)}, {"\",\n"} );
+               subshader->debug_source_count ++;
+            }
+            _end_temporary_frame( temp_frame );
+         }
+      }
 
       kv = keyvalues_get_next( kvs, kv );
    }
@@ -355,24 +597,27 @@ i32 _terminal_main(void)
 {
    buffer_copy( "project", 0, _metacompiler.project_name, sizeof(_metacompiler.project_name) );
 
-   u32 size = BYTES_MB(8),
-       options = k_stream_null_terminate|k_stream_overflow_error;
-   stream_open_buffer_write( &_metacompiler.source_list, _heap_allocate(size), size, options );
-   stream_open_buffer_write( &_metacompiler.include_path_list, _heap_allocate(size), size, options );
-   stream_open_buffer_write( &_metacompiler.define_list, _heap_allocate(size), size, options );
-   stream_open_buffer_write( &_metacompiler.configuration, _heap_allocate(size), size, options );
-
-   stream_open_buffer_write( &_input.layer_enums, _heap_allocate(size), size, options );
-   stream_open_buffer_write( &_input.input_enums, _heap_allocate(size), size, options );
-   stream_open_buffer_write( &_input.input_structures, _heap_allocate(size), size, options );
-   
-   stream_open_buffer_write( &_console.cvar_header, _heap_allocate(size), size, options ); 
-   stream_open_buffer_write( &_console.cvar_definitions, _heap_allocate(size), size, options ); 
-   stream_open_buffer_write( &_console.command_definitions, _heap_allocate(size), size, options ); 
-   stream_open_buffer_write( &_console.command_prototypes, _heap_allocate(size), size, options ); 
-
-   stream_open_buffer_write( &_threads.enums, _heap_allocate(size), size, options ); 
-   stream_open_buffer_write( &_threads.structures, _heap_allocate(size), size, options ); 
+   u32 options = k_stream_null_terminate;
+   stream_open_auto( &_metacompiler.source_list, options );
+   stream_open_auto( &_metacompiler.include_path_list, options );
+   stream_open_auto( &_metacompiler.define_list, options );
+   stream_open_auto( &_metacompiler.configuration, options );
+   stream_open_auto( &_input.layer_enums, options );
+   stream_open_auto( &_input.input_enums, options );
+   stream_open_auto( &_input.input_structures, options );
+   stream_open_auto( &_console.cvar_header, options );
+   stream_open_auto( &_console.cvar_definitions, options );
+   stream_open_auto( &_console.command_definitions, options );
+   stream_open_auto( &_console.command_prototypes, options );
+   stream_open_auto( &_threads.enums, options );
+   stream_open_auto( &_threads.structures, options );
+   stream_open_auto( &_shaders.shader_enum, options );
+   stream_open_auto( &_shaders.uniform_enum, options );
+   stream_open_auto( &_shaders.uniform_aliases, options );
+   stream_open_auto( &_shaders.uniform_funcs, options );
+   stream_open_auto( &_shaders.uniform_func_protos, options );
+   stretchy_init( &_hooks.events, sizeof( struct hook_event ) );
+   stretchy_init( &_shaders.shaders, sizeof( struct shader ) );
 
    if( platform == k_platform_linux )   _metacompiler.enabled_features[0] = "linux";
    if( platform == k_platform_windows ) _metacompiler.enabled_features[0] = "windows";
@@ -382,7 +627,8 @@ i32 _terminal_main(void)
    _append_kv_list( target_file_path, context );
 
    system_call( "mkdir -p generated" );
-
+   
+   $log( $info, {"Generating input header"} );
    if( _input.using )
    {
       struct stream input_header, input_source;
@@ -397,6 +643,7 @@ i32 _terminal_main(void)
       stream_close( &input_source );
    }
 
+   $log( $info, {"Generating threads header"} );
    if( _threads.using )
    {
       struct stream thread_header, thread_source;
@@ -412,6 +659,7 @@ i32 _terminal_main(void)
       stream_close( &thread_source );
    }
 
+   $log( $info, {"Generating console header"} );
    if( _console.using )
    {
       struct stream console_header, console_source;
@@ -427,6 +675,105 @@ i32 _terminal_main(void)
       stream_close( &console_source );
    }
 
+   $log( $info, {"Generating hooks header"} );
+   if( _hooks.using )
+   {
+      struct stream header, source;
+      ASSERT_CRITICAL( stream_open_file( &header, "generated/hooks.h", k_stream_write ) );
+      ASSERT_CRITICAL( stream_open_file( &source, "generated/hooks.c", k_stream_write ) );
+
+      for( u32 i=0; i<stretchy_count( &_hooks.events ); i ++ )
+      {
+         struct hook_event *event = stretchy_get( &_hooks.events, i );
+
+         const c8 *returns = string_get( &event->returns ),
+                  *alias = string_get( &event->alias ),
+                  *prototype = string_get( &event->proto );
+
+         $v_string( &header, {"extern "}, {returns}, {" (*_event_"}, {alias}, {"_subscribers[])( "}, {prototype}, {" );\n"} );
+
+         for( u32 j=0; j<stretchy_count( &event->users ); j ++ )
+         {
+            struct hook_user *user = stretchy_get( &event->users, j );
+            $v_string( &source, {returns}, {" "}, {string_get(&user->function_name)}, {"( "}, {prototype}, {" );\n"} );
+         }
+         $v_string( &source, {"\n"} );
+
+         $v_string( &source, {returns}, {" (*_event_"}, {alias}, {"_subscribers[])( "}, {prototype}, {" ) = \n{\n"} );
+         for( u32 j=0; j<stretchy_count( &event->users ); j ++ )
+         {
+            struct hook_user *user = stretchy_get( &event->users, j );
+            $v_string( &source, {"   "}, {string_get(&user->function_name)}, {",\n"} );
+         }
+         $v_string( &source, {"   NULL\n};\n\n"} );
+      }
+
+      stream_close( &source );
+      stream_close( &header );
+
+      $v_string( &_metacompiler.source_list, {"      generated/hooks.c \\\n"} );
+   }
+
+   $log( $info, {"Generating shader header"} );
+   if( _shaders.using )
+   {
+      struct stream header, source;
+      ASSERT_CRITICAL( stream_open_file( &header, "generated/shaders.h", k_stream_write ) );
+      ASSERT_CRITICAL( stream_open_file( &source, "generated/shaders.c", k_stream_write ) );
+
+      $v_string( &header, {"enum shader_id\n{\n"}, {string_get( &_shaders.shader_enum )}, {"   k_shader_count\n};\n"} );
+      $v_string( &header, {"enum shader_uniform_id\n{\n"}, {string_get( &_shaders.uniform_enum )}, {"   k_shader_uniform_count\n};\n"} );
+      $v_string( &header, {string_get( &_shaders.uniform_func_protos )} );
+
+      $v_string( &source, {"const c8 *_uniform_aliases[] = \n{\n"}, {string_get( &_shaders.uniform_aliases )}, {"};\n"} );
+
+      $v_string( &source, {"struct shader _shader_definitions[] = {\n"} );
+      for( u32 i=0; i<stretchy_count( &_shaders.shaders ); i ++ )
+      {
+         struct shader *shader = stretchy_get( &_shaders.shaders, i );
+         $v_string( &source, {"   [k_shader_"}, {string_get( &shader->name )}, {"] = \n   {\n"} );
+         $v_string( &source, {"      .name = \""}, {string_get( &shader->name )}, {"\",\n"} );
+         $v_string( &source, {"      .subshader_count = "}, $unsigned( shader->subshader_count ), {",\n"} );
+         $v_string( &source, {"      .uniform_start = "}, $unsigned( shader->uniform_start ), {",\n"} );
+         $v_string( &source, {"      .uniform_count = "}, $unsigned( shader->uniform_count ), {",\n"} );
+         $v_string( &source, {"      .subshaders = (struct subshader[])\n      {\n"} );
+
+         for( u32 j=0; j<shader->subshader_count; j ++ )
+         {
+            struct subshader *subshader = &shader->subshaders[ j ];
+            $v_string( &source, {"         {\n"} );
+            $v_string( &source, {"            .type = "}, 
+                  { (const c8 *[]){ [k_subshader_vertex] = "k_subshader_vertex",
+                                    [k_subshader_fragment] = "k_subshader_fragment",
+                                    [k_subshader_geometry] = "k_subshader_geometry" }[ subshader->type ] }, {",\n"} );
+            $v_string( &source, {"            .source_count = "}, $unsigned( subshader->debug_source_count ), {",\n"} );
+            $v_string( &source, {"            .source_list = (const c8 *[])\n"} );
+            $v_string( &source, {"            {\n"} );
+            $v_string( &source, {string_get( &subshader->debug_source_list )} );
+            $v_string( &source, {"            },\n"} );
+            $v_string( &source, {"            .source_static = \""} );
+
+            for( u32 k=0; k<subshader->static_source.offset; k ++ )
+            {
+               c8 c = subshader->static_source.buffer_read[ k ];
+               if( c == '\n' ) $v_string( &source, {"\\n"} );
+               else            string_append_c8( &source, c );
+            }
+            $v_string( &source, {"\",\n"} );
+            $v_string( &source, {"            .source_uniforms = \""}, {string_get( &subshader->uniform_source )}, {"\",\n"} );
+            $v_string( &source, {"         },\n"} );
+         }
+
+         $v_string( &source, {"      },\n"} );
+         $v_string( &source, {"   },\n"} );
+      }
+      $v_string( &source, {"};\n"} );
+      $v_string( &source, {string_get( &_shaders.uniform_funcs )} );
+
+      stream_close( &source );
+      stream_close( &header );
+   }
+
    /* main */
    u32 temp_frame = _start_temporary_frame();
    {