framebuffer api
authorhgn <hgodden00@gmail.com>
Tue, 11 Jun 2024 18:35:53 +0000 (19:35 +0100)
committerhgn <hgodden00@gmail.com>
Tue, 11 Jun 2024 18:35:53 +0000 (19:35 +0100)
submodules/SDL_GameControllerDB
submodules/anyascii
submodules/qoi
submodules/stb
vg_build_utils_shader.h
vg_console.c
vg_engine.c
vg_framebuffer.c [new file with mode: 0644]
vg_framebuffer.h [new file with mode: 0644]

index 6ed8d054340ee8a93a684e11360b66cd8a5c168e..c5b4df0e1061175cb11e3ebbf8045178339864a5 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 6ed8d054340ee8a93a684e11360b66cd8a5c168e
+Subproject commit c5b4df0e1061175cb11e3ebbf8045178339864a5
index 44e971c774d9ec67ca6c1f16c5a476724821ab63..eb5332d0b5e48d58397e6f27475a18e058330d23 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 44e971c774d9ec67ca6c1f16c5a476724821ab63
+Subproject commit eb5332d0b5e48d58397e6f27475a18e058330d23
index b8d77df1e80b652a57f0b7270449b179a6b91f40..dfc056e813c98d307238d35f7f041a725d699dfc 160000 (submodule)
@@ -1 +1 @@
-Subproject commit b8d77df1e80b652a57f0b7270449b179a6b91f40
+Subproject commit dfc056e813c98d307238d35f7f041a725d699dfc
index 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55..5736b15f7ea0ffb08dd38af21067c314d6a3aae9 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55
+Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9
index 64a524b843be4a1319fa05f9b36577e9eb5118b1..a9d885b04f23fde38cac5c26340e82d86c5b37ca 100644 (file)
@@ -136,15 +136,19 @@ static int compile_subshader( vg_str *str, char *name )
 void vg_build_shader_impl( char *path )
 {
    FILE *fp = fopen( path, "w" );
-   fputs( vg_shaderbuild.code_function_body.buffer, fp );
+
+   if( vg_shaderbuild.code_function_body.buffer )
+      fputs( vg_shaderbuild.code_function_body.buffer, fp );
    fputs( "\n\n", fp );
 
    fputs( "void vg_auto_shader_link(void)\n{\n", fp );
-   fputs( vg_shaderbuild.code_link.buffer, fp );
+   if( vg_shaderbuild.code_link.buffer )
+      fputs( vg_shaderbuild.code_link.buffer, fp );
    fputs( "}\n\n", fp );
 
    fputs( "void vg_auto_shader_register(void)\n{\n", fp );
-   fputs( vg_shaderbuild.code_register.buffer, fp );
+   if( vg_shaderbuild.code_register.buffer )
+      fputs( vg_shaderbuild.code_register.buffer, fp );
    fputs( "}\n\n", fp );
 
    fclose( fp );
index c3e1d0d3759acb557ffee6103364413dc9e83e53..0f7387d04396fe1f1e1daaffd5c5af387eea1875 100644 (file)
@@ -325,6 +325,9 @@ u32 str_lcs( const char *s1, const char *s2 )
 void console_suggest_score_text( const char *str, const char *input,
                                  int minscore )
 {
+   if( !str )
+      return;
+
    /* filter duplicates */
    for( int i=0; i<vg_console.suggestion_count; i++ )
       if( !strcmp( vg_console.suggestions[i].str, str ) )
index 34de465315be099e9fb1952d17a03c9ac4f07dc8..86b44c4397604c820f6d3d9b87de9babf609c30d 100644 (file)
@@ -63,6 +63,7 @@ static void _vg_opengl_sync_init(void)
 #include "vg_shader.h"
 #include "vg_tex.h"
 #include "vg_input.h"
+#include "vg_framebuffer.h"
 #include "vg_lines.h"
 #include "vg_rigidbody_view.h"
 #include "vg_loader.h"
@@ -130,6 +131,7 @@ static void _vg_load_full( void *data )
    vg_ui.tex_bg = vg.tex_missing;
 
    /* internal */
+   vg_loader_step( vg_framebuffer_init, NULL );
    vg_loader_step( vg_input_init, vg_input_free );
    vg_loader_step( vg_lines_init, NULL );
    vg_loader_step( vg_rb_view_init, NULL );
@@ -234,7 +236,7 @@ static void _vg_process_events(void)
                vg.window_x = w;
                vg.window_y = h;
 
-               vg_framebuffer_resize(w,h);
+               vg_framebuffer_update_sizes();
             }
          }
          else if( event.window.event == SDL_WINDOWEVENT_CLOSE )
