From 03b636c9e54577357983244fa7214d6717068d37 Mon Sep 17 00:00:00 2001 From: hgn Date: Sun, 19 Oct 2025 00:18:19 +0100 Subject: [PATCH] stuff --- .gitmodules | 3 + foundation.kv | 15 ++ include/common_api.h | 1 + include/engine_interface.h | 2 + source/engine/main.c | 26 +++ source/engine/vg_framebuffer.c | 367 +++++++++++++++++++++++++++++++++ source/engine/vg_framebuffer.h | 83 ++++++++ source/vg_tex/vg_tex.h | 1 + source/vg_water/vg_water.c | 261 +++++++++++++++++++++++ source/vg_water/vg_water.h | 8 + source/vg_water/vg_water.kv | 12 ++ source/vg_water/water.fs | 151 ++++++++++++++ submodules/miniaudio | 1 + 13 files changed, 931 insertions(+) create mode 100644 source/engine/vg_framebuffer.c create mode 100644 source/engine/vg_framebuffer.h create mode 100644 source/vg_water/vg_water.c create mode 100644 source/vg_water/vg_water.h create mode 100644 source/vg_water/vg_water.kv create mode 100644 source/vg_water/water.fs create mode 160000 submodules/miniaudio diff --git a/.gitmodules b/.gitmodules index af46047..8fbe57b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "submodules/hashmap.c"] path = submodules/hashmap.c url = https://github.com/tidwall/hashmap.c.git +[submodule "submodules/miniaudio"] + path = submodules/miniaudio + url = https://github.com/mackron/miniaudio.git diff --git a/foundation.kv b/foundation.kv index 6891d36..cd18ebd 100644 --- a/foundation.kv +++ b/foundation.kv @@ -47,6 +47,7 @@ add source/foundation/temporary.c { if game_engine + include source/engine { add source/engine/main.c @@ -76,6 +77,11 @@ add source/foundation/temporary.c name ENGINE_UI prototype "void" } + event + { + name ENGINE_WINDOW_RESIZE + prototype "void" + } } { @@ -157,6 +163,15 @@ add source/foundation/temporary.c } } + { + add source/engine/vg_framebuffer.c + hook + { + event ENGINE_WINDOW_RESIZE + function _framebuffer_resize + } + } + add source/engine/shader.c input_layer { diff --git a/include/common_api.h b/include/common_api.h index b8e2ecd..f504ab8 100644 --- a/include/common_api.h +++ b/include/common_api.h @@ -1,4 +1,5 @@ /* Voyager common application interface */ +#pragma once #define VG_PRE_MAIN \ _exit_init(); \ diff --git a/include/engine_interface.h b/include/engine_interface.h index 58c7a1d..a428d99 100644 --- a/include/engine_interface.h +++ b/include/engine_interface.h @@ -14,6 +14,8 @@ struct _engine void *window_handle; i32 w, h; + + i32 native_fbo; } extern _engine; #include "generated/console.h" diff --git a/source/engine/main.c b/source/engine/main.c index 029ae1b..aec6498 100644 --- a/source/engine/main.c +++ b/source/engine/main.c @@ -62,6 +62,21 @@ i32 async_thread( void *_ ) return 0; } +APIENTRY void _opengl_debug( GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar *message, + const void *userParam) +{ + if( type == GL_DEBUG_TYPE_ERROR || type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR ) + $log( $error, {message} ); + + if( type == GL_DEBUG_TYPE_ERROR ) + _fatal_exit(); +} + i32 main( i32 argc, const c8 *argv[] ) { VG_PRE_MAIN; @@ -71,6 +86,7 @@ i32 main( i32 argc, const c8 *argv[] ) glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 4 ); glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 ); glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE ); + glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE ); _engine.window_handle = glfwCreateWindow(640, 480, "My Title", NULL, NULL); if( !_engine.window_handle ) { @@ -81,11 +97,15 @@ i32 main( i32 argc, const c8 *argv[] ) glfwMakeContextCurrent( _engine.window_handle ); gladLoadGLLoader( (GLADloadproc)glfwGetProcAddress ); glfwSetCharCallback( _engine.window_handle, _character_callback ); + glDebugMessageCallback( _opengl_debug, NULL ); GLFWmonitor *monitor = glfwGetPrimaryMonitor(); const GLFWvidmode *vidmode = glfwGetVideoMode( monitor ); _engine_set_framerate( vidmode->refreshRate, 0 ); f64 next_frame_time = 0.0, fixed_time = 0.0, fixed_accumulator = 0.0; + glfwGetFramebufferSize( _engine.window_handle, &_engine.w, &_engine.h ); + glGetIntegerv( GL_FRAMEBUFFER_BINDING, &_engine.native_fbo ); + /* ------------- */ @@ -143,7 +163,13 @@ L_new_frame:; _engine.time_delta = actual_delta; _engine.time_fixed_extrapolate = fixed_accumulator / (1.0/60.0); + i32 pw = _engine.w, ph = _engine.h; glfwGetFramebufferSize( _engine.window_handle, &_engine.w, &_engine.h ); + if( (pw != _engine.w) || (ph != _engine.h) ) + { + EVENT_CALL( ENGINE_WINDOW_RESIZE ); + } + EVENT_CALL( ENGINE_RENDER ); _engine_ui_pre_render(); EVENT_CALL( ENGINE_UI ); diff --git a/source/engine/vg_framebuffer.c b/source/engine/vg_framebuffer.c new file mode 100644 index 0000000..76efbc5 --- /dev/null +++ b/source/engine/vg_framebuffer.c @@ -0,0 +1,367 @@ +#include "vg_framebuffer.h" +#include "common_api.h" +#include "engine_interface.h" +#include "vg_tex.h" +#include "maths/common_maths.h" +#include "common_thread_api.h" + +struct +{ + struct framebuffer *list[16]; + u32 count; + i32 gw, gh; +} +static _framebuffer; + +struct framebuffer *_framebuffer_allocate( struct stack_allocator *stack, u32 attachment_count, bool track ) +{ + u32 size = sizeof(struct framebuffer) + sizeof(struct framebuffer_attachment)*attachment_count; + struct framebuffer *fb = stack_allocate( stack, size, 8, "Framebuffer metadata" ); + zero_buffer( fb, size ); + + fb->attachment_count = attachment_count; + if( track ) + { + if( _framebuffer.count != ARRAY_COUNT(_framebuffer.list) ) + _framebuffer.list[ _framebuffer.count ++ ] = fb; + else + { + // FIXME: THIS SHOULD BE PART OF VGC + $log( $fatal, {"Framebuffer list is full, and tried to allocate another."} ); + _fatal_exit(); + } + } + return fb; +} + +void framebuffer_get_res( struct framebuffer *fb, i32 *x, i32 *y ) +{ + if( fb->resolution_div ) + { + *x = _engine.w / fb->resolution_div; + *y = _engine.h / fb->resolution_div; + } + else + { + *x = fb->fixed_w; + *y = fb->fixed_h; + } +} + +void framebuffer_inverse_ratio( struct framebuffer *fb, f32 inverse[2] ) +{ + if( fb ) + { + i32 x, y; + framebuffer_get_res( fb, &x, &y ); + f32 render[2] = { fb->render_w, fb->render_h }, + original[2]= { x, y }; + + v2_div( render, original, inverse ); + } + else + v2_div( (f32[2]){1.0f,1.0f}, (f32[2]){ _engine.w, _engine.h }, inverse ); +} + +void framebuffer_bind( struct framebuffer *fb, f32 scaling ) +{ + i32 x, y; + framebuffer_get_res( fb, &x, &y ); + + if( scaling != 1.0f ) + { + x = scaling*(f32)x; + y = scaling*(f32)y; + + x = i32_max( 16, x ); + y = i32_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 framebuffer_bind_texture( struct framebuffer *fb, u32 attachment, u32 slot ) +{ + struct framebuffer_attachment *at = &fb->attachments[attachment]; + + if( (at->purpose != k_framebuffer_attachment_type_texture) && + (at->purpose != k_framebuffer_attachment_type_texture_depth) ) + { + $log( $fatal, {"illegal operation: bind non-texture framebuffer attachment to texture slot"} ); + _fatal_exit(); + } + + vg_tex_bind( GL_TEXTURE_2D, &fb->attachments[attachment].tex, slot ); +} + +/* + * Convert OpenGL attachment ID enum to string + */ +#define FB_FORMAT_STR( E ) { E, #E }, +static const c8 *render_fb_attachment_str( GLenum e ) +{ + struct { GLenum e; const c8 *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; itex.name == 0 ) + return; + + i32 rx, ry; + framebuffer_get_res( fb, &rx, &ry ); + + if( a->purpose == k_framebuffer_attachment_type_renderbuffer ) + { + glBindRenderbuffer( GL_RENDERBUFFER, a->tex.name ); + 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->tex.name ); + glTexImage2D( GL_TEXTURE_2D, 0, a->internalformat, rx, ry, 0, a->format, a->type, NULL ); + } +} + +void framebuffer_init( struct framebuffer *fb ) +{ + ASSERT_CRITICAL( _thread_has_flags( _get_thread_id(), THREAD_FLAG_OPENGL ) ); + glGenFramebuffers( 1, &fb->id ); + glBindFramebuffer( GL_FRAMEBUFFER, fb->id ); + + i32 rx, ry; + framebuffer_get_res( fb, &rx, &ry ); + + $log( $info, {"allocate_framebuffer( '"}, {fb->display_name}, {"', "}, $signed( rx ), {"x"}, $signed( ry ), {")"}); + + GLenum colour_attachments[ fb->attachment_count ]; + u32 colour_count = 0; + + for( u32 j=0; jattachment_count; j++ ) + { + struct framebuffer_attachment *attachment = &fb->attachments[j]; + + if( attachment->purpose == k_framebuffer_attachment_type_none ) + continue; + + $log( $info, {" + attachment #"}, $unsigned(j), {": "}, {render_fb_attachment_str( attachment->attachment )}, {", "}, + {render_fb_format_str( attachment->internalformat )} ); + + if( attachment->purpose == k_framebuffer_attachment_type_renderbuffer ) + { + glGenRenderbuffers( 1, &attachment->tex.name ); + attachment->tex.flags = VG_TEX_COMPLETE|VG_TEX_FRAMEBUFFER_ATTACHMENT|VG_TEX_PRIVATE|VG_TEX_NOMIP; + framebuffer_allocate_texture( fb, attachment ); + glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, attachment->tex.name ); + } + else if( attachment->purpose == k_framebuffer_attachment_type_texture || + attachment->purpose == k_framebuffer_attachment_type_texture_depth ) + { + glGenTextures( 1, &attachment->tex.name ); + attachment->tex.flags = VG_TEX_COMPLETE|VG_TEX_FRAMEBUFFER_ATTACHMENT|VG_TEX_LINEAR|VG_TEX_CLAMP|VG_TEX_NOMIP; + + 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->tex.name, 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 ) + { + $log( $ok, {" -> complete"} ); + } + else + { + if( result == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT ) + { + $log( $fatal, {" -> Incomplete attachment"} ); + } + else if( result == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT ) + { + $log( $fatal, {" -> Missing attachment"} ); + } + else if( result == GL_FRAMEBUFFER_UNSUPPORTED ) + { + $log( $fatal, {" -> Unsupported framebuffer format"} ); + } + else + { + $log( $fatal, {" -> Generic error"} ); + } + _fatal_exit(); + } +} + +void framebuffer_free( struct framebuffer *fb ) +{ + glDeleteFramebuffers( 1, &fb->id ); + for( u32 j=0; jattachment_count; j++ ) + { + struct framebuffer_attachment *attachment = &fb->attachments[j]; + if( attachment->purpose == k_framebuffer_attachment_type_none ) + continue; + + if( attachment->purpose == k_framebuffer_attachment_type_renderbuffer ) + { + glDeleteRenderbuffers( 1, &attachment->tex.name ); + zero_buffer( &attachment->tex, sizeof(struct vg_tex) ); + } + else if( attachment->purpose == k_framebuffer_attachment_type_texture || + attachment->purpose == k_framebuffer_attachment_type_texture_depth ) + { + vg_tex_delete( &attachment->tex ); + } + } +} + +void _framebuffer_resize(void) +{ + $log( $info, {"Resizing framebuffers..."} ); + ASSERT_CRITICAL( _thread_has_flags( _get_thread_id(), THREAD_FLAG_OPENGL ) ); + for( i32 i=0; i<_framebuffer.count; i++ ) + { + struct framebuffer *fb = _framebuffer.list[i]; + for( int j=0; jattachment_count; j++ ) + { + struct framebuffer_attachment *attachment = &fb->attachments[j]; + framebuffer_allocate_texture( fb, attachment ); + } + } +} diff --git a/source/engine/vg_framebuffer.h b/source/engine/vg_framebuffer.h new file mode 100644 index 0000000..eed53ac --- /dev/null +++ b/source/engine/vg_framebuffer.h @@ -0,0 +1,83 @@ +#pragma once +#include "opengl.h" +#include "vg_tex.h" + +#define VG_FRAMEBUFFER_GLOBAL 1 +#define VG_FRAMEBUFFER_SPECIALIZED 0 + +struct framebuffer +{ + const c8 *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; + u32 attachment_count; + + struct framebuffer_attachment + { + const c8 *display_name; + enum 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 framebuffer_quality_profile + { + k_framebuffer_quality_all, + k_framebuffer_quality_high_only + } + quality; + + GLenum internalformat, + format, + type, + attachment; + struct vg_tex tex; + + /* Runtime */ + int debug_view; + } + attachments[]; +}; + +struct framebuffer *_framebuffer_allocate( struct stack_allocator *stack, u32 attachment_count, bool track ); + +/* + * Get the current (automatically scaled or fixed) resolution of framebuffer + */ +void framebuffer_get_res( struct framebuffer *fb, i32 *x, i32 *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 framebuffer_inverse_ratio( struct framebuffer *fb, f32 inverse[2] ); + +/* + * Bind framebuffer for drawing to + */ +void framebuffer_bind( struct framebuffer *fb, f32 scaling ); + +/* + * Bind framebuffer attachment's texture + */ +void framebuffer_bind_texture( struct framebuffer *fb, u32 attachment, u32 slot ); + +/* + * Allocate graphics memory and initialize + */ +void framebuffer_init( struct framebuffer *fb ); +void framebuffer_free( struct framebuffer *fb ); diff --git a/source/vg_tex/vg_tex.h b/source/vg_tex/vg_tex.h index b2d4385..b528fe9 100644 --- a/source/vg_tex/vg_tex.h +++ b/source/vg_tex/vg_tex.h @@ -1,5 +1,6 @@ #pragma once #include "opengl.h" +#include "common_api.h" /* TODO: Include Okaypeg alongside QOI. */ diff --git a/source/vg_water/vg_water.c b/source/vg_water/vg_water.c new file mode 100644 index 0000000..3f6d539 --- /dev/null +++ b/source/vg_water/vg_water.c @@ -0,0 +1,261 @@ +#include "vg_water.h" + +static vg_tex _water_surface_tex; + +static void _world_water_load_content( void *_, vg_async_info *async ) +{ + _vg_tex_load( &world_water.tex_water_surf, "textures/water_surf.qoi", VG_TEX_LINEAR|VG_TEX_REPEAT ); +} + +void _world_water_init(void) +{ + VG_ASSERT( _vg_thread_has_flags( VG_THREAD_MAIN ) ); + _vg_async_send( _vg_async_alloc( VG_THREAD_ASYNC_ID, 0 ), (vg_async_fn)_world_water_load_content ); +} + +void water_set_surface( world_instance *world, float height ) +{ + world->water.height = height; + v4_copy( (v4f){ 0.0f, 1.0f, 0.0f, height }, world->water.plane ); +} + +/* + * Does not write motion vectors + */ +void render_water_texture( world_instance *world, vg_camera *cam ) +{ + if( !world->water.enabled || (vg.quality_profile == k_quality_profile_low) ) + return; + + /* Draw reflection buffa */ + vg_framebuffer_bind( _vg_render.fb_water_reflection, _vg_render.scale ); + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + + /* + * Create flipped view matrix. Don't care about motion vectors + */ + float cam_height = cam->transform[3][1] - world->water.height; + + vg_camera water_cam; + water_cam.farz = cam->farz; + water_cam.nearz = cam->nearz; + v3_copy( cam->transform[3], water_cam.transform[3] ); + water_cam.transform[3][1] -= 2.0f * cam_height; + + m3x3f flip; + m3x3_identity( flip ); + flip[1][1] = -1.0f; + m3x3_mul( flip, cam->transform, water_cam.transform ); + + vg_camera_update_view( &water_cam ); + + /* + * Create clipped projection + */ + v4f clippa = { 0.0f, 1.0f, 0.0f, world->water.height-0.1f }; + m4x3_mulp( water_cam.transform_inverse, clippa, clippa ); + clippa[3] *= -1.0f; + + m4x4_copy( cam->mtx.p, water_cam.mtx.p ); + m4x4_clip_projection( water_cam.mtx.p, clippa ); + + vg_camera_finalize( &water_cam ); + + /* + * Draw world + */ + glEnable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + glCullFace( GL_FRONT ); + render_world( world, &water_cam, 0, 1, 0, 1, _vg_render.fb_water_reflection ); + glCullFace( GL_BACK ); + + /* + * Create beneath view matrix + */ + vg_camera beneath_cam; + m4x3_copy( cam->transform, beneath_cam.transform ); + vg_camera_update_view( &beneath_cam ); + + float bias = -(cam->transform[3][1]-world->water.height)*0.1f; + + v4f clippb = { 0.0f, -1.0f, 0.0f, -(world->water.height) + bias }; + m4x3_mulp( beneath_cam.transform_inverse, clippb, clippb ); + clippb[3] *= -1.0f; + + m4x4_copy( cam->mtx.p, beneath_cam.mtx.p ); + m4x4_clip_projection( beneath_cam.mtx.p, clippb ); + vg_camera_finalize( &beneath_cam ); + + + vg_framebuffer_bind( _vg_render.fb_water_refraction, _vg_render.scale ); + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + glEnable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + glCullFace( GL_BACK ); + render_world( world, &beneath_cam, 0, 1, 0, 1, _vg_render.fb_water_refraction ); + glCullFace( GL_BACK ); + + + + + vg_framebuffer_bind( _vg_render.fb_water_beneath, _vg_render.scale ); + glClearColor( 1.0f, 0.0f, 0.0f, 0.0f ); + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + glEnable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + render_world_depth( world, &beneath_cam ); +} + +void render_water_surface( world_instance *world, vg_camera *cam ) +{ + if( !world->water.enabled ) + return; + + if( vg.quality_profile == k_quality_profile_high ) + { + /* Draw surface */ + shader_scene_water_use(); + + vg_framebuffer_bind_texture( _vg_render.fb_water_reflection, 0, 0 ); + shader_scene_water_uTexMain( 0 ); + + vg_tex_bind( GL_TEXTURE_2D, &world_water.tex_water_surf, 1 ); + shader_scene_water_uTexDudv( 1 ); + + shader_scene_water_uInvRes( (v2f){ 1.0f / (f32)_vg_window.w, 1.0f / (f32)_vg_window.h }); + WORLD_LINK_LIGHTING( world, scene_water ); + + vg_framebuffer_bind_texture( _vg_render.fb_water_beneath, 0, 5 ); + shader_scene_water_uTexBack( 5 ); + shader_scene_water_uTime( _world.time ); + shader_scene_water_uCamera( cam->transform[3] ); + shader_scene_water_uSurfaceY( world->water.height ); + + vg_framebuffer_bind_texture( _vg_render.fb_water_refraction, 0, 6 ); + shader_scene_water_uTexRefraction( 6 ); + + shader_scene_water_uPv( cam->mtx.pv ); + shader_scene_water_uPvmPrev( cam->mtx_prev.pv ); + + m4x3f full; + m4x3_identity( full ); + shader_scene_water_uMdl( full ); + + //glEnable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + //glBlendEquation(GL_FUNC_ADD); + + scene_mesh_bind( &world->transparent_scene_mesh ); + for( int i=0; isurface_count; i++ ) + { + struct world_surface *mat = &world->surfaces[i]; + + if( mat->info.shader == k_shader_water ) + { + VG_ASSERT(i); + union shader_props *props = &world->meta.shader_props[i-1]; + shader_scene_water_uShoreColour( props->water.shore_colour ); + shader_scene_water_uOceanColour( props->water.deep_colour ); + shader_scene_water_uFresnel( props->water.fresnel ); + shader_scene_water_uWaterScale( props->water.water_sale ); + shader_scene_water_uWaveSpeed( props->water.wave_speed ); + vg_model_draw_submesh( &mat->sm_no_collide ); + } + } + + glDisable(GL_BLEND); + } + else if( (vg.quality_profile == k_quality_profile_low) || + (vg.quality_profile == k_quality_profile_min) ) + { + shader_scene_water_fast_use(); + + vg_tex_bind( GL_TEXTURE_2D, &world_water.tex_water_surf, 1 ); + shader_scene_water_fast_uTexDudv( 1 ); + + shader_scene_water_fast_uTime( _world.time ); + shader_scene_water_fast_uCamera( cam->transform[3] ); + shader_scene_water_fast_uSurfaceY( world->water.height ); + WORLD_LINK_LIGHTING( world, scene_water_fast ); + + m4x3f full; + m4x3_identity( full ); + shader_scene_water_fast_uMdl( full ); + shader_scene_water_fast_uPv( cam->mtx.pv ); + shader_scene_water_fast_uPvmPrev( cam->mtx_prev.pv ); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + + scene_mesh_bind( &world->transparent_scene_mesh ); + for( int i=0; isurface_count; i++ ) + { + struct world_surface *mat = &world->surfaces[i]; + + if( mat->info.shader == k_shader_water ) + { + VG_ASSERT(i); + union shader_props *props = &world->meta.shader_props[i-1]; + shader_scene_water_fast_uShoreColour( props->water.shore_colour ); + shader_scene_water_fast_uOceanColour( props->water.deep_colour ); + + vg_model_draw_submesh( &mat->sm_no_collide ); + } + } + + glDisable(GL_BLEND); + } +} + +static void world_water_drown(void) +{ + if( localplayer.immunity ) + return; + + if( localplayer.drowned ) + return; + + player__networked_sfx( k_player_subsystem_walk, 32, k_player_walk_soundeffect_splash, localplayer.rb.co, 1.0f ); + vg_info( "player fell of due to walking into walker\n" ); + localplayer.drowned = 1; + player__dead_transition( k_player_die_type_water ); + + if( !((_world.event == k_world_event_challenge) && (_world.challenge_state >= k_challenge_state_running)) ) + { + vg_str str; + struct gui_helper *helper; + if( (helper = _gui_new_helper(input_button_list[k_srbind_reset], &str)) ) + { + vg_strcat( &str, "Respawn" ); + } + } +} + +bool world_water_player_safe( world_instance *world, f32 allowance ) +{ + if( !world->water.enabled ) + return 1; + if( world->info.flags & k_world_flag_water_is_safe ) + return 1; + + if( localplayer.rb.co[1]+allowance < world->water.height ) + { + world_water_drown(); + return 0; + } + + return 1; +} + +entity_event_result ent_water_event( ent_event *event ) +{ + world_instance *world = &_world.main; + if( AF_STR_EQ( world->meta.packed_strings, event->pstr_recieve_event, "drown" ) ) + { + world_water_drown(); + return k_entity_event_result_OK; + } + else return k_entity_event_result_unhandled; +} diff --git a/source/vg_water/vg_water.h b/source/vg_water/vg_water.h new file mode 100644 index 0000000..e7538f7 --- /dev/null +++ b/source/vg_water/vg_water.h @@ -0,0 +1,8 @@ +#pragma once + +void _vg_water_set_height( f32 height ); + +void render_water_texture( world_instance *world, vg_camera *cam ); +void render_water_surface( world_instance *world, vg_camera *cam ); +entity_event_result ent_water_event( ent_event *event ); +bool world_water_player_safe( world_instance *world, f32 allowance ); diff --git a/source/vg_water/vg_water.kv b/source/vg_water/vg_water.kv new file mode 100644 index 0000000..4c15cda --- /dev/null +++ b/source/vg_water/vg_water.kv @@ -0,0 +1,12 @@ +add vg_water.c +include "" + +shader +{ + name vg_water + + subshader + { + type vertex + } +} diff --git a/source/vg_water/water.fs b/source/vg_water/water.fs new file mode 100644 index 0000000..a7be103 --- /dev/null +++ b/source/vg_water/water.fs @@ -0,0 +1,151 @@ +uniform sampler2D uTexMain; +uniform sampler2D uTexDudv; +uniform sampler2D uTexBack; +uniform sampler2D uTexRefraction; + +uniform vec2 uInvRes; +uniform float uTime; +uniform vec3 uCamera; +uniform float uSurfaceY; +uniform vec3 uBoard0; +uniform vec3 uBoard1; + +uniform vec3 uShoreColour; +uniform vec3 uOceanColour; +uniform float uFresnel; +uniform float uWaterScale; +uniform vec4 uWaveSpeed; + +#include "light_clearskies_stddef.glsl" +#include "common_scene.glsl" +#include "motion_vectors_fs.glsl" + +// Pasted from common_world.glsl +vec3 water_compute_lighting( vec3 diffuse, vec3 normal, vec3 co ) +{ + float light_mask = compute_board_shadow(); + + if( g_light_preview == 1 ) + diffuse = vec3(0.75); + + // Lighting + vec3 halfview = uCamera - co; + float fdist = length(halfview); + halfview /= fdist; + + float world_shadow = newlight_compute_sun_shadow( + co, g_sun_dir.xyz * (1.0/(max(g_sun_dir.y,0.0)+0.2)) ); + + vec3 total_light = clearskies_lighting( + normal, min( light_mask, world_shadow ), halfview ); + + vec3 cube_coord = (co - g_cube_min.xyz) * g_cube_inv_range.xyz; + cube_coord = floor( cube_coord ); + + if( g_debug_indices == 1 ) + { + return rand33(cube_coord); + } + + if( g_debug_complexity == 1 ) + { + ivec3 coord = ivec3( cube_coord ); + uvec4 index_sample = texelFetch( uLightsIndex, coord, 0 ); + + uint light_count = (index_sample.x & 0x3u) + (index_sample.y & 0x3u); + return vec3( float(light_count)*(1.0/6.0), 0.0, 0.5 ); + } + + // FIXME: this coord should absolutely must be clamped! + + ivec3 coord = ivec3( cube_coord ); + uvec4 index_sample = texelFetch( uLightsIndex, coord, 0 ); + + total_light += + scene_calculate_packed_light_patch( index_sample.x, + halfview, co, normal ) + * light_mask; + total_light += + scene_calculate_packed_light_patch( index_sample.y, + halfview, co, normal ) + * light_mask; + + return diffuse * total_light; +} + +vec4 water_surf( vec3 halfview, vec3 vnorm, float depthvalue, + vec4 beneath, vec4 above, vec4 dudva ) +{ + vec3 surface_tint = mix(uShoreColour, uOceanColour, depthvalue); + + float ffresnel = pow(1.0-dot( vnorm, halfview ),uFresnel); + + vec3 lightdir = vec3(0.95,0.0,-0.3); + vec3 specdir = reflect( -lightdir, vnorm ); + float spec = pow(max(dot(halfview,specdir),0.0),20.0)*0.3; + + // Depth + float depthblend = pow( beneath.r, 0.8 ); + + // Foam + float fband = fract( aCo.z*0.02+uTime*0.1+depthvalue*10.0 ); + fband = step( fband+dudva.a*0.8, 0.3 ) * max((1.0-depthvalue*4.0),0.0); + + //vec4 surf = mix( vec4(surface_tint,depthblend), vec4(1.0,1.0,1.0,0.5), fband ); + vec4 surf = vec4(surface_tint,depthblend); + surf.rgb = water_compute_lighting( surf.rgb, aNorm.xyz, aWorldCo ); + surf.rgb = mix(surf.rgb, above.rgb, ffresnel ); + + // Take a section of the sky function to give us a matching fog colour + vec3 fog_colour = clearskies_ambient( -halfview ); + float sun_theta = dot( -halfview, g_sun_dir.xyz ); + float sun_size = max( 0.0, sun_theta * 0.5 + 0.5 ); + float sun_shape = sun_size * max(g_sun_dir.y,0.0) * 0.5; + + vec3 sun_colour = mix( vec3(1.0), g_sunset_colour.rgb, g_sunset_phase*0.5 ); + sun_colour *= sun_shape; + + fog_colour += sun_colour; + surf.rgb = scene_apply_fog( surf.rgb, fog_colour, + distance(uCamera, aWorldCo) ); + + return surf; +} + +void main() +{ + compute_motion_vectors(); + + // Create texture coords + vec2 ssuv = gl_FragCoord.xy*uInvRes; + + // Surface colour composite + float depthvalue = clamp( -world_water_depth(aCo)*(1.0/25.0)*2.0, 0.0,1.0 ); + + vec2 world_coord = aCo.xz * uWaterScale; + vec4 time_offsets = vec4( uTime ) * uWaveSpeed; + vec4 dudva = texture( uTexDudv, world_coord + time_offsets.xy )-0.5; + vec4 dudvb = texture( uTexDudv, world_coord *7.0 - time_offsets.zw )-0.5; + + vec3 surfnorm = dudva.rgb + dudvb.rgb; + surfnorm = normalize(vec3(0.0,1.0,0.0) + dudva.xyz*0.4 + dudvb.xyz*0.1); + + // Lighting + vec3 halfview = -normalize( aCo-uCamera ); + + // Sample textures + vec4 above = texture( uTexMain, ssuv + surfnorm.xz*0.2 ); + vec4 beneath = texture( uTexBack, ssuv ); + + float ripple_strength = 0.6; + vec4 rippled = texture( uTexRefraction, ssuv - surfnorm.xz*ripple_strength*depthvalue ); + + // Fog + float fdist = pow(length( aCo.xz-uCamera.xz ) * 0.00047, 2.6); + + // Composite + vec4 vsurface = water_surf( halfview, surfnorm, depthvalue, beneath, above, dudva ); + vsurface.a -= fdist; + + oColour = vec4( mix( rippled.rgb*0.9, vsurface.rgb, vsurface.a ), 1.0 ); +} diff --git a/submodules/miniaudio b/submodules/miniaudio new file mode 160000 index 0000000..f40cf03 --- /dev/null +++ b/submodules/miniaudio @@ -0,0 +1 @@ +Subproject commit f40cf03f80cdb7e741d43e53b7e706e8c1394bcf -- 2.25.1