--- /dev/null
+#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 );
+ }
+ }
+}