1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
6 #include "vg_platform.h"
9 #if defined(VG_SERVER) || defined(VG_TOOLS)
14 #include "../../dep/glad/glad.h"
16 #define GLFW_INCLUDE_GLCOREARB
22 #include "../../dep/glfw/glfw3.h"
25 #include "vg_stdint.h"
27 void vg_register_exit( void( *funcptr
)(void), const char *name
);
34 //#include "vg_steamworks.h"
39 #include "vg_gldiag.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
;
83 GLFWgamepadstate gamepad
;
85 const char *gamepad_name
;
92 enum vg_thread_purpose
94 k_thread_purpose_nothing
,
95 k_thread_purpose_main
,
96 k_thread_purpose_loader
100 int gl_context_level
;
103 static VG_THREAD_LOCAL
struct vg_thread_info vg_thread_info
;
106 //#define VG_SYNC_DEBUG
109 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
111 #define VG_SYNC_LOG(...)
114 static void vg_fatal_exit_loop( const char *error
);
116 static void vg_ensure_engine_running(void)
118 /* Check if the engine is no longer running */
119 vg_mutex_lock( &vg
.mux_engine_status
);
120 if( vg
.engine_status
!= k_engine_status_running
)
122 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
123 vg_mutex_unlock( &vg
.mux_engine_status
);
125 /* Safe to disregard loader thread from this point on, elswhere */
126 if( vg_thread_info
.purpose
== k_thread_purpose_loader
)
128 vg_semaphore_post( &vg
.sem_loader
);
131 VG_SYNC_LOG( "[%d] about to kill\n");
134 vg_mutex_unlock( &vg
.mux_engine_status
);
138 * Sync execution so that the OpenGL context is switched onto this thread.
139 * Anything after this call will be in a valid context.
141 static void vg_acquire_thread_sync(void)
143 /* We dont want to do anything if this is the main thread */
144 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
147 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
149 vg_ensure_engine_running();
151 /* Check if thread already has the context */
152 if( vg_thread_info
.gl_context_level
)
154 vg_thread_info
.gl_context_level
++;
155 VG_SYNC_LOG( "[%d] We already have sync here\n" );
159 vg_mutex_lock( &vg
.mux_context
);
160 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
162 vg_mutex_unlock( &vg
.mux_context
);
164 /* wait until told we can go */
165 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
166 vg_semaphore_wait( &vg
.sem_allow_exec
);
167 glfwMakeContextCurrent( vg
.window
);
169 /* context now valid to work in while we hold up main thread */
170 VG_SYNC_LOG( "[%d] Context acquired.\n" );
171 vg_thread_info
.gl_context_level
++;
175 * Signify that we are done with the OpenGL context in this thread.
176 * Anything after this call will be in an undefined context.
178 static void vg_release_thread_sync(void)
180 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
183 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
185 /* signal that we are done */
186 vg_thread_info
.gl_context_level
--;
188 if( !vg_thread_info
.gl_context_level
)
190 VG_SYNC_LOG( "[%d] Releasing context.\n" );
191 glfwMakeContextCurrent( NULL
);
192 vg_semaphore_post( &vg
.sem_exec_finished
);
196 static void vg_run_synced_content(void)
198 assert( vg_thread_info
.purpose
== k_thread_purpose_main
);
200 vg_mutex_lock( &vg
.mux_context
);
202 if( vg
.exec_context
!= 0 )
204 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg
.exec_context
);
206 /* allow operations to go */
207 vg_thread_info
.gl_context_level
= 0;
208 glfwMakeContextCurrent( NULL
);
209 vg_semaphore_post( &vg
.sem_allow_exec
);
211 /* wait for operations to complete */
212 VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg
.exec_context
);
213 vg_semaphore_wait( &vg
.sem_exec_finished
);
215 /* check if we killed the engine */
216 vg_ensure_engine_running();
218 /* re-engage main thread */
219 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
221 glfwMakeContextCurrent( vg
.window
);
222 vg_thread_info
.gl_context_level
= 1;
225 vg_mutex_unlock( &vg
.mux_context
);
228 static void vg_opengl_sync_init(void)
230 vg_semaphore_init( &vg
.sem_allow_exec
, 0 );
231 vg_semaphore_init( &vg
.sem_exec_finished
, 0 );
232 vg_semaphore_init( &vg
.sem_loader
, 1 );
233 vg_semaphore_init( &vg
.sem_fatal
, 1 );
234 vg_mutex_init( &vg
.mux_context
);
236 vg_set_thread_name( "[vg] Main" );
237 vg_thread_info
.purpose
= k_thread_purpose_main
;
238 vg_thread_info
.gl_context_level
= 1;
241 static void vg_checkgl( const char *src_info
);
242 #define VG_STRINGIT( X ) #X
243 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
245 #include "vg_audio.h"
246 #include "vg_shader.h"
248 #include "vg_input.h"
250 #include "vg_console.h"
251 #include "vg_lines.h"
252 #include "vg_debug.h"
253 #include "vg_loader.h"
257 static void vg_register(void) VG_GAMELOOP
;
258 static void vg_start(void) VG_GAMELOOP
;
259 static void vg_update(int loaded
) VG_GAMELOOP
;
260 static void vg_framebuffer_resize(int w
, int h
) VG_GAMELOOP
;
261 static void vg_render(void) VG_GAMELOOP
;
262 static void vg_ui(void) VG_GAMELOOP
;
264 static void vg_checkgl( const char *src_info
)
269 while( (err
= glGetError()) != GL_NO_ERROR
)
271 vg_error( "(%s) OpenGL Error: #%d\n", src_info
, err
);
276 vg_fatal_exit_loop( "OpenGL Error" );
279 void vg_mouse_callback( GLFWwindow
* ptrW
, double xpos
, double ypos
)
285 void vg_scroll_callback( GLFWwindow
* ptrW
, double xoffset
, double yoffset
)
287 vg_mouse_wheel
[0] += xoffset
;
288 vg_mouse_wheel
[1] += yoffset
;
291 void vg_framebuffer_resize_callback( GLFWwindow
*ptrW
, int w
, int h
)
295 vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w
, h
);
302 vg_framebuffer_resize(w
,h
);
305 static int vg_bake_shaders(void)
307 vg_acquire_thread_sync();
309 if( !vg_shaders_recompile() )
311 vg_shaders_free(NULL
);
312 vg_release_thread_sync();
317 vg_release_thread_sync();
318 vg_loader_highwater( NULL
, vg_shaders_free
, NULL
);
323 void vg_preload(void);
325 static void vg_load_full(void)
330 vg_loader_highwater( vg_gamepad_init
, NULL
, NULL
);
331 vg_loader_highwater( vg_lines_init
, vg_lines_free
, NULL
);
332 vg_loader_highwater( vg_audio_init
, vg_audio_free
, NULL
);
337 vg_acquire_thread_sync();
339 vg_release_thread_sync();
342 static void vg_enter( int argc
, char *argv
[], const char *window_name
)
345 while( vg_argp( argc
, argv
) )
347 if( (arg
= vg_opt_arg( 'w' )) )
349 vg_window_x
= atoi( arg
);
352 if( (arg
= vg_opt_arg( 'h' )) )
354 vg_window_y
= atoi( arg
);
357 if( (arg
= vg_long_opt_arg( "samples" )) )
359 vg_samples
= VG_MAX( 0, VG_MIN( 8, atoi( arg
) ) );
367 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR
, 3 );
368 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR
, 3 );
369 glfwWindowHint( GLFW_OPENGL_PROFILE
, GLFW_OPENGL_CORE_PROFILE
);
370 glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT
, GL_TRUE
);
371 glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR
, GLFW_RELEASE_BEHAVIOR_FLUSH
);
373 glfwWindowHint( GLFW_RESIZABLE
, GLFW_FALSE
);
374 glfwWindowHint( GLFW_DOUBLEBUFFER
, GLFW_TRUE
);
376 glfwWindowHint( GLFW_SAMPLES
, vg_samples
);
378 GLFWmonitor
*monitor_primary
= glfwGetPrimaryMonitor();
380 const GLFWvidmode
*mode
= glfwGetVideoMode( monitor_primary
);
381 glfwWindowHint( GLFW_RED_BITS
, mode
->redBits
);
382 glfwWindowHint( GLFW_GREEN_BITS
, mode
->greenBits
);
383 glfwWindowHint( GLFW_BLUE_BITS
, mode
->blueBits
);
386 glfwWindowHint( GLFW_REFRESH_RATE
, 60 );
389 vg_window_x
= mode
->width
;
392 vg_window_y
= mode
->height
;
395 if( (vg
.window
= glfwCreateWindow( vg_window_x
, vg_window_y
,
396 window_name
, monitor_primary
, NULL
)) )
398 glfwGetFramebufferSize( vg
.window
, &vg_window_x
, &vg_window_y
);
399 vg_success( "Window created (%dx%d)\n", vg_window_x
, vg_window_y
);
403 vg_error( "GLFW Failed to initialize\n" );
407 /* We need 3.1.2 for correct VSync on windows */
409 int vmaj
, vmin
, vrev
;
410 glfwGetVersion( &vmaj
, &vmin
, &vrev
);
413 (vmaj
== 3 && vmin
< 1) ||
414 (vmaj
== 3 && vmin
== 1 && vrev
< 2 ) )
416 vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
423 vg_success( "GLFW Version %d.%d.%d\n", vmaj
, vmin
, vrev
);
426 glfwMakeContextCurrent( vg
.window
);
427 glfwSwapInterval( 1 );
429 glfwSetWindowSizeLimits( vg
.window
, 800, 600, GLFW_DONT_CARE
,GLFW_DONT_CARE
);
430 glfwSetFramebufferSizeCallback( vg
.window
, vg_framebuffer_resize_callback
);
432 glfwSetCursorPosCallback( vg
.window
, vg_mouse_callback
);
433 glfwSetScrollCallback( vg
.window
, vg_scroll_callback
);
435 glfwSetCharCallback( vg
.window
, console_proc_wchar
);
436 glfwSetKeyCallback( vg
.window
, console_proc_key
);
438 glfwSetInputMode(vg_window
, GLFW_CURSOR
, GLFW_CURSOR_HIDDEN
);
441 if( !gladLoadGLLoader((GLADloadproc
)glfwGetProcAddress
) )
443 vg_error( "Glad Failed to initialize\n" );
448 const unsigned char* glver
= glGetString( GL_VERSION
);
449 vg_success( "Load setup complete, OpenGL version: %s\n", glver
);
450 vg_run_gfx_diagnostics();
452 if( !ui_default_init() )
455 if( !vg_loader_init() )
458 vg_mutex_init( &vg
.mux_engine_status
);
459 vg
.engine_status
= k_engine_status_running
;
461 vg_opengl_sync_init();
468 if( glfwWindowShouldClose( vg
.window
) )
471 v2_copy( (v2f
){ 0.0f
, 0.0f
}, vg_mouse_wheel
);
474 vg_time_last
= vg_time
;
475 vg_time
= glfwGetTime();
476 vg_time_delta
= vg_minf( vg_time
- vg_time_last
, 0.1f
);
480 glClearColor( 0.0f
,sinf(vg_time
*20.0)*0.5f
+0.5f
,0.0f
,1.0f
);
481 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
501 vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
510 ui_begin( &ui_global_ctx
, vg_window_x
, vg_window_y
);
511 ui_set_mouse( &ui_global_ctx
, vg_mouse
[0], vg_mouse
[1],
512 vg_get_button_state( "primary" ) );
514 audio_debug_ui( vg_pv
);
518 ui_resolve( &ui_global_ctx
);
519 ui_draw( &ui_global_ctx
, NULL
);
523 glfwSwapBuffers( vg
.window
);
524 vg_run_synced_content();
527 vg_console_write_persistent();
529 vg_mutex_lock( &vg
.mux_engine_status
);
530 vg
.engine_status
= k_engine_status_none
;
531 vg_mutex_unlock( &vg
.mux_engine_status
);
543 * Immediately transfer away from calling thread into a safe loop, signal for
544 * others to shutdown, then free everything once the user closes the window.
546 static void vg_fatal_exit_loop( const char *error
)
548 vg_error( "Fatal error: %s\n", error
);
549 assert( vg_semaphore_trywait( &vg
.sem_fatal
) );
551 vg_mutex_lock( &vg
.mux_engine_status
);
553 if( vg
.engine_status
== k_engine_status_none
)
555 vg_mutex_unlock( &vg
.mux_engine_status
);
557 /* TODO: Correct shutdown before other systems */
562 vg_mutex_unlock( &vg
.mux_engine_status
);
567 * wait until loader checks in, it will die
574 * wait for main to get to us, it will never be used again
576 * undefined behaviour:
577 * fatal_exit_loop is called in both threads, preventing an appropriate
578 * reaction to the crash. This *should* be made
579 * obvious by the assertion
581 vg_acquire_thread_sync();
583 vg_mutex_lock( &vg
.mux_engine_status
);
584 vg
.engine_status
= k_engine_status_crashed
;
585 vg
.str_const_engine_err
= error
;
586 vg_mutex_unlock( &vg
.mux_engine_status
);
589 * Wait for loader to finish what it was doing, if it was running.
590 * Then we can continue in our nice error screen
592 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
594 vg_semaphore_wait( &vg
.sem_loader
);
599 * todo: draw error loop
603 if( glfwWindowShouldClose( vg
.window
) )
608 glClearColor( 0.15f
+ sinf(glfwGetTime())*0.1f
, 0.0f
, 0.0f
,1.0f
);
609 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
610 glViewport( 0,0, vg_window_x
, vg_window_y
);
614 glfwSwapBuffers( vg
.window
);
617 /* Can now shutdown and EXIT */
628 static void vg_fatal_exit_loop( const char *error
)
630 vg_error( "Fatal error: %s\n", error
);
637 * Graphic cards will check these to force it to use the GPU
639 u32 NvOptimusEnablement
= 0x00000001;
640 int AmdPowerXpressRequestHighPerformance
= 1;