[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
{
if game_engine
+ include source/engine
{
add source/engine/main.c
name ENGINE_UI
prototype "void"
}
+ event
+ {
+ name ENGINE_WINDOW_RESIZE
+ prototype "void"
+ }
}
{
}
}
+ {
+ add source/engine/vg_framebuffer.c
+ hook
+ {
+ event ENGINE_WINDOW_RESIZE
+ function _framebuffer_resize
+ }
+ }
+
add source/engine/shader.c
input_layer
{
/* Voyager common application interface */
+#pragma once
#define VG_PRE_MAIN \
_exit_init(); \
void *window_handle;
i32 w, h;
+
+ i32 native_fbo;
}
extern _engine;
#include "generated/console.h"
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;
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 )
{
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 );
+
/* ------------- */
_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 );
--- /dev/null
+#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 );
+ }
+ }
+}
--- /dev/null
+#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 );
#pragma once
#include "opengl.h"
+#include "common_api.h"
/* TODO: Include Okaypeg alongside QOI. */
--- /dev/null
+#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;
+}
--- /dev/null
+#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 );
--- /dev/null
+add vg_water.c
+include ""
+
+shader
+{
+ name vg_water
+
+ subshader
+ {
+ type vertex
+ }
+}
--- /dev/null
+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 );
+}
--- /dev/null
+Subproject commit f40cf03f80cdb7e741d43e53b7e706e8c1394bcf