@@ -303,6 +305,8 @@ static void _vg_gameloop_render(void)
       ui_update_mouse( &vg_ui.ctx, 
          (ui_px[2]){ vg.mouse_pos[0], vg.mouse_pos[1] }, vg.mouse_state );
 
+      vg_framebuffer_ui( &vg_ui.ctx );
+
       if( vg_console.enabled )
       { 
          ui_ignore_input_frames( &vg_ui.ctx, 10 );
@@ -1299,6 +1303,7 @@ int AmdPowerXpressRequestHighPerformance = 1;
 #include "vg_rigidbody.c"
 #include "vg_rigidbody_view.c"
 #include "vg_shader.c"
+#include "vg_framebuffer.c"
 
 #ifdef VG_CUSTOM_SHADERS
  #include "shaders/impl.c"
diff --git a/vg_framebuffer.c b/vg_framebuffer.c
new file mode 100644 (file)
index 0000000..3eb7b86
--- /dev/null
@@ -0,0 +1,542 @@
+#include "vg_framebuffer.h"
+#include "vg_platform.h"
+#include "vg_async.h"
+
+struct
+{
+   vg_framebuffer *list[16];
+   u32 count;
+}
+static _vg_framebuffer;
+
+void vg_framebuffer_get_res( vg_framebuffer *fb, int *x, int *y )
+{
+   if( fb->resolution_div )
+   {
+      *x = vg.window_x / fb->resolution_div;
+      *y = vg.window_y / fb->resolution_div;
+   }
+   else
+   {
+      *x = fb->fixed_w;
+      *y = fb->fixed_h;
+   }
+}
+
+void vg_framebuffer_inverse_ratio( vg_framebuffer *fb, v2f inverse )
+{
+   if( fb )
+   {
+      int x, y;
+      vg_framebuffer_get_res( fb, &x, &y );
+
+      v2f render   = { fb->render_w, fb->render_h },
+          original = { x, y };
+
+      v2_div( render, original, inverse );
+   }
+   else
+   {
+      v2_div( (v2f){1.0f,1.0f}, (v2f){ vg.window_x, vg.window_y }, inverse );
+   }
+}
+
+void vg_framebuffer_bind( vg_framebuffer *fb, f32 scaling )
+{
+   int x, y;
+   vg_framebuffer_get_res( fb, &x, &y );
+
+   if( scaling != 1.0f )
+   {
+      x = scaling*(float)x;
+      y = scaling*(float)y;
+
+      x = VG_MAX( 16, x );
+      y = VG_MAX( 16, y );
+
+      fb->render_w = x;
+      fb->render_h = y;
+   }
+   else
+   {
+      fb->render_w = x;
+      fb->render_h = y;
+   }
+
+   glBindFramebuffer( GL_FRAMEBUFFER, fb->id );
+   glViewport( 0, 0, x, y );
+}
+
+void vg_framebuffer_bind_texture( vg_framebuffer *fb, int attachment, int slot )
+{
+   vg_framebuffer_attachment *at = &fb->attachments[attachment];
+
+   if( (at->purpose != k_framebuffer_attachment_type_texture) &&
+       (at->purpose != k_framebuffer_attachment_type_texture_depth) )
+   {
+      vg_fatal_error( "illegal operation: bind non-texture framebuffer"
+                          " attachment to texture slot" );
+   }
+
+   glActiveTexture( GL_TEXTURE0 + slot );
+   glBindTexture( GL_TEXTURE_2D, fb->attachments[attachment].id );
+}
+
+/* 
+ * Convert OpenGL attachment ID enum to string
+ */
+#define FB_FORMAT_STR( E ) { E, #E },
+static const char *render_fb_attachment_str( GLenum e )
+{
+   struct { GLenum e; const char *str; }
+   formats[] =
+   { 
+      FB_FORMAT_STR(GL_COLOR_ATTACHMENT0)
+      FB_FORMAT_STR(GL_COLOR_ATTACHMENT1)
+      FB_FORMAT_STR(GL_COLOR_ATTACHMENT2)
+      FB_FORMAT_STR(GL_COLOR_ATTACHMENT3)
+      FB_FORMAT_STR(GL_COLOR_ATTACHMENT4)
+      FB_FORMAT_STR(GL_DEPTH_STENCIL_ATTACHMENT)
+   };
+
+   for( int i=0; i<vg_list_size(formats); i++ )
+      if( formats[i].e == e )
+         return formats[i].str;
+
+   return "UNDEFINED";
+}
+
+/*
+ * Convert OpenGL texture format enums from TexImage2D table 1,2 &
+ * RenderBufferStorage Table 1, into strings
+ */
+static const char *render_fb_format_str( GLenum format )
+{
+   struct { GLenum e; const char *str; }
+   formats[] =
+   { 
+      /* Table 1 */
+      FB_FORMAT_STR(GL_DEPTH_COMPONENT)
+      FB_FORMAT_STR(GL_DEPTH_STENCIL)
+      FB_FORMAT_STR(GL_RED)
+      FB_FORMAT_STR(GL_RG)
+      FB_FORMAT_STR(GL_RGB)
+      FB_FORMAT_STR(GL_RGBA)
+
+      /* Render buffer formats */
+      FB_FORMAT_STR(GL_DEPTH_COMPONENT16)
+      FB_FORMAT_STR(GL_DEPTH_COMPONENT24)
+      FB_FORMAT_STR(GL_DEPTH_COMPONENT32F)
+      FB_FORMAT_STR(GL_DEPTH24_STENCIL8)
+      FB_FORMAT_STR(GL_DEPTH32F_STENCIL8)
+      FB_FORMAT_STR(GL_STENCIL_INDEX8)
+
+      /* Table 2 */
+      FB_FORMAT_STR(GL_R8)
+      FB_FORMAT_STR(GL_R8_SNORM)
+      FB_FORMAT_STR(GL_R16)
+      FB_FORMAT_STR(GL_R16_SNORM)
+      FB_FORMAT_STR(GL_RG8)
+      FB_FORMAT_STR(GL_RG8_SNORM)
+      FB_FORMAT_STR(GL_RG16)
+      FB_FORMAT_STR(GL_RG16_SNORM)
+      FB_FORMAT_STR(GL_R3_G3_B2)
+      FB_FORMAT_STR(GL_RGB4)
+      FB_FORMAT_STR(GL_RGB5)
+      FB_FORMAT_STR(GL_RGB8)
+      FB_FORMAT_STR(GL_RGB8_SNORM)
+      FB_FORMAT_STR(GL_RGB10)
+      FB_FORMAT_STR(GL_RGB12)
+      FB_FORMAT_STR(GL_RGB16_SNORM)
+      FB_FORMAT_STR(GL_RGBA2)
+      FB_FORMAT_STR(GL_RGBA4)
+      FB_FORMAT_STR(GL_RGB5_A1)
+      FB_FORMAT_STR(GL_RGBA8)
+      FB_FORMAT_STR(GL_RGBA8_SNORM)
+      FB_FORMAT_STR(GL_RGB10_A2)
+      FB_FORMAT_STR(GL_RGB10_A2UI)
+      FB_FORMAT_STR(GL_RGBA12)
+      FB_FORMAT_STR(GL_RGBA16)
+      FB_FORMAT_STR(GL_SRGB8)
+      FB_FORMAT_STR(GL_SRGB8_ALPHA8)
+      FB_FORMAT_STR(GL_R16F)
+      FB_FORMAT_STR(GL_RG16F)
+      FB_FORMAT_STR(GL_RGB16F)
+      FB_FORMAT_STR(GL_RGBA16F)
+      FB_FORMAT_STR(GL_R32F)
+      FB_FORMAT_STR(GL_RG32F)
+      FB_FORMAT_STR(GL_RGB32F)
+      FB_FORMAT_STR(GL_RGBA32F)
+      FB_FORMAT_STR(GL_R11F_G11F_B10F)
+      FB_FORMAT_STR(GL_RGB9_E5)
+      FB_FORMAT_STR(GL_R8I)
+      FB_FORMAT_STR(GL_R8UI)
+      FB_FORMAT_STR(GL_R16I)
+      FB_FORMAT_STR(GL_R16UI)
+      FB_FORMAT_STR(GL_R32I)
+      FB_FORMAT_STR(GL_R32UI)
+      FB_FORMAT_STR(GL_RG8I)
+      FB_FORMAT_STR(GL_RG8UI)
+      FB_FORMAT_STR(GL_RG16I)
+      FB_FORMAT_STR(GL_RG16UI)
+      FB_FORMAT_STR(GL_RG32I)
+      FB_FORMAT_STR(GL_RG32UI)
+      FB_FORMAT_STR(GL_RGB8I)
+      FB_FORMAT_STR(GL_RGB8UI)
+      FB_FORMAT_STR(GL_RGB16I)
+      FB_FORMAT_STR(GL_RGB16UI)
+      FB_FORMAT_STR(GL_RGB32I)
+      FB_FORMAT_STR(GL_RGB32UI)
+      FB_FORMAT_STR(GL_RGBA8I)
+      FB_FORMAT_STR(GL_RGBA8UI)
+      FB_FORMAT_STR(GL_RGBA16I)
+      FB_FORMAT_STR(GL_RGBA16UI)
+      FB_FORMAT_STR(GL_RGBA32I)
+      FB_FORMAT_STR(GL_RGBA32UI)
+   };
+
+   for( int i=0; i<vg_list_size(formats); i++ )
+      if( formats[i].e == format )
+         return formats[i].str;
+
+   return "UNDEFINED";
+}
+
+/*
+ * Bind and allocate texture for framebuffer attachment
+ */
+static void vg_framebuffer_allocate_texture( vg_framebuffer *fb, 
+                                             vg_framebuffer_attachment *a )
+{
+   int rx, ry;
+   vg_framebuffer_get_res( fb, &rx, &ry );
+
+   if( a->purpose == k_framebuffer_attachment_type_renderbuffer )
+   {
+      glBindRenderbuffer( GL_RENDERBUFFER, a->id );
+      glRenderbufferStorage( GL_RENDERBUFFER, a->internalformat, rx, ry );
+   }
+   else if( a->purpose == k_framebuffer_attachment_type_texture ||
+            a->purpose == k_framebuffer_attachment_type_texture_depth )
+   {
+      glBindTexture( GL_TEXTURE_2D, a->id );
+      glTexImage2D( GL_TEXTURE_2D, 0, a->internalformat, rx, ry,
+                                   0, a->format, a->type, NULL );
+   }
+}
+
+vg_framebuffer *vg_framebuffer_allocate( void *alloc,
+                                         u32 attachment_count, bool track )
+{
+   vg_framebuffer *fb = vg_linear_alloc( alloc, sizeof(vg_framebuffer) );
+
+   if( track )
+   {
+      if( _vg_framebuffer.count != vg_list_size(_vg_framebuffer.list) )
+         _vg_framebuffer.list[ _vg_framebuffer.count ++ ] = fb;
+      else
+         vg_fatal_error( "Framebuffer list is full" );
+   }
+
+   fb->attachments = vg_linear_alloc( alloc, 
+                     sizeof(vg_framebuffer_attachment) * attachment_count );
+   fb->attachment_count = attachment_count;
+
+   return fb;
+}
+
+static void async_framebuffer_create( void *payload, u32 size )
+{
+   vg_framebuffer *fb = payload;
+   glGenFramebuffers( 1, &fb->id );
+   glBindFramebuffer( GL_FRAMEBUFFER, fb->id );
+
+   int rx, ry;
+   vg_framebuffer_get_res( fb, &rx, &ry );
+
+   vg_info( "allocate_framebuffer( %s, %dx%d )\n", fb->display_name, rx, ry );
+   vg_info( "{\n" );
+
+   GLenum colour_attachments[ fb->attachment_count ];
+   u32    colour_count = 0;
+
+   for( u32 j=0; j<fb->attachment_count; j++ )
+   {
+      vg_framebuffer_attachment *attachment = &fb->attachments[j];
+
+      if( attachment->purpose == k_framebuffer_attachment_type_none )
+         continue;
+
+      vg_info( "  %s: %s\n", 
+                  render_fb_attachment_str( attachment->attachment ),
+                  render_fb_format_str( attachment->internalformat ) );
+
+      if( attachment->purpose == k_framebuffer_attachment_type_renderbuffer )
+      {
+         glGenRenderbuffers( 1, &attachment->id );
+         vg_framebuffer_allocate_texture( fb, attachment );
+         glFramebufferRenderbuffer( GL_FRAMEBUFFER, 
+                                    GL_DEPTH_STENCIL_ATTACHMENT,
+                                    GL_RENDERBUFFER, attachment->id );
+      }
+      else if( attachment->purpose == k_framebuffer_attachment_type_texture || 
+         attachment->purpose == k_framebuffer_attachment_type_texture_depth )
+      {
+         glGenTextures( 1, &attachment->id );
+         vg_framebuffer_allocate_texture( fb, attachment );
+         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+
+         glFramebufferTexture2D( GL_FRAMEBUFFER, attachment->attachment,
+                                 GL_TEXTURE_2D,  attachment->id, 0 );
+
+         if( attachment->purpose == k_framebuffer_attachment_type_texture )
+            colour_attachments[ colour_count ++ ] = attachment->attachment;
+      }
+   }
+
+   glDrawBuffers( colour_count, colour_attachments );
+
+   /* 
+    * Check result 
+    */
+   GLenum result = glCheckFramebufferStatus( GL_FRAMEBUFFER );
+
+   if( result == GL_FRAMEBUFFER_COMPLETE )
+   {
+      vg_success( "  status: complete\n" );
+      vg_info( "}\n" );
+   }
+   else
+   {
+      if( result == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT )
+         vg_error( "  status: Incomplete attachment" );
+      else if( result == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT )
+         vg_error( "  status: Missing attachment" );
+      else if( result == GL_FRAMEBUFFER_UNSUPPORTED )
+         vg_error( "  status: Unsupported framebuffer format" );
+      else
+         vg_error( "  status: Generic Error" );
+
+      vg_info( "}\n" );
+      vg_fatal_error( "Incomplete framebuffer (see logs)" );
+   }
+}
+
+void vg_framebuffer_create( vg_framebuffer *fb )
+{
+   vg_async_call( async_framebuffer_create, fb, 0 );
+}
+
+void vg_framebuffer_ui( ui_context *ctx )
+{
+   ui_px w = vg.window_x/3,
+         h = vg.window_y/3;
+
+   ui_rect frame = {0,0,vg.window_x/3,vg.window_y/3};
+
+   for( int i=0; i<_vg_framebuffer.count; i++ )
+   {
+      vg_framebuffer *fb = _vg_framebuffer.list[i];
+
+      for( int j=0; j<fb->attachment_count; j++ )
+      {
+         vg_framebuffer_attachment *at = &fb->attachments[j];
+
+         if( !at->debug_view )
+            continue;
+
+         ui_fill( ctx, frame, 0xff000000 );
+         if( at->purpose == k_framebuffer_attachment_type_renderbuffer )
+         {
+            ui_text( ctx, frame,
+                     "<hardware texture>", 1, k_ui_align_middle_center, 0 );
+         }
+         else
+         {
+            f32 img_ratio = (f32)fb->render_w / (f32)fb->render_h,
+                frame_ratio = (f32)w / (f32)h;
+
+            ui_rect img = { 0,0, 0,0 };
+
+            if( img_ratio >= frame_ratio )
+            {
+               img[2] = w;
+               img[3] = w / img_ratio;
+            }
+            else
+            {
+               img[2] = h * img_ratio;
+               img[3] = h;
+            }
+
+            ui_rect_center( frame, img );
+            ui_image( ctx, img, &fb->attachments[j].id );
+         }
+
+         ui_rect top, sub, next;
+         ui_split( frame, k_ui_axis_h, ctx->font->sy*2 + 18, 8, top, next );
+         ui_split( next, k_ui_axis_h, ctx->font->sy + 18, 8, sub, next );
+
+         ui_text( ctx, top, fb->display_name, 2, k_ui_align_middle_left, 0 );
+         ui_text( ctx, sub, at->display_name, 1, k_ui_align_middle_left, 0 );
+
+         frame[0] += w;
+
+         if( (frame[0] + w) > vg.window_x )
+         {
+            frame[0] = 0;
+            frame[1] += h;
+         }
+      }
+   }
+}
+
+static void vg_framebuffer_show( vg_framebuffer *fb,
+                                 vg_framebuffer_attachment *at,
+                                 int operation )
+{
+   at->debug_view = operation;
+   vg_info( "%s %s:%s\n", (operation? "shown": "hidden" ), 
+               fb->display_name, at->display_name );
+}
+
+/* 
+ * arg0: command           "show"/"hide"
+ * arg1: framebuffer name  <name>/"all"
+ * arg2: subname           <name>/none
+ */
+static int vg_framebuffer_control( int argc, char const *argv[] )
+{
+   if( argc < 2 )
+   {
+      vg_error( "Usage: fb \"show/hide\" <name>/\"all\" <name>/none\n" );
+      return 0;
+   }
+
+   int modify_all = 0,
+       operation  = 0;
+
+   if( !strcmp( argv[0], "show" ) )
+      operation = 1;
+   else if( !strcmp( argv[0], "hide" ) )
+      operation = 0;
+   else
+   {
+      vg_error( "Unknown framebuffer operation: '%s'\n", argv[0] );
+      return 0;
+   }
+
+   if( !strcmp( argv[1], "all" ) )
+      modify_all = 1;
+
+   for( int i=0; i<_vg_framebuffer.count; i++ )
+   {
+      vg_framebuffer *fb = _vg_framebuffer.list[i];
+      
+      for( int j=0; j<fb->attachment_count; j++ )
+      {
+         vg_framebuffer_attachment *at = &fb->attachments[j];
+
+         if( at->purpose == k_framebuffer_attachment_type_none )
+            continue;
+
+         if( modify_all )
+         {
+            vg_framebuffer_show( fb, at, operation );
+         }
+         else
+         {
+            if( !strcmp( fb->display_name, argv[1] ) )
+            {
+               if( argc == 2 )
+                  vg_framebuffer_show( fb, at, operation );
+               else if( !strcmp( at->display_name, argv[2] ) )
+                  vg_framebuffer_show( fb, at, operation );
+            }
+         }
+      }
+   }
+
+   return 0;
+}
+
+static void vg_framebuffer_poll( int argc, char const *argv[] )
+{
+   const char *term = argv[argc-1];
+
+   if( argc == 1 )
+   {
+      console_suggest_score_text( "show", term, 0 );
+      console_suggest_score_text( "hide", term, 0 );
+   }
+   else if( argc == 2 )
+   {
+      console_suggest_score_text( "all", term, 0 );
+
+      for( int i=0; i<_vg_framebuffer.count; i++ )
+      {
+         vg_framebuffer *fb = _vg_framebuffer.list[i];
+         console_suggest_score_text( fb->display_name, term, 0 );
+      }
+   }
+   else if( argc == 3 )
+   {
+      int modify_all = 0;
+
+      if( !strcmp( argv[1], "all" ) )
+         modify_all = 1;
+
+      for( int i=0; i<_vg_framebuffer.count; i++ )
+      {
+         vg_framebuffer *fb = _vg_framebuffer.list[ i ];
+         
+         for( int j=0; j<fb->attachment_count; j++ )
+         {
+            vg_framebuffer_attachment *at = &fb->attachments[j];
+
+            if( at->purpose == k_framebuffer_attachment_type_none )
+               continue;
+
+            if( modify_all )
+            {
+               console_suggest_score_text( at->display_name, term, 0 );
+            }
+            else if( !strcmp( fb->display_name, argv[1] ) )
+            {
+               console_suggest_score_text( at->display_name, term, 0 );
+            }
+         }
+      }
+   }
+}
+
+void vg_framebuffer_init(void)
+{
+   //vg_console_reg_var( "blur_strength", &k_blur_strength, k_var_dtype_f32, 0 );
+   //vg_console_reg_var( "render_scale", &k_render_scale,
+   //                    k_var_dtype_f32, VG_VAR_PERSISTENT );
+   //vg_console_reg_var( "fov", &k_fov, k_var_dtype_f32, VG_VAR_PERSISTENT );
+   //vg_console_reg_var( "cam_height", &k_cam_height, 
+   //                     k_var_dtype_f32, VG_VAR_PERSISTENT );
+   //vg_console_reg_var( "blur_effect", &k_blur_effect, 
+   //                     k_var_dtype_i32, VG_VAR_PERSISTENT );
+
+   vg_console_reg_cmd( "fb", vg_framebuffer_control, 
+                             vg_framebuffer_poll );
+}
+
+void vg_framebuffer_update_sizes(void)
+{
+   for( int i=0; i<_vg_framebuffer.count; i++ )
+   {
+      vg_framebuffer *fb = _vg_framebuffer.list[i];
+      for( int j=0; j<fb->attachment_count; j++ )
+      {
+         vg_framebuffer_attachment *attachment = &fb->attachments[j];
+         vg_framebuffer_allocate_texture( fb, attachment );
+      }
+   }
+}
diff --git a/vg_framebuffer.h b/vg_framebuffer.h
new file mode 100644 (file)
index 0000000..90500e0
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021-2024 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+#pragma once
+#include "vg_engine.h"
+
+typedef struct vg_framebuffer vg_framebuffer;
+typedef struct vg_framebuffer_attachment vg_framebuffer_attachment;
+
+struct vg_framebuffer
+{
+   const char *display_name;
+   int         resolution_div, /* If 0:     Use fixed_w, fixed_h.
+                                  If non-0: Automatically size itself to 
+                                            the window resolution divided by 
+                                            this value */
+               fixed_w,
+               fixed_h,
+
+               render_w, /* The currently rendering resolution */
+               render_h;
+   GLuint id;
+
+   struct vg_framebuffer_attachment
+   {
+      const char *display_name;
+      
+      enum vg_framebuffer_attachment_type
+      {
+         k_framebuffer_attachment_type_none,
+         k_framebuffer_attachment_type_texture,
+         k_framebuffer_attachment_type_renderbuffer,
+         k_framebuffer_attachment_type_texture_depth
+      }
+      purpose;
+
+      enum vg_framebuffer_quality_profile
+      {
+         k_framebuffer_quality_all,
+         k_framebuffer_quality_high_only
+      }
+      quality;
+      
+      GLenum internalformat,
+             format,
+             type,
+             attachment;
+
+      GLuint id;
+
+      /* Runtime */
+      int debug_view;
+   }
+   *attachments;
+   u32 attachment_count;
+};
+
+/* 
+ * Initialize framebuffer system
+ */
+void vg_framebuffer_init(void);
+
+/* 
+ * Get the current (automatically scaled or fixed) resolution of framebuffer
+ */
+void vg_framebuffer_get_res( vg_framebuffer *fb, int *x, int *y );
+
+/*
+ * Get the inverse ratio to project pixel coordinates (0->1920) to UV coordinates
+ *
+ * NOTE: won't necesarily use the full 0->1 range, but may index a subsection
+ *       of the framebuffer if using variable scale rendering.
+ */
+void vg_framebuffer_inverse_ratio( vg_framebuffer *fb, v2f inverse );
+
+/*
+ * Bind framebuffer for drawing to
+ */
+void vg_framebuffer_bind( vg_framebuffer *fb, f32 scaling );
+
+/*
+ * Bind framebuffer attachment's texture
+ */
+void vg_framebuffer_bind_texture( vg_framebuffer *fb, int attachment, int slot );
+
+/* 
+ * Allocation of a framebuffer memory. Optionally, track this framebuffer in 
+ * debugging systems.
+ */
+vg_framebuffer *vg_framebuffer_allocate( void *alloc, 
+                                         u32 attachment_count, bool track );
+
+/*
+ * Allocate graphics memory and initialize 
+ */
+void vg_framebuffer_create( vg_framebuffer *fb );
+
+/*
+ * Draw framebuffer debugging stuff
+ */
+void vg_framebuffer_ui( ui_context *ctx );
+
+void vg_framebuffer_update_sizes(void);