#ifndef VG_HEADER_H
#define VG_HEADER_H
-#include <stdio.h>
-#include <stdlib.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <math.h>
+#include "vg_platform.h"
+
#if defined(VG_SERVER) || defined(VG_TOOLS)
#define VG_NON_CLIENT
#ifndef VG_SERVER
#include "../../dep/glad/glad.h"
-#include "../../dep/glfw/glfw3.h"
-#endif
-#define STB_DS_IMPLEMENTATION
-#include "stb/stb_ds.h"
+#define GLFW_INCLUDE_GLCOREARB
+
+#ifdef _WIN32
+ #define GLFW_DLL
+#endif
-#define QOI_IMPLEMENTATION
-#include "phoboslab/qoi.h"
+#include "../../dep/glfw/glfw3.h"
+#endif
#include "vg_stdint.h"
-#include "vg_platform.h"
void vg_register_exit( void( *funcptr )(void), const char *name );
m4x4f vg_pv;
-#ifdef VG_CAPTURE_MODE
-int vg_window_x = 1920;
-int vg_window_y = 1080;
-#else
-int vg_window_x = 1366;
-int vg_window_y = 768;
-#endif
+int vg_window_x = 0;
+int vg_window_y = 0;
+int vg_samples = 0;
v2f vg_mouse;
v2f vg_mouse_wheel;
vg_time_last,
vg_time_delta;
-
struct vg
{
/* Engine sync */
vg_mutex mux_context;
vg_semaphore sem_allow_exec,
- sem_exec_finished;
+ sem_exec_finished,
+ sem_loader,
+ sem_fatal;
int exec_context;
vg_mutex mux_engine_status;
- int engine_running;
+ enum engine_status
+ {
+ k_engine_status_none,
+ k_engine_status_running,
+ k_engine_status_crashed
+ }
+ engine_status;
+ const char *str_const_engine_err;
+ int is_loaded;
/* Gamepad */
GLFWgamepadstate gamepad;
}
static vg;
+struct vg_thread_info
+{
+ enum vg_thread_purpose
+ {
+ k_thread_purpose_nothing,
+ k_thread_purpose_main,
+ k_thread_purpose_loader
+ }
+ purpose;
+
+ int gl_context_level;
+};
+
+static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
+
//#define VG_SYNC_DEBUG
#ifdef VG_SYNC_DEBUG
- #define VG_SYNC_LOG(...) vg_info(__VA_ARGS__)
+ #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
#else
#define VG_SYNC_LOG(...)
#endif
-/*
- * Sync execution so that the OpenGL context is switched onto this thread.
- * Anything after this call will be in a valid context.
- */
-static int vg_acquire_thread_sync( int id )
+static void vg_fatal_exit_loop( const char *error );
+
+static void vg_ensure_engine_running(void)
{
- if( id == 0 )
- {
- /* no action */
- return 1;
- }
- else
+ /* Check if the engine is no longer running */
+ vg_mutex_lock( &vg.mux_engine_status );
+ if( vg.engine_status != k_engine_status_running )
{
- /* Check if the engine is no longer running */
- vg_mutex_lock( &vg.mux_engine_status );
- if( !vg.engine_running )
+ VG_SYNC_LOG( "[%d] Engine is no longer running\n");
+ vg_mutex_unlock( &vg.mux_engine_status );
+
+ /* Safe to disregard loader thread from this point on, elswhere */
+ if( vg_thread_info.purpose == k_thread_purpose_loader )
{
- VG_SYNC_LOG( "[%d] Engine is no longer running\n", id );
- vg_mutex_unlock( &vg.mux_engine_status );
- return 0;
+ vg_semaphore_post( &vg.sem_loader );
}
- vg_mutex_unlock( &vg.mux_engine_status );
+ VG_SYNC_LOG( "[%d] about to kill\n");
+ vg_thread_exit();
+ }
+ vg_mutex_unlock( &vg.mux_engine_status );
+}
+
+/*
+ * Sync execution so that the OpenGL context is switched onto this thread.
+ * Anything after this call will be in a valid context.
+ */
+static void vg_acquire_thread_sync(void)
+{
+ /* We dont want to do anything if this is the main thread */
+ if( vg_thread_info.purpose == k_thread_purpose_main )
+ return;
- vg_mutex_lock( &vg.mux_context );
- VG_SYNC_LOG( "[%d] Signal to sync.\n", id );
- vg.exec_context = id;
- vg_mutex_unlock( &vg.mux_context );
-
- /* wait until told we can go */
- VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n", id );
- vg_semaphore_wait( &vg.sem_allow_exec );
- glfwMakeContextCurrent( vg.window );
+ assert( vg_thread_info.purpose == k_thread_purpose_loader );
- /* context now valid to work in while we hold up main thread */
- VG_SYNC_LOG( "[%d] Context acquired.\n", id );
+ vg_ensure_engine_running();
- return 1;
+ /* Check if thread already has the context */
+ if( vg_thread_info.gl_context_level )
+ {
+ vg_thread_info.gl_context_level ++;
+ VG_SYNC_LOG( "[%d] We already have sync here\n" );
+ return;
}
+
+ vg_mutex_lock( &vg.mux_context );
+ VG_SYNC_LOG( "[%d] Signal to sync.\n" );
+ vg.exec_context = 1;
+ vg_mutex_unlock( &vg.mux_context );
+
+ /* wait until told we can go */
+ VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
+ vg_semaphore_wait( &vg.sem_allow_exec );
+ glfwMakeContextCurrent( vg.window );
+
+ /* context now valid to work in while we hold up main thread */
+ VG_SYNC_LOG( "[%d] Context acquired.\n" );
+ vg_thread_info.gl_context_level ++;
}
/*
* Signify that we are done with the OpenGL context in this thread.
* Anything after this call will be in an undefined context.
*/
-static void vg_release_thread_sync( int id )
+static void vg_release_thread_sync(void)
{
- if( id == 0 )
- {
- vg_mutex_lock( &vg.mux_context );
-
- if( vg.exec_context != 0 )
- {
- VG_SYNC_LOG( "[%d] Allowing content (%d).\n", id, vg.exec_context );
+ if( vg_thread_info.purpose == k_thread_purpose_main )
+ return;
- /* allow operations to go */
- glfwMakeContextCurrent( NULL );
- vg_semaphore_post( &vg.sem_allow_exec );
+ assert( vg_thread_info.purpose == k_thread_purpose_loader );
- /* wait for operations to complete */
- VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", id, vg.exec_context );
- vg_semaphore_wait( &vg.sem_exec_finished );
-
- /* re-engage main thread */
- VG_SYNC_LOG( "[%d] Re-engaging.\n", id );
- vg.exec_context = 0;
- glfwMakeContextCurrent( vg.window );
- }
+ /* signal that we are done */
+ vg_thread_info.gl_context_level --;
- vg_mutex_unlock( &vg.mux_context );
- }
- else
+ if( !vg_thread_info.gl_context_level )
{
- /* signal that we are done */
- VG_SYNC_LOG( "[%d] Releasing context.\n", id );
+ VG_SYNC_LOG( "[%d] Releasing context.\n" );
glfwMakeContextCurrent( NULL );
vg_semaphore_post( &vg.sem_exec_finished );
}
}
+static void vg_run_synced_content(void)
+{
+ assert( vg_thread_info.purpose == k_thread_purpose_main );
+
+ vg_mutex_lock( &vg.mux_context );
+
+ if( vg.exec_context != 0 )
+ {
+ VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
+
+ /* allow operations to go */
+ vg_thread_info.gl_context_level = 0;
+ glfwMakeContextCurrent( NULL );
+ vg_semaphore_post( &vg.sem_allow_exec );
+
+ /* wait for operations to complete */
+ VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg.exec_context );
+ vg_semaphore_wait( &vg.sem_exec_finished );
+
+ /* check if we killed the engine */
+ vg_ensure_engine_running();
+
+ /* re-engage main thread */
+ VG_SYNC_LOG( "[%d] Re-engaging.\n" );
+ vg.exec_context = 0;
+ glfwMakeContextCurrent( vg.window );
+ vg_thread_info.gl_context_level = 1;
+ }
+
+ vg_mutex_unlock( &vg.mux_context );
+}
+
static void vg_opengl_sync_init(void)
{
vg_semaphore_init( &vg.sem_allow_exec, 0 );
vg_semaphore_init( &vg.sem_exec_finished, 0 );
+ vg_semaphore_init( &vg.sem_loader, 1 );
+ vg_semaphore_init( &vg.sem_fatal, 1 );
vg_mutex_init( &vg.mux_context );
-}
-static void vg_opengl_sync_free(void)
-{
- vg_semaphore_free( &vg.sem_allow_exec );
- vg_semaphore_free( &vg.sem_exec_finished );
- vg_mutex_free( &vg.mux_context );
+ vg_set_thread_name( "[vg] Main" );
+ vg_thread_info.purpose = k_thread_purpose_main;
+ vg_thread_info.gl_context_level = 1;
}
-
-int vg_checkgl( const char *src_info );
+static void vg_checkgl( const char *src_info );
#define VG_STRINGIT( X ) #X
#define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
#include "vg_lines.h"
#include "vg_debug.h"
#include "vg_loader.h"
+#include "vg_opt.h"
#define VG_GAMELOOP
static void vg_register(void) VG_GAMELOOP;
static void vg_render(void) VG_GAMELOOP;
static void vg_ui(void) VG_GAMELOOP;
-int vg_checkgl( const char *src_info )
+static void vg_checkgl( const char *src_info )
{
int fail = 0;
fail = 1;
}
- return fail;
+ if( fail )
+ vg_fatal_exit_loop( "OpenGL Error" );
}
-
-
void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
{
vg_mouse[0] = xpos;
void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
{
+ if( !w || !h )
+ {
+ vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w, h );
+ return;
+ }
+
vg_window_x = w;
vg_window_y = h;
static int vg_bake_shaders(void)
{
- if( vg_acquire_thread_sync(1) )
- {
- if( !vg_shaders_recompile() )
- {
- vg_shaders_free(NULL);
- vg_release_thread_sync(1);
- return 0;
- }
- else
- {
- vg_release_thread_sync(1);
+ vg_acquire_thread_sync();
- if( !vg_loader_highwater( vg_shaders_free, NULL ) ) return 0;
- else return 1;
- }
+ if( !vg_shaders_recompile() )
+ {
+ vg_shaders_free(NULL);
+ vg_release_thread_sync();
+ return 0;
+ }
+ else
+ {
+ vg_release_thread_sync();
+ vg_loader_highwater( NULL, vg_shaders_free, NULL );
+ return 1;
}
-
- return 0;
}
-int vg_preload(void);
-int vg_load(void);
-static int vg_load_full(void)
+void vg_preload(void);
+void vg_load(void);
+static void vg_load_full(void)
{
- if( !vg_preload() ) return 0;
+ vg_preload();
/* internal */
- if( !vg_gamepad_init() ) return 0;
- if( !vg_loader_highwater( NULL, NULL ) ) return 0;
-
- if( !vg_lines_init() ) return 0;
- if( !vg_loader_highwater( vg_lines_free, NULL ) ) return 0;
-
- if( !vg_audio_init() ) return 0;
- if( !vg_loader_highwater( vg_audio_free, NULL ) ) return 0;
+ vg_loader_highwater( vg_gamepad_init, NULL, NULL );
+ vg_loader_highwater( vg_lines_init, vg_lines_free, NULL );
+ vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
/* client */
- if( !vg_load() ) return 0;
+ vg_load();
- return 1;
+ vg_acquire_thread_sync();
+ vg.is_loaded = 1;
+ vg_release_thread_sync();
}
static void vg_enter( int argc, char *argv[], const char *window_name )
{
+ char *arg;
+ while( vg_argp( argc, argv ) )
+ {
+ if( (arg = vg_opt_arg( 'w' )) )
+ {
+ vg_window_x = atoi( arg );
+ }
+
+ if( (arg = vg_opt_arg( 'h' )) )
+ {
+ vg_window_y = atoi( arg );
+ }
+
+ if( (arg = vg_long_opt_arg( "samples" )) )
+ {
+ vg_samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
+ }
+ }
+
vg_log_init();
vg_console_init();
glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE );
glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE );
+ glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR, GLFW_RELEASE_BEHAVIOR_FLUSH );
- glfwWindowHint( GLFW_RESIZABLE, GLFW_TRUE );
+ glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE );
glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
-#if 0
- glfwWindowHint(GLFW_SAMPLES,4);
-#endif
+ glfwWindowHint( GLFW_SAMPLES, vg_samples );
GLFWmonitor *monitor_primary = glfwGetPrimaryMonitor();
glfwWindowHint( GLFW_RED_BITS, mode->redBits );
glfwWindowHint( GLFW_GREEN_BITS, mode->greenBits );
glfwWindowHint( GLFW_BLUE_BITS, mode->blueBits );
-
- /* This is set like this because of an OS issue */
- int refresh_rate = mode->refreshRate;
- if( refresh_rate < 28 || refresh_rate >= 144 )
- refresh_rate = 60;
- glfwWindowHint( GLFW_REFRESH_RATE, refresh_rate );
-
- if( !(vg.window = glfwCreateWindow( vg_window_x, vg_window_y,
- window_name, NULL, NULL)) )
+
+ /* TODO? */
+ glfwWindowHint( GLFW_REFRESH_RATE, 60 );
+
+ if( !vg_window_x )
+ vg_window_x = mode->width;
+
+ if( !vg_window_y )
+ vg_window_y = mode->height;
+
+
+ if( (vg.window = glfwCreateWindow( vg_window_x, vg_window_y,
+ window_name, monitor_primary, NULL)) )
+ {
+ glfwGetFramebufferSize( vg.window, &vg_window_x, &vg_window_y );
+ vg_success( "Window created (%dx%d)\n", vg_window_x, vg_window_y );
+ }
+ else
{
vg_error( "GLFW Failed to initialize\n" );
return;
}
+ /* We need 3.1.2 for correct VSync on windows */
+ {
+ int vmaj, vmin, vrev;
+ glfwGetVersion( &vmaj, &vmin, &vrev );
+
+ if( vmaj < 3 ||
+ (vmaj == 3 && vmin < 1) ||
+ (vmaj == 3 && vmin == 1 && vrev < 2 ) )
+ {
+ vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
+ vmaj, vmin, vrev );
+
+ glfwTerminate();
+ return;
+ }
+
+ vg_success( "GLFW Version %d.%d.%d\n", vmaj, vmin, vrev );
+ }
+
glfwMakeContextCurrent( vg.window );
glfwSwapInterval( 1 );
goto il_exit_loader;
vg_mutex_init( &vg.mux_engine_status );
- vg.engine_running = 1;
+ vg.engine_status = k_engine_status_running;
vg_opengl_sync_init();
vg_loader_start();
int loaded = 0;
-
+ int counter =0;
while(1)
{
- vg_acquire_thread_sync( 0 );
-
if( glfwWindowShouldClose( vg.window ) )
- {
break;
- }
v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
glfwPollEvents();
vg_time = glfwGetTime();
vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
- enum loader_status load_status = vg_loader_status();
-
- if( load_status == k_loader_status_complete )
+ if( vg.is_loaded )
{
glClearColor( 0.0f,sinf(vg_time*20.0)*0.5f+0.5f,0.0f,1.0f );
glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
}
else
{
- if( load_status == k_loader_status_fail )
- {
- break;
- }
- else
- {
- vg_loader_render();
- }
+ vg_loader_render();
}
vg_update_inputs();
vg_update( loaded );
+ counter ++;
+
+#if 0
+ if( counter == 30 )
+ vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
+#endif
+
if( loaded )
{
vg_render();
}
}
-
-
-
glfwSwapBuffers( vg.window );
-
- if( VG_CHECK_GL_ERR() )
- break;
-
- vg_release_thread_sync( 0 );
+ vg_run_synced_content();
}
vg_console_write_persistent();
vg_mutex_lock( &vg.mux_engine_status );
- vg.engine_running = 0;
+ vg.engine_status = k_engine_status_none;
vg_mutex_unlock( &vg.mux_engine_status );
-
vg_loader_free();
- vg_opengl_sync_free();
-
- vg_mutex_free( &vg.mux_engine_status );
il_exit_loader:
ui_default_free();
glfwTerminate();
}
+/*
+ * Immediately transfer away from calling thread into a safe loop, signal for
+ * others to shutdown, then free everything once the user closes the window.
+ */
+static void vg_fatal_exit_loop( const char *error )
+{
+ vg_error( "Fatal error: %s\n", error );
+ assert( vg_semaphore_trywait( &vg.sem_fatal ) );
+
+ vg_mutex_lock( &vg.mux_engine_status );
+
+ if( vg.engine_status == k_engine_status_none )
+ {
+ vg_mutex_unlock( &vg.mux_engine_status );
+
+ /* TODO: Correct shutdown before other systems */
+ exit(0);
+ }
+ else
+ {
+ vg_mutex_unlock( &vg.mux_engine_status );
+
+ /*
+ * if main
+ * if loader running
+ * wait until loader checks in, it will die
+ * else
+ * pass immediately
+ * else
+ * if have context
+ * pass immediately
+ * else
+ * wait for main to get to us, it will never be used again
+ *
+ * undefined behaviour:
+ * fatal_exit_loop is called in both threads, preventing an appropriate
+ * reaction to the crash. This *should* be made
+ * obvious by the assertion
+ */
+ vg_acquire_thread_sync();
+
+ vg_mutex_lock( &vg.mux_engine_status );
+ vg.engine_status = k_engine_status_crashed;
+ vg.str_const_engine_err = error;
+ vg_mutex_unlock( &vg.mux_engine_status );
+
+ /*
+ * Wait for loader to finish what it was doing, if it was running.
+ * Then we can continue in our nice error screen
+ */
+ if( vg_thread_info.purpose == k_thread_purpose_main )
+ {
+ vg_semaphore_wait( &vg.sem_loader );
+ }
+ vg_audio_free(NULL);
+
+ /*
+ * todo: draw error loop
+ */
+ while(1)
+ {
+ if( glfwWindowShouldClose( vg.window ) )
+ break;
+
+ glfwPollEvents();
+
+ glClearColor( 0.15f + sinf(glfwGetTime())*0.1f, 0.0f, 0.0f,1.0f );
+ glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
+ glViewport( 0,0, vg_window_x, vg_window_y );
+
+ vg_render_log();
+
+ glfwSwapBuffers( vg.window );
+ }
+
+ /* Can now shutdown and EXIT */
+ vg_loader_free();
+ ui_default_free();
+
+ glfwTerminate();
+ exit(0);
+ }
+}
+
+#else
+
+static void vg_fatal_exit_loop( const char *error )
+{
+ vg_error( "Fatal error: %s\n", error );
+ exit(0);
+}
+
#endif
/*