From: hgn Date: Tue, 11 Jun 2024 18:35:53 +0000 (+0100) Subject: framebuffer api X-Git-Url: https://harrygodden.com/git/?a=commitdiff_plain;h=62bf9dfe9e90cafff4d51e51ccbc498458ff474a;p=vg.git framebuffer api --- diff --git a/submodules/SDL_GameControllerDB b/submodules/SDL_GameControllerDB index 6ed8d05..c5b4df0 160000 --- a/submodules/SDL_GameControllerDB +++ b/submodules/SDL_GameControllerDB @@ -1 +1 @@ -Subproject commit 6ed8d054340ee8a93a684e11360b66cd8a5c168e +Subproject commit c5b4df0e1061175cb11e3ebbf8045178339864a5 diff --git a/submodules/anyascii b/submodules/anyascii index 44e971c..eb5332d 160000 --- a/submodules/anyascii +++ b/submodules/anyascii @@ -1 +1 @@ -Subproject commit 44e971c774d9ec67ca6c1f16c5a476724821ab63 +Subproject commit eb5332d0b5e48d58397e6f27475a18e058330d23 diff --git a/submodules/qoi b/submodules/qoi index b8d77df..dfc056e 160000 --- a/submodules/qoi +++ b/submodules/qoi @@ -1 +1 @@ -Subproject commit b8d77df1e80b652a57f0b7270449b179a6b91f40 +Subproject commit dfc056e813c98d307238d35f7f041a725d699dfc diff --git a/submodules/stb b/submodules/stb index 8b5f1f3..5736b15 160000 --- a/submodules/stb +++ b/submodules/stb @@ -1 +1 @@ -Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 diff --git a/vg_build_utils_shader.h b/vg_build_utils_shader.h index 64a524b..a9d885b 100644 --- a/vg_build_utils_shader.h +++ b/vg_build_utils_shader.h @@ -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 ); diff --git a/vg_console.c b/vg_console.c index c3e1d0d..0f7387d 100644 --- a/vg_console.c +++ b/vg_console.c @@ -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; iresolution_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; ipurpose == 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; jattachment_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; jattachment_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, + "", 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 /"all" + * arg2: subname /none + */ +static int vg_framebuffer_control( int argc, char const *argv[] ) +{ + if( argc < 2 ) + { + vg_error( "Usage: fb \"show/hide\" /\"all\" /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; jattachment_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; jattachment_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; jattachment_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 index 0000000..90500e0 --- /dev/null +++ b/vg_framebuffer.h @@ -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);