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"
50 vg_semaphore sem_allow_exec
,
56 vg_mutex mux_engine_status
;
60 k_engine_status_running
,
61 k_engine_status_crashed
64 const char *str_const_engine_err
;
67 /* Window information */
87 k_engine_stage_update
,
88 k_engine_stage_update_fixed
,
89 k_engine_stage_rendering
,
98 GLFWgamepadstate gamepad
;
100 const char *gamepad_name
;
105 struct vg_thread_info
107 enum vg_thread_purpose
109 k_thread_purpose_nothing
,
110 k_thread_purpose_main
,
111 k_thread_purpose_loader
115 int gl_context_level
;
118 static VG_THREAD_LOCAL
struct vg_thread_info vg_thread_info
;
121 //#define VG_SYNC_DEBUG
124 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
126 #define VG_SYNC_LOG(...)
129 static void vg_fatal_exit_loop( const char *error
);
131 static void vg_ensure_engine_running(void)
133 /* Check if the engine is no longer running */
134 vg_mutex_lock( &vg
.mux_engine_status
);
135 if( vg
.engine_status
!= k_engine_status_running
)
137 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
138 vg_mutex_unlock( &vg
.mux_engine_status
);
140 /* Safe to disregard loader thread from this point on, elswhere */
141 if( vg_thread_info
.purpose
== k_thread_purpose_loader
)
143 vg_semaphore_post( &vg
.sem_loader
);
146 VG_SYNC_LOG( "[%d] about to kill\n");
149 vg_mutex_unlock( &vg
.mux_engine_status
);
153 * Sync execution so that the OpenGL context is switched onto this thread.
154 * Anything after this call will be in a valid context.
156 static void vg_acquire_thread_sync(void)
158 /* We dont want to do anything if this is the main thread */
159 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
162 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
164 vg_ensure_engine_running();
166 /* Check if thread already has the context */
167 if( vg_thread_info
.gl_context_level
)
169 vg_thread_info
.gl_context_level
++;
170 VG_SYNC_LOG( "[%d] We already have sync here\n" );
174 vg_mutex_lock( &vg
.mux_context
);
175 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
177 vg_mutex_unlock( &vg
.mux_context
);
179 /* wait until told we can go */
180 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
181 vg_semaphore_wait( &vg
.sem_allow_exec
);
182 glfwMakeContextCurrent( vg
.window
);
184 /* context now valid to work in while we hold up main thread */
185 VG_SYNC_LOG( "[%d] Context acquired.\n" );
186 vg_thread_info
.gl_context_level
++;
190 * Signify that we are done with the OpenGL context in this thread.
191 * Anything after this call will be in an undefined context.
193 static void vg_release_thread_sync(void)
195 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
198 assert( vg_thread_info
.purpose
== k_thread_purpose_loader
);
200 /* signal that we are done */
201 vg_thread_info
.gl_context_level
--;
203 if( !vg_thread_info
.gl_context_level
)
205 VG_SYNC_LOG( "[%d] Releasing context.\n" );
206 glfwMakeContextCurrent( NULL
);
207 vg_semaphore_post( &vg
.sem_exec_finished
);
211 static void vg_run_synced_content(void)
213 assert( vg_thread_info
.purpose
== k_thread_purpose_main
);
215 vg_mutex_lock( &vg
.mux_context
);
217 if( vg
.exec_context
!= 0 )
219 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg
.exec_context
);
221 /* allow operations to go */
222 vg_thread_info
.gl_context_level
= 0;
223 glfwMakeContextCurrent( NULL
);
224 vg_semaphore_post( &vg
.sem_allow_exec
);
226 /* wait for operations to complete */
227 VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg
.exec_context
);
228 vg_semaphore_wait( &vg
.sem_exec_finished
);
230 /* check if we killed the engine */
231 vg_ensure_engine_running();
233 /* re-engage main thread */
234 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
236 glfwMakeContextCurrent( vg
.window
);
237 vg_thread_info
.gl_context_level
= 1;
240 vg_mutex_unlock( &vg
.mux_context
);
243 static void vg_opengl_sync_init(void)
245 vg_semaphore_init( &vg
.sem_allow_exec
, 0 );
246 vg_semaphore_init( &vg
.sem_exec_finished
, 0 );
247 vg_semaphore_init( &vg
.sem_loader
, 1 );
248 vg_semaphore_init( &vg
.sem_fatal
, 1 );
249 vg_mutex_init( &vg
.mux_context
);
251 vg_set_thread_name( "[vg] Main" );
252 vg_thread_info
.purpose
= k_thread_purpose_main
;
253 vg_thread_info
.gl_context_level
= 1;
256 static void vg_checkgl( const char *src_info
);
257 #define VG_STRINGIT( X ) #X
258 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
260 #include "vg_console.h"
261 #include "vg_profiler.h"
262 #include "vg_audio.h"
263 #include "vg_shader.h"
265 #include "vg_input.h"
267 #include "vg_lines.h"
268 #include "vg_debug.h"
269 #include "vg_loader.h"
273 static struct vg_profile vg_prof_update
= {.name
="update()"},
274 vg_prof_render
= {.name
="render()"};
277 static void vg_register(void) VG_GAMELOOP
;
278 static void vg_start(void) VG_GAMELOOP
;
280 static void vg_update(int loaded
) VG_GAMELOOP
;
281 static void vg_update_fixed(int loaded
) VG_GAMELOOP
;
282 static void vg_update_post(int loaded
) VG_GAMELOOP
;
284 static void vg_framebuffer_resize(int w
, int h
) VG_GAMELOOP
;
285 static void vg_render(void) VG_GAMELOOP
;
286 static void vg_ui(void) VG_GAMELOOP
;
288 static void vg_checkgl( const char *src_info
)
293 while( (err
= glGetError()) != GL_NO_ERROR
)
295 vg_error( "(%s) OpenGL Error: #%d\n", src_info
, err
);
300 vg_fatal_exit_loop( "OpenGL Error" );
303 void vg_mouse_callback( GLFWwindow
* ptrW
, double xpos
, double ypos
)
309 void vg_scroll_callback( GLFWwindow
* ptrW
, double xoffset
, double yoffset
)
311 vg
.mouse_wheel
[0] += xoffset
;
312 vg
.mouse_wheel
[1] += yoffset
;
315 void vg_framebuffer_resize_callback( GLFWwindow
*ptrW
, int w
, int h
)
319 vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w
, h
);
326 vg_framebuffer_resize(w
,h
);
329 static int vg_bake_shaders(void)
331 vg_acquire_thread_sync();
333 if( !vg_shaders_recompile() )
335 vg_shaders_free(NULL
);
336 vg_release_thread_sync();
341 vg_release_thread_sync();
342 vg_loader_highwater( NULL
, vg_shaders_free
, NULL
);
347 void vg_preload(void);
349 static void vg_load_full(void)
354 vg_loader_highwater( vg_gamepad_init
, NULL
, NULL
);
355 vg_loader_highwater( vg_lines_init
, vg_lines_free
, NULL
);
356 vg_loader_highwater( vg_audio_init
, vg_audio_free
, NULL
);
357 vg_loader_highwater( vg_profiler_init
, NULL
, NULL
);
362 vg_acquire_thread_sync();
364 vg_release_thread_sync();
367 static void vg_enter( int argc
, char *argv
[], const char *window_name
)
370 while( vg_argp( argc
, argv
) )
372 if( (arg
= vg_opt_arg( 'w' )) )
374 vg
.window_x
= atoi( arg
);
377 if( (arg
= vg_opt_arg( 'h' )) )
379 vg
.window_y
= atoi( arg
);
382 if( (arg
= vg_long_opt_arg( "samples" )) )
384 vg
.samples
= VG_MAX( 0, VG_MIN( 8, atoi( arg
) ) );
392 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR
, 3 );
393 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR
, 3 );
394 glfwWindowHint( GLFW_OPENGL_PROFILE
, GLFW_OPENGL_CORE_PROFILE
);
395 glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT
, GL_TRUE
);
396 glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR
, GLFW_RELEASE_BEHAVIOR_FLUSH
);
398 glfwWindowHint( GLFW_RESIZABLE
, GLFW_FALSE
);
399 glfwWindowHint( GLFW_DOUBLEBUFFER
, GLFW_TRUE
);
401 glfwWindowHint( GLFW_SAMPLES
, vg
.samples
);
403 GLFWmonitor
*monitor_primary
= glfwGetPrimaryMonitor();
405 const GLFWvidmode
*mode
= glfwGetVideoMode( monitor_primary
);
406 glfwWindowHint( GLFW_RED_BITS
, mode
->redBits
);
407 glfwWindowHint( GLFW_GREEN_BITS
, mode
->greenBits
);
408 glfwWindowHint( GLFW_BLUE_BITS
, mode
->blueBits
);
410 glfwWindowHint( GLFW_REFRESH_RATE
, mode
->refreshRate
);
413 vg
.window_x
= mode
->width
;
416 vg
.window_y
= mode
->height
;
418 vg
.refresh_rate
= mode
->refreshRate
;
420 if( (vg
.window
= glfwCreateWindow( vg
.window_x
, vg
.window_y
,
421 window_name
, monitor_primary
, NULL
)) )
423 glfwGetFramebufferSize( vg
.window
, &vg
.window_x
, &vg
.window_y
);
424 vg_success( "Window created (%dx%d)\n", vg
.window_x
, vg
.window_y
);
428 vg_error( "GLFW Failed to initialize\n" );
432 /* We need 3.1.2 for correct VSync on windows */
434 int vmaj
, vmin
, vrev
;
435 glfwGetVersion( &vmaj
, &vmin
, &vrev
);
438 (vmaj
== 3 && vmin
< 1) ||
439 (vmaj
== 3 && vmin
== 1 && vrev
< 2 ) )
441 vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
448 vg_success( "GLFW Version %d.%d.%d\n", vmaj
, vmin
, vrev
);
451 glfwMakeContextCurrent( vg
.window
);
452 glfwSwapInterval( 1 );
454 glfwSetWindowSizeLimits( vg
.window
, 800, 600, GLFW_DONT_CARE
,GLFW_DONT_CARE
);
455 glfwSetFramebufferSizeCallback( vg
.window
, vg_framebuffer_resize_callback
);
457 glfwSetCursorPosCallback( vg
.window
, vg_mouse_callback
);
458 glfwSetScrollCallback( vg
.window
, vg_scroll_callback
);
460 glfwSetCharCallback( vg
.window
, console_proc_wchar
);
461 glfwSetKeyCallback( vg
.window
, console_proc_key
);
463 glfwSetInputMode(vg_window
, GLFW_CURSOR
, GLFW_CURSOR_HIDDEN
);
466 if( !gladLoadGLLoader((GLADloadproc
)glfwGetProcAddress
) )
468 vg_error( "Glad Failed to initialize\n" );
473 const unsigned char* glver
= glGetString( GL_VERSION
);
474 vg_success( "Load setup complete, OpenGL version: %s\n", glver
);
475 vg_run_gfx_diagnostics();
477 if( !ui_default_init() )
480 if( !vg_loader_init() )
483 vg_mutex_init( &vg
.mux_engine_status
);
484 vg
.engine_status
= k_engine_status_running
;
486 vg_opengl_sync_init();
489 vg
.accumulator
= 0.75f
* (1.0f
/60.0f
);
494 if( glfwWindowShouldClose( vg
.window
) )
497 v2_copy( (v2f
){ 0.0f
, 0.0f
}, vg
.mouse_wheel
);
500 vg
.time_last
= vg
.time
;
501 vg
.time
= glfwGetTime();
502 vg
.time_delta
= vg
.time
-vg
.time_last
;
506 glClearColor( 0.0f
,sinf(vg
.time
*20.0)*0.5f
+0.5f
,0.0f
,1.0f
);
507 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
522 * -------------------------------------------------------
524 vg_profile_begin( &vg_prof_update
);
527 vg
.engine_stage
= k_engine_stage_update
;
530 /* Fixed update loop */
531 vg
.engine_stage
= k_engine_stage_update_fixed
;
532 vg
.accumulator
+= vg
.time_delta
;
534 vg
.fixed_iterations
= 0;
535 while( vg
.accumulator
>= (VG_TIMESTEP_FIXED
-0.00125) )
537 vg_update_fixed( loaded
);
539 vg
.accumulator
-= VG_TIMESTEP_FIXED
;
540 vg
.accumulator
= VG_MAX( 0.0, vg
.accumulator
);
542 vg
.fixed_iterations
++;
543 if( vg
.fixed_iterations
== 8 )
551 * ---------------------------------------------
553 vg
.engine_stage
= k_engine_stage_update
;
554 vg_update_post( loaded
);
555 vg_profile_end( &vg_prof_update
);
557 vg_profile_begin( &vg_prof_render
);
562 vg
.engine_stage
= k_engine_stage_rendering
;
566 vg
.engine_stage
= k_engine_stage_ui
;
568 ui_begin( &ui_global_ctx
, vg
.window_x
, vg
.window_y
);
569 ui_set_mouse( &ui_global_ctx
, vg
.mouse
[0], vg
.mouse
[1],
570 vg_get_button_state( "primary" ) );
573 (struct vg_profile
*[]){&vg_prof_update
,&vg_prof_render
}, 2,
574 (1.0f
/(float)vg
.refresh_rate
)*1000.0f
,
575 (ui_rect
){ 4, 4, 250, 0 }, 0
585 "refresh: %.1f (%.1fms)\n"
587 "iterations: %d (acc: %.3fms%%)\n",
588 vg
.window_x
, vg
.window_y
,
589 vg
.refresh_rate
, (1.0f
/vg
.refresh_rate
)*1000.0f
,
592 (vg
.accumulator
/VG_TIMESTEP_FIXED
)*100.0f
);
594 ui_text( &ui_global_ctx
, (ui_rect
){258, 4+24+12,0,0},perf
, 1,0);
597 audio_debug_ui( vg
.pv
);
601 ui_resolve( &ui_global_ctx
);
602 ui_draw( &ui_global_ctx
, NULL
);
606 vg_profile_end( &vg_prof_render
);
608 glfwSwapBuffers( vg
.window
);
609 vg_run_synced_content();
612 vg_console_write_persistent();
614 vg_mutex_lock( &vg
.mux_engine_status
);
615 vg
.engine_status
= k_engine_status_none
;
616 vg_mutex_unlock( &vg
.mux_engine_status
);
628 * Immediately transfer away from calling thread into a safe loop, signal for
629 * others to shutdown, then free everything once the user closes the window.
631 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, only
632 * when calling the program from outside its normal directory.
634 * A workaround is placed to skip the error loop on windows builds
636 static void vg_fatal_exit_loop( const char *error
)
638 vg_error( "Fatal error: %s\n", error
);
639 assert( vg_semaphore_trywait( &vg
.sem_fatal
) );
641 vg_mutex_lock( &vg
.mux_engine_status
);
643 if( vg
.engine_status
== k_engine_status_none
)
645 vg_mutex_unlock( &vg
.mux_engine_status
);
647 /* TODO: Correct shutdown before other systems */
652 vg_mutex_unlock( &vg
.mux_engine_status
);
657 * wait until loader checks in, it will die
664 * wait for main to get to us, it will never be used again
666 * undefined behaviour:
667 * fatal_exit_loop is called in both threads, preventing an appropriate
668 * reaction to the crash. This *should* be made
669 * obvious by the assertion
671 vg_acquire_thread_sync();
673 vg_mutex_lock( &vg
.mux_engine_status
);
674 vg
.engine_status
= k_engine_status_crashed
;
675 vg
.str_const_engine_err
= error
;
676 vg_mutex_unlock( &vg
.mux_engine_status
);
679 * Wait for loader to finish what it was doing, if it was running.
680 * Then we can continue in our nice error screen
682 if( vg_thread_info
.purpose
== k_thread_purpose_main
)
684 vg_semaphore_wait( &vg
.sem_loader
);
691 if( glfwWindowShouldClose( vg
.window
) )
694 if( glfwGetKey( vg
.window
, GLFW_KEY_ESCAPE
) )
699 glBindFramebuffer( GL_FRAMEBUFFER
, 0 );
701 glDisable(GL_DEPTH_TEST
);
702 glBlendFunc(GL_ONE_MINUS_DST_ALPHA
, GL_DST_ALPHA
);
703 glBlendEquation(GL_FUNC_ADD
);
705 glClearColor( 0.15f
+ sinf(glfwGetTime())*0.1f
, 0.0f
, 0.0f
,1.0f
);
706 glClear( GL_COLOR_BUFFER_BIT
|GL_DEPTH_BUFFER_BIT
);
707 glViewport( 0,0, vg
.window_x
, vg
.window_y
);
711 glfwSwapBuffers( vg
.window
);
715 /* Can now shutdown and EXIT */
726 static void vg_fatal_exit_loop( const char *error
)
728 vg_error( "Fatal error: %s\n", error
);
735 * Graphic cards will check these to force it to use the GPU
737 u32 NvOptimusEnablement
= 0x00000001;
738 int AmdPowerXpressRequestHighPerformance
= 1;