simplify gitignore
[vg.git] / src / vg / vg.h
index e6f4001469395ee47869c591a2075de60f185885..77d27e2fb9f80553cacad4469608b79f82a4678d 100644 (file)
@@ -1,9 +1,27 @@
 /* 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)
 
 #ifndef VG_SERVER
 #include "../../dep/glad/glad.h"
+
+#define GLFW_INCLUDE_GLCOREARB
+
+#ifdef _WIN32
+  #define GLFW_DLL
+#endif
+
 #include "../../dep/glfw/glfw3.h"
 #endif
 
@@ -29,28 +54,6 @@ void vg_register_exit( void( *funcptr )(void), const char *name );
 #endif
 
 #ifndef VG_NON_CLIENT
-#include "vg_gldiag.h"
-#endif
-
-#ifndef VG_NON_CLIENT
-
-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
-
-v2f vg_mouse;
-v2f vg_mouse_wheel;
-v3f vg_mouse_ws;
-
-double vg_time,
-       vg_time_last,
-       vg_time_delta;
 
 struct vg
 {
@@ -73,16 +76,57 @@ struct vg
    }
    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;
 }
-static vg;
+VG_STATIC vg = { .time_rate = 1.0 };
 
 struct vg_thread_info
 {
@@ -108,9 +152,17 @@ static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
   #define VG_SYNC_LOG(...)
 #endif
 
-static void vg_fatal_exit_loop( const char *error );
+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 );
+   }
+}
+
 
-static void vg_ensure_engine_running(void)
+VG_STATIC void vg_ensure_engine_running(void)
 {
    /* Check if the engine is no longer running */
    vg_mutex_lock( &vg.mux_engine_status );
@@ -135,7 +187,7 @@ static void vg_ensure_engine_running(void)
  * 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)
+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 )
@@ -172,7 +224,7 @@ static void vg_acquire_thread_sync(void)
  * 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(void)
+VG_STATIC void vg_release_thread_sync(void)
 {
    if( vg_thread_info.purpose == k_thread_purpose_main )
       return;
@@ -190,7 +242,7 @@ static void vg_release_thread_sync(void)
    }
 }
 
-static void vg_run_synced_content(void)
+VG_STATIC void vg_run_synced_content(void)
 {
    assert( vg_thread_info.purpose == k_thread_purpose_main );
 
@@ -222,7 +274,7 @@ static void vg_run_synced_content(void)
    vg_mutex_unlock( &vg.mux_context );
 }
 
-static void vg_opengl_sync_init(void)
+VG_STATIC void vg_opengl_sync_init(void)
 {
    vg_semaphore_init( &vg.sem_allow_exec, 0 );
    vg_semaphore_init( &vg.sem_exec_finished, 0 );
@@ -235,29 +287,38 @@ static void vg_opengl_sync_init(void)
    vg_thread_info.gl_context_level = 1;
 }
 
-static void vg_checkgl( const char *src_info );
+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_console.h"
 #include "vg_lines.h"
-#include "vg_debug.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
-static void vg_register(void) VG_GAMELOOP;
-static void vg_start(void) VG_GAMELOOP;
-static void vg_update(int loaded) VG_GAMELOOP;
-static void vg_framebuffer_resize(int w, int h) VG_GAMELOOP;
-static void vg_render(void) VG_GAMELOOP;
-static void vg_ui(void) VG_GAMELOOP;
-
-static void vg_checkgl( const char *src_info )
+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;
 
@@ -274,52 +335,60 @@ static void vg_checkgl( const char *src_info )
 
 void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
 {
-   vg_mouse[0] = xpos;
-   vg_mouse[1] = 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;
+   vg.mouse_wheel[0] += xoffset;
+   vg.mouse_wheel[1] += yoffset;
 }
 
 void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
 {
-   vg_window_x = w;
-   vg_window_y = 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);
 }
 
-static int vg_bake_shaders(void)
+VG_STATIC void vg_bake_shaders(void)
 {
    vg_acquire_thread_sync();
 
-   if( !vg_shaders_recompile() )
-   {
-      vg_shaders_free(NULL);
-      vg_release_thread_sync();
-      return 0;
-   }
-   else
+#if 0
+   vg_function_push( (struct vg_cmd)
    {
-      vg_release_thread_sync();
-      vg_loader_highwater( NULL, vg_shaders_free, NULL );
-      return 1;
-   }
+      .name = "shaders",
+      .function = vg_shaders_live_recompile
+   });
+#endif
+
+   vg_shaders_compile();
+   vg_release_thread_sync();
 }
 
