1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
5 * [global (.data)] [temp-stack] [system-stack] [game-heap]
7 * 1. Program starts: .data memory is loaded
8 * 2. System initialization:
9 * 2a. the large heap buffer is allocated
10 * 2b. each engine system is initialized in order, using some of the
12 * 2c. game systems are also put into here
19 #include "vg_platform.h"
27 #if defined(VG_SERVER) || defined(VG_TOOLS)
32 #include "../../dep/glad/glad.h"
34 #define GLFW_INCLUDE_GLCOREARB
40 #include "../../dep/glfw/glfw3.h"
43 #include "vg_stdint.h"
45 void vg_register_exit( void( *funcptr
)(void), const char *name
);
52 //#include "vg_steamworks.h"
64 vg_semaphore sem_allow_exec
,
70 vg_mutex mux_engine_status
;
74 k_engine_status_running
,
75 k_engine_status_crashed
78 const char *str_const_engine_err
;
81 /* Window information */
101 int fixed_iterations
;
106 k_engine_stage_update
,
107 k_engine_stage_update_fixed
,
108 k_engine_stage_rendering
,
117 k_quality_profile_high
= 0,
118 k_quality_profile_low
= 1,
123 GLFWgamepadstate gamepad
;
125 const char *gamepad_name
;
127 int gamepad_use_trackpad_look
;
129 VG_STATIC vg
= { .time_rate
= 1.0 };
131 struct vg_thread_info
133 enum vg_thread_purpose
135 k_thread_purpose_nothing
,
136 k_thread_purpose_main
,
137 k_thread_purpose_loader
141 int gl_context_level
;
144 static VG_THREAD_LOCAL
struct vg_thread_info vg_thread_info
;
147 //#define VG_SYNC_DEBUG
150 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
152 #define VG_SYNC_LOG(...)
155 VG_STATIC
void vg_fatal_exit_loop( const char *error
);
156 VG_STATIC
void vg_required( void *ptr
, const char *path
)
160 vg_fatal_exit_loop( path
);
165 VG_STATIC
void vg_ensure_engine_running(void)
167 /* Check if the engine is no longer running */
168 vg_mutex_lock( &vg
.mux_engine_status
);
169 if( vg
.engine_status
!= k_engine_status_running
)
171 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
172 vg_mutex_unlock( &vg
.mux_engine_status
);
174 /* Safe to disregard loader thread from this point on, elswhere */
175 if( vg_thread_info
.purpose
== k_thread_purpose_loader
)
177 vg_semaphore_post( &vg
.sem_loader
);
180 VG_SYNC_LOG( "[%d] about to kill\n");
183 vg_mutex_unlock( &vg
.mux_engine_status
);
187 * Sync execution so that the OpenGL context is switched onto this thread.
188 * Anything after this call will be in a valid context.
190 VG_STATIC
void vg_acquire_thread_sync(void)
192 /* We dont want to do anything if this is the main thread */
193 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
196 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
198 vg_ensure_engine_running();
200 /* Check if thread already has the context */
201 if( vg_thread_info
.gl_context_level
)
203 vg_thread_info
.gl_context_level
++;
204 VG_SYNC_LOG( "[%d] We already have sync here\n" );
208 vg_mutex_lock( &vg
.mux_context
);
209 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
211 vg_mutex_unlock( &vg
.mux_context
);
213 /* wait until told we can go */
214 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
215 vg_semaphore_wait( &vg
.sem_allow_exec
);
216 glfwMakeContextCurrent( vg
.window
);
218 /* context now valid to work in while we hold up main thread */
219 VG_SYNC_LOG( "[%d] Context acquired.\n" );
220 vg_thread_info
.gl_context_level
++;
224 * Signify that we are done with the OpenGL context in this thread.
225 * Anything after this call will be in an undefined context.
227 VG_STATIC
void vg_release_thread_sync(void)
229 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
232 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
234 /* signal that we are done */
235 vg_thread_info
.gl_context_level
--;
237 if( !vg_thread_info
.gl_context_level
)
239 VG_SYNC_LOG( "[%d] Releasing context.\n" );
240 glfwMakeContextCurrent( NULL
);
241 vg_semaphore_post( &vg
.sem_exec_finished
);
245 VG_STATIC
void vg_run_synced_content(void)
247 assert( vg_thread_info
.purpose
== k_thread_purpose_main
);
249 vg_mutex_lock( &vg
.mux_context
);
251 if( vg
.exec_context
!= 0 )
253 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg
.exec_context
);
255 /* allow operations to go */
256 vg_thread_info
.gl_context_level
= 0;
257 glfwMakeContextCurrent( NULL
);
258 vg_semaphore_post( &vg
.sem_allow_exec
);
260 /* wait for operations to complete */
261 VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg
.exec_context
);
262 vg_semaphore_wait( &vg
.sem_exec_finished
);
264 /* check if we killed the engine */
265 vg_ensure_engine_running();
267 /* re-engage main thread */
268 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
270 glfwMakeContextCurrent( vg
.window
);
271 vg_thread_info
.gl_context_level
= 1;
274 vg_mutex_unlock( &vg
.mux_context
);
277 VG_STATIC
void vg_opengl_sync_init(void)
279 vg_semaphore_init( &vg
.sem_allow_exec
, 0 );
280 vg_semaphore_init( &vg
.sem_exec_finished
, 0 );
281 vg_semaphore_init( &vg
.sem_loader
, 1 );
282 vg_semaphore_init( &vg
.sem_fatal
, 1 );
283 vg_mutex_init( &vg
.mux_context
);
285 vg_set_thread_name( "[vg] Main" );
286 vg_thread_info
.purpose
= k_thread_purpose_main
;
287 vg_thread_info
.gl_context_level
= 1;
290 VG_STATIC
void vg_checkgl( const char *src_info
);
291 #define VG_STRINGIT( X ) #X
292 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
294 #include "vg_console.h"
295 #include "vg_profiler.h"
296 #include "vg_audio.h"
297 #include "vg_shader.h"
299 #include "vg_input.h"
301 #include "vg_lines.h"
302 #include "vg_loader.h"
306 VG_STATIC
struct vg_profile vg_prof_update
= {.name
="update()"},
307 vg_prof_render
= {.name
="render()"};
310 VG_STATIC
void vg_register(void) VG_GAMELOOP
;
311 VG_STATIC
void vg_start(void) VG_GAMELOOP
;
313 VG_STATIC
void vg_update(int loaded
) VG_GAMELOOP
;
314 VG_STATIC
void vg_update_fixed(int loaded
) VG_GAMELOOP
;
315 VG_STATIC
void vg_update_post(int loaded
) VG_GAMELOOP
;
317 VG_STATIC
void vg_framebuffer_resize(int w
, int h
) VG_GAMELOOP
;
318 VG_STATIC
void vg_render(void) VG_GAMELOOP
;
319 VG_STATIC
void vg_ui(void) VG_GAMELOOP
;
321 VG_STATIC
void vg_checkgl( const char *src_info
)
326 while( (err
= glGetError()) != GL_NO_ERROR
)
328 vg_error( "(%s) OpenGL Error: #%d\n", src_info
, err
);
333 vg_fatal_exit_loop( "OpenGL Error" );
336 void vg_mouse_callback( GLFWwindow
* ptrW
, double xpos
, double ypos
)
338 vg
.mouse_delta
[0] += xpos
- vg
.mouse_pos
[0];
339 vg
.mouse_delta
[1] += ypos
- vg
.mouse_pos
[1];
341 vg
.mouse_pos
[0] = xpos
;
342 vg
.mouse_pos
[1] = ypos
;
345 void vg_scroll_callback( GLFWwindow
* ptrW
, double xoffset
, double yoffset
)
347 vg
.mouse_wheel
[0] += xoffset
;
348 vg
.mouse_wheel
[1] += yoffset
;
351 void vg_framebuffer_resize_callback( GLFWwindow
*ptrW
, int w
, int h
)
355 vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w
, h
);
362 vg_framebuffer_resize(w
,h
);
365 VG_STATIC
void vg_bake_shaders(void)
367 vg_acquire_thread_sync();
370 vg_function_push( (struct vg_cmd
)
373 .function
= vg_shaders_live_recompile
377 vg_shaders_compile();
378 vg_release_thread_sync();
381 VG_STATIC
void vg_preload(void);
382 VG_STATIC
void vg_load(void);
383 VG_STATIC
void vg_load_full(void)
388 vg_loader_highwater( vg_gamepad_init
, NULL
, NULL
);
389 vg_loader_highwater( vg_lines_init
, NULL
, NULL
);
390 vg_loader_highwater( vg_audio_init
, vg_audio_free
, NULL
);
391 vg_loader_highwater( vg_profiler_init
, NULL
, NULL
);
396 vg_acquire_thread_sync();
398 vg_release_thread_sync();
401 VG_STATIC
void vg_enter( int argc
, char *argv
[], const char *window_name
)
404 while( vg_argp( argc
, argv
) )
406 if( (arg
= vg_opt_arg( 'w' )) )
408 vg
.window_x
= atoi( arg
);
411 if( (arg
= vg_opt_arg( 'h' )) )
413 vg
.window_y
= atoi( arg
);
416 if( (arg
= vg_long_opt_arg( "samples" )) )
418 vg
.samples
= VG_MAX( 0, VG_MIN( 8, atoi( arg
) ) );
421 if( vg_long_opt( "use-libc-malloc" ) )
423 vg_mem
.use_libc_malloc
= atoi( arg
);
426 if( vg_long_opt( "high-performance" ) )
428 vg
.quality_profile
= k_quality_profile_low
;
437 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR
, 3 );
438 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR
, 3 );
439 glfwWindowHint( GLFW_OPENGL_PROFILE
, GLFW_OPENGL_CORE_PROFILE
);
440 //glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_FALSE );
441 glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR
, GLFW_RELEASE_BEHAVIOR_FLUSH
);
443 glfwWindowHint( GLFW_RESIZABLE
, GLFW_FALSE
);
444 glfwWindowHint( GLFW_DOUBLEBUFFER
, GLFW_TRUE
);
446 glfwWindowHint( GLFW_SAMPLES
, vg
.samples
);
448 GLFWmonitor
*monitor_primary
= glfwGetPrimaryMonitor();
450 const GLFWvidmode
*mode
= glfwGetVideoMode( monitor_primary
);
451 glfwWindowHint( GLFW_RED_BITS
, mode
->redBits
);
452 glfwWindowHint( GLFW_GREEN_BITS
, mode
->greenBits
);
453 glfwWindowHint( GLFW_BLUE_BITS
, mode
->blueBits
);
455 glfwWindowHint( GLFW_REFRESH_RATE
, mode
->refreshRate
);
458 vg
.window_x
= mode
->width
;
461 vg
.window_y
= mode
->height
;
463 vg
.refresh_rate
= mode
->refreshRate
;
465 if( (vg
.window
= glfwCreateWindow( vg
.window_x
, vg
.window_y
,
466 window_name
, monitor_primary
, NULL
)) )
468 glfwGetFramebufferSize( vg
.window
, &vg
.window_x
, &vg
.window_y
);
469 vg_success( "Window created (%dx%d)\n", vg
.window_x
, vg
.window_y
);
473 vg_error( "GLFW Failed to initialize\n" );
477 /* We need 3.1.2 for correct VSync on windows */
479 int vmaj
, vmin
, vrev
;
480 glfwGetVersion( &vmaj
, &vmin
, &vrev
);
483 (vmaj
== 3 && vmin
< 1) ||
484 (vmaj
== 3 && vmin
== 1 && vrev
< 2 ) )
486 vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
493 vg_success( "GLFW Version %d.%d.%d\n", vmaj
, vmin
, vrev
);
496 glfwMakeContextCurrent( vg
.window
);
497 glfwSwapInterval( 1 );
499 glfwSetWindowSizeLimits( vg
.window
, 800, 600, GLFW_DONT_CARE
,GLFW_DONT_CARE
);
500 glfwSetFramebufferSizeCallback( vg
.window
, vg_framebuffer_resize_callback
);
502 glfwSetCursorPosCallback( vg
.window
, vg_mouse_callback
);
503 glfwSetScrollCallback( vg
.window
, vg_scroll_callback
);
505 glfwSetCharCallback( vg
.window
, console_proc_wchar
);
506 glfwSetKeyCallback( vg
.window
, console_proc_key
);
507 glfwSetInputMode( vg
.window
, GLFW_CURSOR
, GLFW_CURSOR_DISABLED
);
509 if( glfwRawMouseMotionSupported() )
510 glfwSetInputMode( vg
.window
, GLFW_RAW_MOUSE_MOTION
, GLFW_TRUE
);
512 if( !gladLoadGLLoader((GLADloadproc
)glfwGetProcAddress
) )
514 vg_error( "Glad Failed to initialize\n" );
519 const unsigned char* glver
= glGetString( GL_VERSION
);
520 vg_success( "Load setup complete, OpenGL version: %s\n", glver
);
523 * -----------------------------------------------------------------------*/
527 vg_mutex_init( &vg
.mux_engine_status
);
528 vg
.engine_status
= k_engine_status_running
;
530 vg_opengl_sync_init();
533 vg
.accumulator
= 0.75f
* (1.0f
/60.0f
);
538 if( glfwWindowShouldClose( vg
.window
) )
541 v2_zero( vg
.mouse_wheel
);
542 v2_zero( vg
.mouse_delta
);
546 vg
.time_real_last
= vg
.time_real
;
547 vg
.time_real
= glfwGetTime();
548 vg
.frame_delta
= vg
.time_real
-vg
.time_real_last
;
551 vg
.time_delta
= vg
.frame_delta
* vg
.time_rate
;
552 vg
.time
+= vg
.time_delta
;
569 * -------------------------------------------------------
571 vg_profile_begin( &vg_prof_update
);
574 vg
.engine_stage
= k_engine_stage_update
;
577 /* Fixed update loop */
578 vg
.engine_stage
= k_engine_stage_update_fixed
;
579 vg
.accumulator
+= vg
.time_delta
;
581 vg
.fixed_iterations
= 0;
582 vg_lines
.allow_input
= 1;
583 while( vg
.accumulator
>= (VG_TIMESTEP_FIXED
-0.00125) )
585 vg_update_fixed( loaded
);
586 vg_lines
.allow_input
= 0;
588 vg
.accumulator
-= VG_TIMESTEP_FIXED
;
589 vg
.accumulator
= VG_MAX( 0.0, vg
.accumulator
);
591 vg
.fixed_iterations
++;
592 if( vg
.fixed_iterations
== 8 )
597 vg_lines
.allow_input
= 1;
601 * ---------------------------------------------
603 vg
.engine_stage
= k_engine_stage_update
;
604 vg_update_post( loaded
);
605 vg_profile_end( &vg_prof_update
);
607 vg_profile_begin( &vg_prof_render
);
612 vg
.engine_stage
= k_engine_stage_rendering
;
616 vg
.engine_stage
= k_engine_stage_ui
;
618 ui_begin( vg
.window_x
, vg
.window_y
);
621 ui_set_mouse( vg
.mouse_pos
[0], vg
.mouse_pos
[1], 0 );
624 (struct vg_profile
*[]){&vg_prof_update
,&vg_prof_render
}, 2,
625 (1.0f
/(float)vg
.refresh_rate
)*1000.0f
,
626 (ui_rect
){ 4, 4, 250, 0 }, 0
636 "refresh: %.1f (%.1fms)\n"
638 "iterations: %d (acc: %.3fms%%)\n",
639 vg
.window_x
, vg
.window_y
,
640 vg
.refresh_rate
, (1.0f
/vg
.refresh_rate
)*1000.0f
,
643 (vg
.accumulator
/VG_TIMESTEP_FIXED
)*100.0f
);
645 ui_text( (ui_rect
){258, 4+24+12,0,0},perf
, 1,0);
648 audio_debug_ui( vg
.pv
);
657 vg_profile_end( &vg_prof_render
);
659 glfwSwapBuffers( vg
.window
);
660 vg_run_synced_content();
663 vg_console_write_persistent();
665 vg_mutex_lock( &vg
.mux_engine_status
);
666 vg
.engine_status
= k_engine_status_none
;
667 vg_mutex_unlock( &vg
.mux_engine_status
);
671 vg_success( "If you see this it means everything went.. \"well\".....\n" );
676 * Immediately transfer away from calling thread into a safe loop, signal for
677 * others to shutdown, then free everything once the user closes the window.
679 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, ONLY
680 * when calling the program from outside its normal directory.
682 VG_STATIC
void vg_fatal_exit_loop( const char *error
)
685 * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
688 * TODO: this on windows?
697 size
= backtrace( array
, 20 );
698 strings
= backtrace_symbols( array
, size
);
700 if( strings
!= NULL
)
702 vg_error( "---------------- gnu backtrace -------------\n" );
704 for( int i
=0; i
<size
; i
++ )
705 vg_info( "%s\n", strings
[i
] );
707 vg_error( "---------------- gnu backtrace -------------\n" );
714 vg_error( "Fatal error: %s\n", error
);
715 assert( vg_semaphore_trywait( &vg
.sem_fatal
) );
717 vg_mutex_lock( &vg
.mux_engine_status
);
719 if( vg
.engine_status
== k_engine_status_none
)
721 vg_mutex_unlock( &vg
.mux_engine_status
);
723 /* TODO: Correct shutdown before other systems */
728 vg_mutex_unlock( &vg
.mux_engine_status
);
733 * wait until loader checks in, it will die
740 * wait for main to get to us, it will never be used again
742 * undefined behaviour:
743 * fatal_exit_loop is called in both threads, preventing an appropriate
744 * reaction to the crash. This *should* be made
745 * obvious by the assertion
747 vg_acquire_thread_sync();
749 vg_mutex_lock( &vg
.mux_engine_status
);
750 vg
.engine_status
= k_engine_status_crashed
;
751 vg
.str_const_engine_err
= error
;
752 vg_mutex_unlock( &vg
.mux_engine_status
);
755 * Wait for loader to finish what it was doing, if it was running.
756 * Then we can continue in our nice error screen
758 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
760 vg_semaphore_wait( &vg
.sem_loader
);
766 if( glfwWindowShouldClose( vg
.window
) )
769 if( glfwGetKey( vg
.window
, GLFW_KEY_ESCAPE
) )
774 glBindFramebuffer( GL_FRAMEBUFFER
, 0 );
776 glDisable(GL_DEPTH_TEST
);
777 glBlendFunc(GL_ONE_MINUS_DST_ALPHA
, GL_DST_ALPHA
);
778 glBlendEquation(GL_FUNC_ADD
);
780 glClearColor( 0.15f
+ sinf(glfwGetTime())*0.1f
, 0.0f
, 0.0f
,1.0f
);
781 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
782 glViewport( 0,0, vg
.window_x
, vg
.window_y
);
786 glfwSwapBuffers( vg
.window
);
789 /* Can now shutdown and EXIT */
798 VG_STATIC
void vg_fatal_exit_loop( const char *error
)
800 vg_error( "Fatal error: %s\n", error
);
807 * Graphic cards will check these to force it to use the GPU
809 u32 NvOptimusEnablement
= 0x00000001;
810 int AmdPowerXpressRequestHighPerformance
= 1;