f3ed6991690edd23e57b12697a910e2b5ebe557d
[vg.git] / src / vg / vg.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 #ifndef VG_HEADER_H
4 #define VG_HEADER_H
5
6 #include "vg_platform.h"
7
8
9 #if defined(VG_SERVER) || defined(VG_TOOLS)
10 #define VG_NON_CLIENT
11 #endif
12
13 #ifndef VG_SERVER
14 #include "../../dep/glad/glad.h"
15
16 #define GLFW_INCLUDE_GLCOREARB
17
18 #ifdef _WIN32
19 #define GLFW_DLL
20 #endif
21
22 #include "../../dep/glfw/glfw3.h"
23 #endif
24
25 #include "vg_stdint.h"
26
27 void vg_register_exit( void( *funcptr )(void), const char *name );
28
29 #include "vg_m.h"
30 #include "vg_io.h"
31 #include "vg_log.h"
32
33 #ifdef VG_STEAM
34 //#include "vg_steamworks.h"
35 #include "vg_steam.h"
36 #endif
37
38 #ifndef VG_NON_CLIENT
39 #include "vg_gldiag.h"
40 #endif
41
42 #ifndef VG_NON_CLIENT
43
44 m4x4f vg_pv;
45
46 int vg_window_x = 0;
47 int vg_window_y = 0;
48 int vg_samples = 0;
49
50 v2f vg_mouse;
51 v2f vg_mouse_wheel;
52 v3f vg_mouse_ws;
53
54 double vg_time,
55 vg_time_last,
56 vg_time_delta;
57
58 struct vg
59 {
60 /* Engine sync */
61 GLFWwindow* window;
62
63 vg_mutex mux_context;
64 vg_semaphore sem_allow_exec,
65 sem_exec_finished,
66 sem_loader,
67 sem_fatal;
68 int exec_context;
69
70 vg_mutex mux_engine_status;
71 enum engine_status
72 {
73 k_engine_status_none,
74 k_engine_status_running,
75 k_engine_status_crashed
76 }
77 engine_status;
78 const char *str_const_engine_err;
79
80 int is_loaded;
81
82 /* Gamepad */
83 GLFWgamepadstate gamepad;
84 int gamepad_ready;
85 const char *gamepad_name;
86 int gamepad_id;
87 }
88 static vg;
89
90 struct vg_thread_info
91 {
92 enum vg_thread_purpose
93 {
94 k_thread_purpose_nothing,
95 k_thread_purpose_main,
96 k_thread_purpose_loader
97 }
98 purpose;
99
100 int gl_context_level;
101 };
102
103 static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
104
105
106 //#define VG_SYNC_DEBUG
107
108 #ifdef VG_SYNC_DEBUG
109 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
110 #else
111 #define VG_SYNC_LOG(...)
112 #endif
113
114 static void vg_fatal_exit_loop( const char *error );
115
116 static void vg_ensure_engine_running(void)
117 {
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 )
121 {
122 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
123 vg_mutex_unlock( &vg.mux_engine_status );
124
125 /* Safe to disregard loader thread from this point on, elswhere */
126 if( vg_thread_info.purpose == k_thread_purpose_loader )
127 {
128 vg_semaphore_post( &vg.sem_loader );
129 }
130
131 VG_SYNC_LOG( "[%d] about to kill\n");
132 vg_thread_exit();
133 }
134 vg_mutex_unlock( &vg.mux_engine_status );
135 }
136
137 /*
138 * Sync execution so that the OpenGL context is switched onto this thread.
139 * Anything after this call will be in a valid context.
140 */
141 static void vg_acquire_thread_sync(void)
142 {
143 /* We dont want to do anything if this is the main thread */
144 if( vg_thread_info.purpose == k_thread_purpose_main )
145 return;
146
147 assert( vg_thread_info.purpose == k_thread_purpose_loader );
148
149 vg_ensure_engine_running();
150
151 /* Check if thread already has the context */
152 if( vg_thread_info.gl_context_level )
153 {
154 vg_thread_info.gl_context_level ++;
155 VG_SYNC_LOG( "[%d] We already have sync here\n" );
156 return;
157 }
158
159 vg_mutex_lock( &vg.mux_context );
160 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
161 vg.exec_context = 1;
162 vg_mutex_unlock( &vg.mux_context );
163
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 );
168
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 ++;
172 }
173
174 /*
175 * Signify that we are done with the OpenGL context in this thread.
176 * Anything after this call will be in an undefined context.
177 */
178 static void vg_release_thread_sync(void)
179 {
180 if( vg_thread_info.purpose == k_thread_purpose_main )
181 return;
182
183 assert( vg_thread_info.purpose == k_thread_purpose_loader );
184
185 /* signal that we are done */
186 vg_thread_info.gl_context_level --;
187
188 if( !vg_thread_info.gl_context_level )
189 {
190 VG_SYNC_LOG( "[%d] Releasing context.\n" );
191 glfwMakeContextCurrent( NULL );
192 vg_semaphore_post( &vg.sem_exec_finished );
193 }
194 }
195
196 static void vg_run_synced_content(void)
197 {
198 assert( vg_thread_info.purpose == k_thread_purpose_main );
199
200 vg_mutex_lock( &vg.mux_context );
201
202 if( vg.exec_context != 0 )
203 {
204 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
205
206 /* allow operations to go */
207 vg_thread_info.gl_context_level = 0;
208 glfwMakeContextCurrent( NULL );
209 vg_semaphore_post( &vg.sem_allow_exec );
210
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 );
214
215 /* check if we killed the engine */
216 vg_ensure_engine_running();
217
218 /* re-engage main thread */
219 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
220 vg.exec_context = 0;
221 glfwMakeContextCurrent( vg.window );
222 vg_thread_info.gl_context_level = 1;
223 }
224
225 vg_mutex_unlock( &vg.mux_context );
226 }
227
228 static void vg_opengl_sync_init(void)
229 {
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 );
235
236 vg_set_thread_name( "[vg] Main" );
237 vg_thread_info.purpose = k_thread_purpose_main;
238 vg_thread_info.gl_context_level = 1;
239 }
240
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__) )
244
245 #include "vg_audio.h"
246 #include "vg_shader.h"
247 #include "vg_tex.h"
248 #include "vg_input.h"
249 #include "vg_ui.h"
250 #include "vg_console.h"
251 #include "vg_lines.h"
252 #include "vg_debug.h"
253 #include "vg_loader.h"
254 #include "vg_opt.h"
255
256 #define VG_GAMELOOP
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;
263
264 static void vg_checkgl( const char *src_info )
265 {
266 int fail = 0;
267
268 GLenum err;
269 while( (err = glGetError()) != GL_NO_ERROR )
270 {
271 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
272 fail = 1;
273 }
274
275 if( fail )
276 vg_fatal_exit_loop( "OpenGL Error" );
277 }
278
279 void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
280 {
281 vg_mouse[0] = xpos;
282 vg_mouse[1] = ypos;
283 }
284
285 void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
286 {
287 vg_mouse_wheel[0] += xoffset;
288 vg_mouse_wheel[1] += yoffset;
289 }
290
291 void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
292 {
293 if( !w || !h )
294 {
295 vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w, h );
296 return;
297 }
298
299 vg_window_x = w;
300 vg_window_y = h;
301
302 vg_framebuffer_resize(w,h);
303 }
304
305 static int vg_bake_shaders(void)
306 {
307 vg_acquire_thread_sync();
308
309 if( !vg_shaders_recompile() )
310 {
311 vg_shaders_free(NULL);
312 vg_release_thread_sync();
313 return 0;
314 }
315 else
316 {
317 vg_release_thread_sync();
318 vg_loader_highwater( NULL, vg_shaders_free, NULL );
319 return 1;
320 }
321 }
322
323 void vg_preload(void);
324 void vg_load(void);
325 static void vg_load_full(void)
326 {
327 vg_preload();
328
329 /* internal */
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 );
333
334 /* client */
335 vg_load();
336
337 vg_acquire_thread_sync();
338 vg.is_loaded = 1;
339 vg_release_thread_sync();
340 }
341
342 static void vg_enter( int argc, char *argv[], const char *window_name )
343 {
344 char *arg;
345 while( vg_argp( argc, argv ) )
346 {
347 if( (arg = vg_opt_arg( 'w' )) )
348 {
349 vg_window_x = atoi( arg );
350 }
351
352 if( (arg = vg_opt_arg( 'h' )) )
353 {
354 vg_window_y = atoi( arg );
355 }
356
357 if( (arg = vg_long_opt_arg( "samples" )) )
358 {
359 vg_samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
360 }
361 }
362
363 vg_log_init();
364 vg_console_init();
365
366 glfwInit();
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 );
372
373 glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE );
374 glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
375
376 glfwWindowHint( GLFW_SAMPLES, vg_samples );
377
378 GLFWmonitor *monitor_primary = glfwGetPrimaryMonitor();
379
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 );
384
385 /* TODO? */
386 glfwWindowHint( GLFW_REFRESH_RATE, 60 );
387
388 if( !vg_window_x )
389 vg_window_x = mode->width;
390
391 if( !vg_window_y )
392 vg_window_y = mode->height;
393
394
395 if( (vg.window = glfwCreateWindow( vg_window_x, vg_window_y,
396 window_name, monitor_primary, NULL)) )
397 {
398 glfwGetFramebufferSize( vg.window, &vg_window_x, &vg_window_y );
399 vg_success( "Window created (%dx%d)\n", vg_window_x, vg_window_y );
400 }
401 else
402 {
403 vg_error( "GLFW Failed to initialize\n" );
404 return;
405 }
406
407 /* We need 3.1.2 for correct VSync on windows */
408 {
409 int vmaj, vmin, vrev;
410 glfwGetVersion( &vmaj, &vmin, &vrev );
411
412 if( vmaj < 3 ||
413 (vmaj == 3 && vmin < 1) ||
414 (vmaj == 3 && vmin == 1 && vrev < 2 ) )
415 {
416 vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
417 vmaj, vmin, vrev );
418
419 glfwTerminate();
420 return;
421 }
422
423 vg_success( "GLFW Version %d.%d.%d\n", vmaj, vmin, vrev );
424 }
425
426 glfwMakeContextCurrent( vg.window );
427 glfwSwapInterval( 1 );
428
429 glfwSetWindowSizeLimits( vg.window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
430 glfwSetFramebufferSizeCallback( vg.window, vg_framebuffer_resize_callback );
431
432 glfwSetCursorPosCallback( vg.window, vg_mouse_callback );
433 glfwSetScrollCallback( vg.window, vg_scroll_callback );
434
435 glfwSetCharCallback( vg.window, console_proc_wchar );
436 glfwSetKeyCallback( vg.window, console_proc_key );
437 #if 0
438 glfwSetInputMode(vg_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
439 #endif
440
441 if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) )
442 {
443 vg_error( "Glad Failed to initialize\n" );
444 glfwTerminate();
445 return;
446 }
447
448 const unsigned char* glver = glGetString( GL_VERSION );
449 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
450 vg_run_gfx_diagnostics();
451
452 if( !ui_default_init() )
453 goto il_exit_ui;
454
455 if( !vg_loader_init() )
456 goto il_exit_loader;
457
458 vg_mutex_init( &vg.mux_engine_status );
459 vg.engine_status = k_engine_status_running;
460
461 vg_opengl_sync_init();
462 vg_loader_start();
463
464 int loaded = 0;
465 int counter =0;
466 while(1)
467 {
468 if( glfwWindowShouldClose( vg.window ) )
469 break;
470
471 v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
472 glfwPollEvents();
473
474 vg_time_last = vg_time;
475 vg_time = glfwGetTime();
476 vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
477
478 if( vg.is_loaded )
479 {
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 );
482
483 if( !loaded )
484 {
485 vg_start();
486 loaded = 1;
487 }
488 }
489 else
490 {
491 vg_loader_render();
492 }
493
494 vg_update_inputs();
495 vg_update( loaded );
496
497 counter ++;
498
499 #if 0
500 if( counter == 30 )
501 vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
502 #endif
503
504 if( loaded )
505 {
506 vg_render();
507
508 /* ui */
509 {
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" ) );
513
514 audio_debug_ui( vg_pv );
515 vg_ui();
516 vg_console_draw();
517
518 ui_resolve( &ui_global_ctx );
519 ui_draw( &ui_global_ctx, NULL );
520 }
521 }
522
523 glfwSwapBuffers( vg.window );
524 vg_run_synced_content();
525 }
526
527 vg_console_write_persistent();
528
529 vg_mutex_lock( &vg.mux_engine_status );
530 vg.engine_status = k_engine_status_none;
531 vg_mutex_unlock( &vg.mux_engine_status );
532
533 vg_loader_free();
534
535 il_exit_loader:
536 ui_default_free();
537
538 il_exit_ui:
539 glfwTerminate();
540 }
541
542 /*
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.
545 */
546 static void vg_fatal_exit_loop( const char *error )
547 {
548 vg_error( "Fatal error: %s\n", error );
549 assert( vg_semaphore_trywait( &vg.sem_fatal ) );
550
551 vg_mutex_lock( &vg.mux_engine_status );
552
553 if( vg.engine_status == k_engine_status_none )
554 {
555 vg_mutex_unlock( &vg.mux_engine_status );
556
557 /* TODO: Correct shutdown before other systems */
558 exit(0);
559 }
560 else
561 {
562 vg_mutex_unlock( &vg.mux_engine_status );
563
564 /*
565 * if main
566 * if loader running
567 * wait until loader checks in, it will die
568 * else
569 * pass immediately
570 * else
571 * if have context
572 * pass immediately
573 * else
574 * wait for main to get to us, it will never be used again
575 *
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
580 */
581 vg_acquire_thread_sync();
582
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 );
587
588 /*
589 * Wait for loader to finish what it was doing, if it was running.
590 * Then we can continue in our nice error screen
591 */
592 if( vg_thread_info.purpose == k_thread_purpose_main )
593 {
594 vg_semaphore_wait( &vg.sem_loader );
595 }
596 vg_audio_free(NULL);
597
598 /*
599 * todo: draw error loop
600 */
601 while(1)
602 {
603 if( glfwWindowShouldClose( vg.window ) )
604 break;
605
606 glfwPollEvents();
607
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 );
611
612 vg_render_log();
613
614 glfwSwapBuffers( vg.window );
615 }
616
617 /* Can now shutdown and EXIT */
618 vg_loader_free();
619 ui_default_free();
620
621 glfwTerminate();
622 exit(0);
623 }
624 }
625
626 #else
627
628 static void vg_fatal_exit_loop( const char *error )
629 {
630 vg_error( "Fatal error: %s\n", error );
631 exit(0);
632 }
633
634 #endif
635
636 /*
637 * Graphic cards will check these to force it to use the GPU
638 */
639 u32 NvOptimusEnablement = 0x00000001;
640 int AmdPowerXpressRequestHighPerformance = 1;
641
642 #endif