-void vg_preload(void);
-void vg_load(void);
-static void vg_load_full(void)
+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, vg_lines_free, 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();
@@ -329,8 +398,38 @@ static void vg_load_full(void)
    vg_release_thread_sync();
 }
 
-static void vg_enter( int argc, char *argv[], const char *window_name )
+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();
 
@@ -338,14 +437,13 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
    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_TRUE );
+   //glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_FALSE );
+   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();
    
@@ -353,20 +451,48 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
    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)) )
+
+   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 );
 
@@ -378,9 +504,10 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
    
    glfwSetCharCallback( vg.window, console_proc_wchar );
    glfwSetKeyCallback( vg.window, console_proc_key );
-#if 0
-   glfwSetInputMode(vg_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
-#endif
+   glfwSetInputMode( vg.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED );
+
+   if( glfwRawMouseMotionSupported() )
+      glfwSetInputMode( vg.window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE );
 
    if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) ) 
    {
@@ -391,13 +518,11 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
 
    const unsigned char* glver = glGetString( GL_VERSION );
    vg_success( "Load setup complete, OpenGL version: %s\n", glver );
-   vg_run_gfx_diagnostics();
-
-   if( !ui_default_init() )
-      goto il_exit_ui;
 
-   if( !vg_loader_init() )
-      goto il_exit_loader;
+   /* init systems 
+    * -----------------------------------------------------------------------*/
+   ui_init_context();
+   vg_loader_init();
 
    vg_mutex_init( &vg.mux_engine_status );
    vg.engine_status = k_engine_status_running;
@@ -405,25 +530,29 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
    vg_opengl_sync_init();
    vg_loader_start();
 
+   vg.accumulator = 0.75f * (1.0f/60.0f);
+
    int loaded = 0;
-   int counter =0;
    while(1)
    {
       if( glfwWindowShouldClose( vg.window ) )
          break;
 
-      v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
+      v2_zero( vg.mouse_wheel );
+      v2_zero( vg.mouse_delta );
+
       glfwPollEvents();
 
-      vg_time_last = vg_time;
-      vg_time = glfwGetTime();
-      vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
+      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 )
       {
-         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 );
-         
          if( !loaded )
          {
             vg_start();
@@ -435,35 +564,98 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
          vg_loader_render();
       }
 
+      /* 
+       * Game logic 
+       * -------------------------------------------------------
+       */
+      vg_profile_begin( &vg_prof_update );
       vg_update_inputs();
+
+      vg.engine_stage = k_engine_stage_update;
       vg_update( loaded );
 
-      counter ++;
+      /* Fixed update loop */
+      vg.engine_stage = k_engine_stage_update_fixed;
+      vg.accumulator += vg.time_delta;
 
-#if 0
-      if( counter == 30 )
-         vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
-#endif
+      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( &ui_global_ctx, vg_window_x, vg_window_y );
-            ui_set_mouse( &ui_global_ctx, vg_mouse[0], vg_mouse[1], 
-                  vg_get_button_state( "primary" ) );
+            ui_begin( vg.window_x, vg.window_y );
+
+            /* TODO */
+            ui_set_mouse( vg.mouse_pos[0], vg.mouse_pos[1], 0 );
             
-            audio_debug_ui( vg_pv );
+            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_global_ctx );
-            ui_draw( &ui_global_ctx, NULL );
+            ui_resolve();
+            ui_draw( NULL );
          }
       }
 
+      vg_profile_end( &vg_prof_render );
+
       glfwSwapBuffers( vg.window );
       vg_run_synced_content();
    }
@@ -476,19 +668,49 @@ static void vg_enter( int argc, char *argv[], const char *window_name )
 
    vg_loader_free();
 
-il_exit_loader:
-   ui_default_free();
-
-il_exit_ui:
+   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.
  */
-static void vg_fatal_exit_loop( const char *error )
+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 ) );
 
@@ -539,21 +761,26 @@ static void vg_fatal_exit_loop( const char *error )
       }
       vg_audio_free(NULL);
 
-      /*
-       * todo: draw error loop
-       */
       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);
 
-         vg_time = glfwGetTime();
-         vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
-      
-         glClearColor( sinf(vg_time*20.0)*0.5f+0.5f, 0.0f, 0.0f,1.0f );
+         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 );
@@ -561,13 +788,19 @@ static void vg_fatal_exit_loop( const char *error )
 
       /* Can now shutdown and EXIT */
       vg_loader_free();
-      ui_default_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
 
 /*