+++ /dev/null
-/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
-
-/*
- * Memory model:
- * [global (.data)] [temp-stack] [system-stack] [game-heap]
- *
- * 1. Program starts: .data memory is loaded
- * 2. System initialization:
- * 2a. the large heap buffer is allocated
- * 2b. each engine system is initialized in order, using some of the
- * system stack
- * 2c. game systems are also put into here
- */
-
-
-#ifndef VG_HEADER_H
-#define VG_HEADER_H
-
-#include "vg_platform.h"
-#include "vg_mem.h"
-
-#ifndef _WIN32
-#include <execinfo.h>
-#endif
-
-
-#if defined(VG_SERVER) || defined(VG_TOOLS)
- #define VG_NON_CLIENT
-#endif
-
-#ifndef VG_SERVER
-#include "../../dep/glad/glad.h"
-
-#define GLFW_INCLUDE_GLCOREARB
-
-#ifdef _WIN32
- #define GLFW_DLL
-#endif
-
-#include "../../dep/glfw/glfw3.h"
-#endif
-
-#include "vg_stdint.h"
-
-void vg_register_exit( void( *funcptr )(void), const char *name );
-
-#include "vg_m.h"
-#include "vg_io.h"
-#include "vg_log.h"
-
-#ifdef VG_STEAM
-//#include "vg_steamworks.h"
-#include "vg_steam.h"
-#endif
-
-#ifndef VG_NON_CLIENT
-
-struct vg
-{
- /* Engine sync */
- GLFWwindow* window;
-
- vg_mutex mux_context;
- vg_semaphore sem_allow_exec,
- sem_exec_finished,
- sem_loader,
- sem_fatal;
- int exec_context;
-
- vg_mutex mux_engine_status;
- 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;
-
- /* Window information */
- int window_x,
- window_y,
- samples;
- float refresh_rate;
-
- double mouse_pos[2];
- v2f
- mouse_delta,
- mouse_wheel;
-
- /* Runtime */
- double time,
- time_delta,
- frame_delta,
- time_real,
- time_real_last,
- time_rate,
- accumulator;
-
- int fixed_iterations;
-
- enum engine_stage
- {
- k_engine_stage_none,
- k_engine_stage_update,
- k_engine_stage_update_fixed,
- k_engine_stage_rendering,
- k_engine_stage_ui
- }
- engine_stage;
-
- /* graphics */
- m4x4f pv;
- enum quality_profile
- {
- k_quality_profile_high = 0,
- k_quality_profile_low = 1,
- }
- quality_profile;
-
- /* Gamepad */
- GLFWgamepadstate gamepad;
- int gamepad_ready;
- const char *gamepad_name;
- int gamepad_id;
- int gamepad_use_trackpad_look;
-}
-VG_STATIC vg = { .time_rate = 1.0 };
-
-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(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
-#else
- #define VG_SYNC_LOG(...)
-#endif
-
-VG_STATIC void vg_fatal_exit_loop( const char *error );
-VG_STATIC void vg_required( void *ptr, const char *path )
-{
- if( !ptr )
- {
- vg_fatal_exit_loop( path );
- }
-}
-
-
-VG_STATIC void vg_ensure_engine_running(void)
-{
- /* Check if the engine is no longer running */
- vg_mutex_lock( &vg.mux_engine_status );
- if( vg.engine_status != k_engine_status_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_semaphore_post( &vg.sem_loader );
- }
-
- 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.
- */
-VG_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;
-
- assert( vg_thread_info.purpose == k_thread_purpose_loader );
-
- vg_ensure_engine_running();
-
- /* 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.
- */
-VG_STATIC void vg_release_thread_sync(void)
-{
- if( vg_thread_info.purpose == k_thread_purpose_main )
- return;
-
- assert( vg_thread_info.purpose == k_thread_purpose_loader );
-
- /* signal that we are done */
- vg_thread_info.gl_context_level --;
-
- if( !vg_thread_info.gl_context_level )
- {
- VG_SYNC_LOG( "[%d] Releasing context.\n" );
- glfwMakeContextCurrent( NULL );
- vg_semaphore_post( &vg.sem_exec_finished );
- }
-}
-
-VG_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 );
-}
-
-VG_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 );
-
- vg_set_thread_name( "[vg] Main" );
- vg_thread_info.purpose = k_thread_purpose_main;
- vg_thread_info.gl_context_level = 1;
-}
-
-VG_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_console.h"
-#include "vg_profiler.h"
-#include "vg_audio.h"
-#include "vg_shader.h"
-#include "vg_tex.h"
-#include "vg_input.h"
-#include "vg_ui.h"
-#include "vg_lines.h"
-#include "vg_loader.h"
-#include "vg_opt.h"
-
-/* Diagnostic */
-VG_STATIC struct vg_profile vg_prof_update = {.name="update()"},
- vg_prof_render = {.name="render()"};
-
-#define VG_GAMELOOP
-VG_STATIC void vg_register(void) VG_GAMELOOP;
-VG_STATIC void vg_start(void) VG_GAMELOOP;
-
-VG_STATIC void vg_update(int loaded) VG_GAMELOOP;
-VG_STATIC void vg_update_fixed(int loaded) VG_GAMELOOP;
-VG_STATIC void vg_update_post(int loaded) VG_GAMELOOP;
-
-VG_STATIC void vg_framebuffer_resize(int w, int h) VG_GAMELOOP;
-VG_STATIC void vg_render(void) VG_GAMELOOP;
-VG_STATIC void vg_ui(void) VG_GAMELOOP;
-
-VG_STATIC void vg_checkgl( const char *src_info )
-{
- int fail = 0;
-
- GLenum err;
- while( (err = glGetError()) != GL_NO_ERROR )
- {
- vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
- fail = 1;
- }
-
- if( fail )
- vg_fatal_exit_loop( "OpenGL Error" );
-}
-
-void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
-{
- vg.mouse_delta[0] += xpos - vg.mouse_pos[0];
- vg.mouse_delta[1] += ypos - vg.mouse_pos[1];
-
- vg.mouse_pos[0] = xpos;
- vg.mouse_pos[1] = ypos;
-}
-
-void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
-{
- vg.mouse_wheel[0] += xoffset;
- vg.mouse_wheel[1] += yoffset;
-}
-
-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;
-
- vg_framebuffer_resize(w,h);
-}
-
-VG_STATIC void vg_bake_shaders(void)
-{
- vg_acquire_thread_sync();
-
-#if 0
- vg_function_push( (struct vg_cmd)
- {
- .name = "shaders",
- .function = vg_shaders_live_recompile
- });
-#endif
-
- vg_shaders_compile();
- vg_release_thread_sync();
-}
-
-VG_STATIC void vg_preload(void);
-VG_STATIC void vg_load(void);
-VG_STATIC void vg_load_full(void)
-{
- vg_preload();
-
- /* internal */
- vg_loader_highwater( vg_gamepad_init, NULL, NULL );
- vg_loader_highwater( vg_lines_init, NULL, NULL );
- vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
- vg_loader_highwater( vg_profiler_init, NULL, NULL );
-
- /* client */
- vg_load();
-
- vg_acquire_thread_sync();
- vg.is_loaded = 1;
- vg_release_thread_sync();
-}
-
-VG_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 ) ) );
- }
-
- if( vg_long_opt( "use-libc-malloc" ) )
- {
- vg_mem.use_libc_malloc = atoi( arg );
- }
-
- if( vg_long_opt( "high-performance" ) )
- {
- vg.quality_profile = k_quality_profile_low;
- }
- }
-
- vg_alloc_quota();
- vg_log_init();
- vg_console_init();
-
- glfwInit();
- glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
- glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
- glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE );
- //glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_FALSE );
- glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR, GLFW_RELEASE_BEHAVIOR_FLUSH );
-
- glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE );
- glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
-
- glfwWindowHint( GLFW_SAMPLES, vg.samples );
-
- GLFWmonitor *monitor_primary = glfwGetPrimaryMonitor();
-
- const GLFWvidmode *mode = glfwGetVideoMode( monitor_primary );
- glfwWindowHint( GLFW_RED_BITS, mode->redBits );
- glfwWindowHint( GLFW_GREEN_BITS, mode->greenBits );
- glfwWindowHint( GLFW_BLUE_BITS, mode->blueBits );
-
- glfwWindowHint( GLFW_REFRESH_RATE, mode->refreshRate );
-
- if( !vg.window_x )
- vg.window_x = mode->width;
-
- if( !vg.window_y )
- vg.window_y = mode->height;
-
- vg.refresh_rate = mode->refreshRate;
-
- 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 );
-
- glfwSetWindowSizeLimits( vg.window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
- glfwSetFramebufferSizeCallback( vg.window, vg_framebuffer_resize_callback );
-
- glfwSetCursorPosCallback( vg.window, vg_mouse_callback );
- glfwSetScrollCallback( vg.window, vg_scroll_callback );
-
- glfwSetCharCallback( vg.window, console_proc_wchar );
- glfwSetKeyCallback( vg.window, console_proc_key );
- glfwSetInputMode( vg.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED );
-
- if( glfwRawMouseMotionSupported() )
- glfwSetInputMode( vg.window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE );
-
- if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) )
- {
- vg_error( "Glad Failed to initialize\n" );
- glfwTerminate();
- return;
- }
-
- const unsigned char* glver = glGetString( GL_VERSION );
- vg_success( "Load setup complete, OpenGL version: %s\n", glver );
-
- /* init systems
- * -----------------------------------------------------------------------*/
- ui_init_context();
- vg_loader_init();
-
- vg_mutex_init( &vg.mux_engine_status );
- vg.engine_status = k_engine_status_running;
-
- vg_opengl_sync_init();
- vg_loader_start();
-
- vg.accumulator = 0.75f * (1.0f/60.0f);
-
- int loaded = 0;
- while(1)
- {
- if( glfwWindowShouldClose( vg.window ) )
- break;
-
- v2_zero( vg.mouse_wheel );
- v2_zero( vg.mouse_delta );
-
- glfwPollEvents();
-
- vg.time_real_last = vg.time_real;
- vg.time_real = glfwGetTime();
- vg.frame_delta = vg.time_real-vg.time_real_last;
-
- /* scaled time */
- vg.time_delta = vg.frame_delta * vg.time_rate;
- vg.time += vg.time_delta;
-
- if( vg.is_loaded )
- {
- if( !loaded )
- {
- vg_start();
- loaded = 1;
- }
- }
- else
- {
- vg_loader_render();
- }
-
- /*
- * Game logic
- * -------------------------------------------------------
- */
- vg_profile_begin( &vg_prof_update );
- vg_update_inputs();
-
- vg.engine_stage = k_engine_stage_update;
- vg_update( loaded );
-
- /* Fixed update loop */
- vg.engine_stage = k_engine_stage_update_fixed;
- vg.accumulator += vg.time_delta;
-
- vg.fixed_iterations = 0;
- vg_lines.allow_input = 1;
- while( vg.accumulator >= (VG_TIMESTEP_FIXED-0.00125) )
- {
- vg_update_fixed( loaded );
- vg_lines.allow_input = 0;
-
- vg.accumulator -= VG_TIMESTEP_FIXED;
- vg.accumulator = VG_MAX( 0.0, vg.accumulator );
-
- vg.fixed_iterations ++;
- if( vg.fixed_iterations == 8 )
- {
- break;
- }
- }
- vg_lines.allow_input = 1;
-
- /*
- * Rendering
- * ---------------------------------------------
- */
- vg.engine_stage = k_engine_stage_update;
- vg_update_post( loaded );
- vg_profile_end( &vg_prof_update );
-
- vg_profile_begin( &vg_prof_render );
-
- if( loaded )
- {
- /* render */
- vg.engine_stage = k_engine_stage_rendering;
- vg_render();
-
- /* ui */
- vg.engine_stage = k_engine_stage_ui;
- {
- ui_begin( vg.window_x, vg.window_y );
-
- /* TODO */
- ui_set_mouse( vg.mouse_pos[0], vg.mouse_pos[1], 0 );
-
- vg_profile_drawn(
- (struct vg_profile *[]){&vg_prof_update,&vg_prof_render}, 2,
- (1.0f/(float)vg.refresh_rate)*1000.0f,
- (ui_rect){ 4, 4, 250, 0 }, 0
- );
-
- if( vg_profiler )
- {
-
- char perf[128];
-
- snprintf( perf, 127,
- "x: %d y: %d\n"
- "refresh: %.1f (%.1fms)\n"
- "samples: %d\n"
- "iterations: %d (acc: %.3fms%%)\n",
- vg.window_x, vg.window_y,
- vg.refresh_rate, (1.0f/vg.refresh_rate)*1000.0f,
- vg.samples,
- vg.fixed_iterations,
- (vg.accumulator/VG_TIMESTEP_FIXED)*100.0f );
-
- ui_text( (ui_rect){258, 4+24+12,0,0},perf, 1,0);
- }
-
- audio_debug_ui( vg.pv );
- vg_ui();
- vg_console_draw();
-
- ui_resolve();
- ui_draw( NULL );
- }
- }
-
- vg_profile_end( &vg_prof_render );
-
- glfwSwapBuffers( vg.window );
- vg_run_synced_content();
- }
-
- vg_console_write_persistent();
-
- vg_mutex_lock( &vg.mux_engine_status );
- vg.engine_status = k_engine_status_none;
- vg_mutex_unlock( &vg.mux_engine_status );
-
- vg_loader_free();
-
- vg_success( "If you see this it means everything went.. \"well\".....\n" );
- 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.
- *
- * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, ONLY
- * when calling the program from outside its normal directory.
- */
-VG_STATIC void vg_fatal_exit_loop( const char *error )
-{
- /*
- * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
- * thanks gnu <3
- *
- * TODO: this on windows?
- */
-
-#ifndef _WIN32
-
- void *array[20];
- char **strings;
- int size, i;
-
- size = backtrace( array, 20 );
- strings = backtrace_symbols( array, size );
-
- if( strings != NULL )
- {
- vg_error( "---------------- gnu backtrace -------------\n" );
-
- for( int i=0; i<size; i++ )
- vg_info( "%s\n", strings[i] );
-
- vg_error( "---------------- gnu backtrace -------------\n" );
- }
-
- free( strings );
-
-#endif
-
- 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);
-
- while(1)
- {
- if( glfwWindowShouldClose( vg.window ) )
- break;
-
- if( glfwGetKey( vg.window, GLFW_KEY_ESCAPE ) )
- break;
-
- glfwPollEvents();
-
- glBindFramebuffer( GL_FRAMEBUFFER, 0 );
- glEnable(GL_BLEND);
- glDisable(GL_DEPTH_TEST);
- glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
- glBlendEquation(GL_FUNC_ADD);
-
- 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();
- glfwTerminate();
- exit(0);
- }
-}
-
-#else
-
-VG_STATIC void vg_fatal_exit_loop( const char *error )
-{
- vg_error( "Fatal error: %s\n", error );
- exit(0);
-}
-
-#endif
-
-/*
- * Graphic cards will check these to force it to use the GPU
- */
-u32 NvOptimusEnablement = 0x00000001;
-int AmdPowerXpressRequestHighPerformance = 1;
-
-#endif