stuff
authorhgn <hgodden00@gmail.com>
Sat, 18 Oct 2025 23:18:19 +0000 (00:18 +0100)
committerhgn <hgodden00@gmail.com>
Sat, 18 Oct 2025 23:18:19 +0000 (00:18 +0100)
13 files changed:
.gitmodules
foundation.kv
include/common_api.h
include/engine_interface.h
source/engine/main.c
source/engine/vg_framebuffer.c [new file with mode: 0644]
source/engine/vg_framebuffer.h [new file with mode: 0644]
source/vg_tex/vg_tex.h
source/vg_water/vg_water.c [new file with mode: 0644]
source/vg_water/vg_water.h [new file with mode: 0644]
source/vg_water/vg_water.kv [new file with mode: 0644]
source/vg_water/water.fs [new file with mode: 0644]
submodules/miniaudio [new submodule]

index af460476150c0fcc67f2be18a0d33b08ee37b2fb..8fbe57b9e8ca68ace6fb5b7c072f773b5a188d8c 100644 (file)
@@ -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
index 6891d36aebda3b5000aa758e1dda41cf34dd556c..cd18ebd293397bf09497635511b55d4e1995d8da 100644 (file)
@@ -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
    {
index b8e2ecd22addc0c245f5b0ce5c2340312e0bbaa9..f504ab8207dbe3e9eb0eff8d42fd73bd9cb43cd1 100644 (file)
@@ -1,4 +1,5 @@
 /* Voyager common application interface */
+#pragma once
 
 #define VG_PRE_MAIN \
    _exit_init(); \
index 58c7a1d15bc5b890a2399c664c2ddf2ecede85b0..a428d9934ac44e47c1446210016b380bc8bf043c 100644 (file)
@@ -14,6 +14,8 @@ struct _engine
 
    void *window_handle;
    i32 w, h;
+
+   i32 native_fbo;
 }
 extern _engine;
 #include "generated/console.h"
index 029ae1b68890f3d7dbebea78729584720e555599..aec649815733994d07bb8abb8636bb42dcf809ec 100644 (file)
@@ -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 (file)
index 0000000..76efbc5
--- /dev/null
@@ -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; i<ARRAY_COUNT(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 c8 *render_fb_format_str( GLenum format )
+{
+   struct { GLenum e; const c8 *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( i32 i=0; i<ARRAY_COUNT(formats); i++ )
+      if( formats[i].e == format )
+         return formats[i].str;
+
+   return "UNDEFINED";
+}
+
+/*
+ * Bind and allocate texture for framebuffer attachment
+ */
+static void framebuffer_allocate_texture( struct framebuffer *fb, struct framebuffer_attachment *a )
+{
+   ASSERT_CRITICAL( _thread_has_flags( _get_thread_id(), THREAD_FLAG_OPENGL ) );
+   if( a->tex.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; j<fb->attachment_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; j<fb->attachment_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; j<fb->attachment_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 (file)
index 0000000..eed53ac
--- /dev/null
@@ -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 );
index b2d4385496a9cd6800f721826fff6220bdf4e1ff..b528fe9a5332595f1fdcc51bfb19435223f0d6ab 100644 (file)
@@ -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 (file)
index 0000000..3f6d539
--- /dev/null
@@ -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; i<world->surface_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; i<world->surface_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 (file)
index 0000000..e7538f7
--- /dev/null
@@ -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 (file)
index 0000000..4c15cda
--- /dev/null
@@ -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 (file)
index 0000000..a7be103
--- /dev/null
@@ -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 (submodule)
index 0000000..f40cf03
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit f40cf03f80cdb7e741d43e53b7e706e8c1394bcf