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"
15 #include "../../dep/glfw/glfw3.h"
18 #include "vg_stdint.h"
20 void vg_register_exit( void( *funcptr
)(void), const char *name
);
27 //#include "vg_steamworks.h"
32 #include "vg_gldiag.h"
39 #ifdef VG_CAPTURE_MODE
40 int vg_window_x
= 1920;
41 int vg_window_y
= 1080;
43 int vg_window_x
= 1366;
44 int vg_window_y
= 768;
61 vg_semaphore sem_allow_exec
,
67 vg_mutex mux_engine_status
;
71 k_engine_status_running
,
72 k_engine_status_crashed
75 const char *str_const_engine_err
;
80 GLFWgamepadstate gamepad
;
82 const char *gamepad_name
;
89 enum vg_thread_purpose
91 k_thread_purpose_nothing
,
92 k_thread_purpose_main
,
93 k_thread_purpose_loader
100 static VG_THREAD_LOCAL
struct vg_thread_info vg_thread_info
;
103 //#define VG_SYNC_DEBUG
106 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
108 #define VG_SYNC_LOG(...)
111 static void vg_fatal_exit_loop( const char *error
);
113 static void vg_ensure_engine_running(void)
115 /* Check if the engine is no longer running */
116 vg_mutex_lock( &vg
.mux_engine_status
);
117 if( vg
.engine_status
!= k_engine_status_running
)
119 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
120 vg_mutex_unlock( &vg
.mux_engine_status
);
122 /* Safe to disregard loader thread from this point on, elswhere */
123 if( vg_thread_info
.purpose
== k_thread_purpose_loader
)
125 vg_semaphore_post( &vg
.sem_loader
);
128 VG_SYNC_LOG( "[%d] about to kill\n");
131 vg_mutex_unlock( &vg
.mux_engine_status
);
135 * Sync execution so that the OpenGL context is switched onto this thread.
136 * Anything after this call will be in a valid context.
138 static void vg_acquire_thread_sync(void)
140 /* We dont want to do anything if this is the main thread */
141 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
144 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
146 vg_ensure_engine_running();
148 /* Check if thread already has the context */
149 if( vg_thread_info
.gl_context_level
)
151 vg_thread_info
.gl_context_level
++;
152 VG_SYNC_LOG( "[%d] We already have sync here\n" );
156 vg_mutex_lock( &vg
.mux_context
);
157 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
159 vg_mutex_unlock( &vg
.mux_context
);
161 /* wait until told we can go */
162 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
163 vg_semaphore_wait( &vg
.sem_allow_exec
);
164 glfwMakeContextCurrent( vg
.window
);
166 /* context now valid to work in while we hold up main thread */
167 VG_SYNC_LOG( "[%d] Context acquired.\n" );
168 vg_thread_info
.gl_context_level
++;
172 * Signify that we are done with the OpenGL context in this thread.
173 * Anything after this call will be in an undefined context.
175 static void vg_release_thread_sync(void)
177 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
180 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
182 /* signal that we are done */
183 vg_thread_info
.gl_context_level
--;
185 if( !vg_thread_info
.gl_context_level
)
187 VG_SYNC_LOG( "[%d] Releasing context.\n" );
188 glfwMakeContextCurrent( NULL
);
189 vg_semaphore_post( &vg
.sem_exec_finished
);
193 static void vg_run_synced_content(void)
195 assert( vg_thread_info
.purpose
== k_thread_purpose_main
);
197 vg_mutex_lock( &vg
.mux_context
);
199 if( vg
.exec_context
!= 0 )
201 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg
.exec_context
);
203 /* allow operations to go */
204 vg_thread_info
.gl_context_level
= 0;
205 glfwMakeContextCurrent( NULL
);
206 vg_semaphore_post( &vg
.sem_allow_exec
);
208 /* wait for operations to complete */
209 VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg
.exec_context
);
210 vg_semaphore_wait( &vg
.sem_exec_finished
);
212 /* check if we killed the engine */
213 vg_ensure_engine_running();
215 /* re-engage main thread */
216 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
218 glfwMakeContextCurrent( vg
.window
);
219 vg_thread_info
.gl_context_level
= 1;
222 vg_mutex_unlock( &vg
.mux_context
);
225 static void vg_opengl_sync_init(void)
227 vg_semaphore_init( &vg
.sem_allow_exec
, 0 );
228 vg_semaphore_init( &vg
.sem_exec_finished
, 0 );
229 vg_semaphore_init( &vg
.sem_loader
, 1 );
230 vg_semaphore_init( &vg
.sem_fatal
, 1 );
231 vg_mutex_init( &vg
.mux_context
);
233 vg_set_thread_name( "[vg] Main" );
234 vg_thread_info
.purpose
= k_thread_purpose_main
;
235 vg_thread_info
.gl_context_level
= 1;
238 static void vg_checkgl( const char *src_info
);
239 #define VG_STRINGIT( X ) #X
240 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
242 #include "vg_audio.h"
243 #include "vg_shader.h"
245 #include "vg_input.h"
247 #include "vg_console.h"
248 #include "vg_lines.h"
249 #include "vg_debug.h"
250 #include "vg_loader.h"
253 static void vg_register(void) VG_GAMELOOP
;
254 static void vg_start(void) VG_GAMELOOP
;
255 static void vg_update(int loaded
) VG_GAMELOOP
;
256 static void vg_framebuffer_resize(int w
, int h
) VG_GAMELOOP
;
257 static void vg_render(void) VG_GAMELOOP
;
258 static void vg_ui(void) VG_GAMELOOP
;
260 static void vg_checkgl( const char *src_info
)
265 while( (err
= glGetError()) != GL_NO_ERROR
)
267 vg_error( "(%s) OpenGL Error: #%d\n", src_info
, err
);
272 vg_fatal_exit_loop( "OpenGL Error" );
275 void vg_mouse_callback( GLFWwindow
* ptrW
, double xpos
, double ypos
)
281 void vg_scroll_callback( GLFWwindow
* ptrW
, double xoffset
, double yoffset
)
283 vg_mouse_wheel
[0] += xoffset
;
284 vg_mouse_wheel
[1] += yoffset
;
287 void vg_framebuffer_resize_callback( GLFWwindow
*ptrW
, int w
, int h
)
292 vg_framebuffer_resize(w
,h
);
295 static int vg_bake_shaders(void)
297 vg_acquire_thread_sync();
299 if( !vg_shaders_recompile() )
301 vg_shaders_free(NULL
);
302 vg_release_thread_sync();
307 vg_release_thread_sync();
308 vg_loader_highwater( NULL
, vg_shaders_free
, NULL
);
313 void vg_preload(void);
315 static void vg_load_full(void)
320 vg_loader_highwater( vg_gamepad_init
, NULL
, NULL
);
321 vg_loader_highwater( vg_lines_init
, vg_lines_free
, NULL
);
322 vg_loader_highwater( vg_audio_init
, vg_audio_free
, NULL
);
327 vg_acquire_thread_sync();
329 vg_release_thread_sync();
332 static void vg_enter( int argc
, char *argv
[], const char *window_name
)
338 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR
, 3 );
339 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR
, 3 );
340 glfwWindowHint( GLFW_OPENGL_PROFILE
, GLFW_OPENGL_CORE_PROFILE
);
341 glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT
, GL_TRUE
);
343 glfwWindowHint( GLFW_RESIZABLE
, GLFW_TRUE
);
344 glfwWindowHint( GLFW_DOUBLEBUFFER
, GLFW_TRUE
);
347 glfwWindowHint(GLFW_SAMPLES
,4);
350 GLFWmonitor
*monitor_primary
= glfwGetPrimaryMonitor();
352 const GLFWvidmode
*mode
= glfwGetVideoMode( monitor_primary
);
353 glfwWindowHint( GLFW_RED_BITS
, mode
->redBits
);
354 glfwWindowHint( GLFW_GREEN_BITS
, mode
->greenBits
);
355 glfwWindowHint( GLFW_BLUE_BITS
, mode
->blueBits
);
357 /* This is set like this because of an OS issue */
358 int refresh_rate
= mode
->refreshRate
;
359 if( refresh_rate
< 28 || refresh_rate
>= 144 )
361 glfwWindowHint( GLFW_REFRESH_RATE
, refresh_rate
);
363 if( !(vg
.window
= glfwCreateWindow( vg_window_x
, vg_window_y
,
364 window_name
, NULL
, NULL
)) )
366 vg_error( "GLFW Failed to initialize\n" );
370 glfwMakeContextCurrent( vg
.window
);
371 glfwSwapInterval( 1 );
373 glfwSetWindowSizeLimits( vg
.window
, 800, 600, GLFW_DONT_CARE
,GLFW_DONT_CARE
);
374 glfwSetFramebufferSizeCallback( vg
.window
, vg_framebuffer_resize_callback
);
376 glfwSetCursorPosCallback( vg
.window
, vg_mouse_callback
);
377 glfwSetScrollCallback( vg
.window
, vg_scroll_callback
);
379 glfwSetCharCallback( vg
.window
, console_proc_wchar
);
380 glfwSetKeyCallback( vg
.window
, console_proc_key
);
382 glfwSetInputMode(vg_window
, GLFW_CURSOR
, GLFW_CURSOR_HIDDEN
);
385 if( !gladLoadGLLoader((GLADloadproc
)glfwGetProcAddress
) )
387 vg_error( "Glad Failed to initialize\n" );
392 const unsigned char* glver
= glGetString( GL_VERSION
);
393 vg_success( "Load setup complete, OpenGL version: %s\n", glver
);
394 vg_run_gfx_diagnostics();
396 if( !ui_default_init() )
399 if( !vg_loader_init() )
402 vg_mutex_init( &vg
.mux_engine_status
);
403 vg
.engine_status
= k_engine_status_running
;
405 vg_opengl_sync_init();
412 if( glfwWindowShouldClose( vg
.window
) )
415 v2_copy( (v2f
){ 0.0f
, 0.0f
}, vg_mouse_wheel
);
418 vg_time_last
= vg_time
;
419 vg_time
= glfwGetTime();
420 vg_time_delta
= vg_minf( vg_time
- vg_time_last
, 0.1f
);
424 glClearColor( 0.0f
,sinf(vg_time
*20.0)*0.5f
+0.5f
,0.0f
,1.0f
);
425 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
445 vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
454 ui_begin( &ui_global_ctx
, vg_window_x
, vg_window_y
);
455 ui_set_mouse( &ui_global_ctx
, vg_mouse
[0], vg_mouse
[1],
456 vg_get_button_state( "primary" ) );
458 audio_debug_ui( vg_pv
);
462 ui_resolve( &ui_global_ctx
);
463 ui_draw( &ui_global_ctx
, NULL
);
467 glfwSwapBuffers( vg
.window
);
468 vg_run_synced_content();
471 vg_console_write_persistent();
473 vg_mutex_lock( &vg
.mux_engine_status
);
474 vg
.engine_status
= k_engine_status_none
;
475 vg_mutex_unlock( &vg
.mux_engine_status
);
487 * Immediately transfer away from calling thread into a safe loop, signal for
488 * others to shutdown, then free everything once the user closes the window.
490 static void vg_fatal_exit_loop( const char *error
)
492 vg_error( "Fatal error: %s\n", error
);
493 assert( vg_semaphore_trywait( &vg
.sem_fatal
) );
495 vg_mutex_lock( &vg
.mux_engine_status
);
497 if( vg
.engine_status
== k_engine_status_none
)
499 vg_mutex_unlock( &vg
.mux_engine_status
);
501 /* TODO: Correct shutdown before other systems */
506 vg_mutex_unlock( &vg
.mux_engine_status
);
511 * wait until loader checks in, it will die
518 * wait for main to get to us, it will never be used again
520 * undefined behaviour:
521 * fatal_exit_loop is called in both threads, preventing an appropriate
522 * reaction to the crash. This *should* be made
523 * obvious by the assertion
525 vg_acquire_thread_sync();
527 vg_mutex_lock( &vg
.mux_engine_status
);
528 vg
.engine_status
= k_engine_status_crashed
;
529 vg
.str_const_engine_err
= error
;
530 vg_mutex_unlock( &vg
.mux_engine_status
);
533 * Wait for loader to finish what it was doing, if it was running.
534 * Then we can continue in our nice error screen
536 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
538 vg_semaphore_wait( &vg
.sem_loader
);
543 * todo: draw error loop
547 if( glfwWindowShouldClose( vg
.window
) )
552 vg_time
= glfwGetTime();
553 vg_time_delta
= vg_minf( vg_time
- vg_time_last
, 0.1f
);
555 glClearColor( sinf(vg_time
*20.0)*0.5f
+0.5f
, 0.0f
, 0.0f
,1.0f
);
556 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
558 glfwSwapBuffers( vg
.window
);
561 /* Can now shutdown and EXIT */
573 * Graphic cards will check these to force it to use the GPU
575 u32 NvOptimusEnablement
= 0x00000001;
576 int AmdPowerXpressRequestHighPerformance
= 1;