e125f26a3c4111c3fa6c10c23f4d15ee2a5bee71
[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 struct vg
45 {
46 /* Engine sync */
47 GLFWwindow* window;
48
49 vg_mutex mux_context;
50 vg_semaphore sem_allow_exec,
51 sem_exec_finished,
52 sem_loader,
53 sem_fatal;
54 int exec_context;
55
56 vg_mutex mux_engine_status;
57 enum engine_status
58 {
59 k_engine_status_none,
60 k_engine_status_running,
61 k_engine_status_crashed
62 }
63 engine_status;
64 const char *str_const_engine_err;
65 int is_loaded;
66
67 /* Window information */
68 int window_x,
69 window_y,
70 samples;
71 float refresh_rate;
72
73 v2f mouse,
74 mouse_wheel;
75
76 /* Runtime */
77 double time,
78 time_last,
79 time_delta,
80 accumulator;
81
82 int fixed_iterations;
83
84 enum engine_stage
85 {
86 k_engine_stage_none,
87 k_engine_stage_update,
88 k_engine_stage_update_fixed,
89 k_engine_stage_rendering,
90 k_engine_stage_ui
91 }
92 engine_stage;
93
94 /* graphics */
95 m4x4f pv;
96
97 /* Gamepad */
98 GLFWgamepadstate gamepad;
99 int gamepad_ready;
100 const char *gamepad_name;
101 int gamepad_id;
102 }
103 static vg;
104
105 struct vg_thread_info
106 {
107 enum vg_thread_purpose
108 {
109 k_thread_purpose_nothing,
110 k_thread_purpose_main,
111 k_thread_purpose_loader
112 }
113 purpose;
114
115 int gl_context_level;
116 };
117
118 static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
119
120
121 //#define VG_SYNC_DEBUG
122
123 #ifdef VG_SYNC_DEBUG
124 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
125 #else
126 #define VG_SYNC_LOG(...)
127 #endif
128
129 static void vg_fatal_exit_loop( const char *error );
130
131 static void vg_ensure_engine_running(void)
132 {
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 )
136 {
137 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
138 vg_mutex_unlock( &vg.mux_engine_status );
139
140 /* Safe to disregard loader thread from this point on, elswhere */
141 if( vg_thread_info.purpose == k_thread_purpose_loader )
142 {
143 vg_semaphore_post( &vg.sem_loader );
144 }
145
146 VG_SYNC_LOG( "[%d] about to kill\n");
147 vg_thread_exit();
148 }
149 vg_mutex_unlock( &vg.mux_engine_status );
150 }
151
152 /*
153 * Sync execution so that the OpenGL context is switched onto this thread.
154 * Anything after this call will be in a valid context.
155 */
156 static void vg_acquire_thread_sync(void)
157 {
158 /* We dont want to do anything if this is the main thread */
159 if( vg_thread_info.purpose == k_thread_purpose_main )
160 return;
161
162 assert( vg_thread_info.purpose == k_thread_purpose_loader );
163
164 vg_ensure_engine_running();
165
166 /* Check if thread already has the context */
167 if( vg_thread_info.gl_context_level )
168 {
169 vg_thread_info.gl_context_level ++;
170 VG_SYNC_LOG( "[%d] We already have sync here\n" );
171 return;
172 }
173
174 vg_mutex_lock( &vg.mux_context );
175 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
176 vg.exec_context = 1;
177 vg_mutex_unlock( &vg.mux_context );
178
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 );
183
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 ++;
187 }
188
189 /*
190 * Signify that we are done with the OpenGL context in this thread.
191 * Anything after this call will be in an undefined context.
192 */
193 static void vg_release_thread_sync(void)
194 {
195 if( vg_thread_info.purpose == k_thread_purpose_main )
196 return;
197
198 assert( vg_thread_info.purpose == k_thread_purpose_loader );
199
200 /* signal that we are done */
201 vg_thread_info.gl_context_level --;
202
203 if( !vg_thread_info.gl_context_level )
204 {
205 VG_SYNC_LOG( "[%d] Releasing context.\n" );
206 glfwMakeContextCurrent( NULL );
207 vg_semaphore_post( &vg.sem_exec_finished );
208 }
209 }
210
211 static void vg_run_synced_content(void)
212 {
213 assert( vg_thread_info.purpose == k_thread_purpose_main );
214
215 vg_mutex_lock( &vg.mux_context );
216
217 if( vg.exec_context != 0 )
218 {
219 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
220
221 /* allow operations to go */
222 vg_thread_info.gl_context_level = 0;
223 glfwMakeContextCurrent( NULL );
224 vg_semaphore_post( &vg.sem_allow_exec );
225
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 );
229
230 /* check if we killed the engine */
231 vg_ensure_engine_running();
232
233 /* re-engage main thread */
234 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
235 vg.exec_context = 0;
236 glfwMakeContextCurrent( vg.window );
237 vg_thread_info.gl_context_level = 1;
238 }
239
240 vg_mutex_unlock( &vg.mux_context );
241 }
242
243 static void vg_opengl_sync_init(void)
244 {
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 );
250
251 vg_set_thread_name( "[vg] Main" );
252 vg_thread_info.purpose = k_thread_purpose_main;
253 vg_thread_info.gl_context_level = 1;
254 }
255
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__) )
259
260 #include "vg_console.h"
261 #include "vg_profiler.h"
262 #include "vg_audio.h"
263 #include "vg_shader.h"
264 #include "vg_tex.h"
265 #include "vg_input.h"
266 #include "vg_ui.h"
267 #include "vg_lines.h"
268 #include "vg_debug.h"
269 #include "vg_loader.h"
270 #include "vg_opt.h"
271
272 /* Diagnostic */
273 static struct vg_profile vg_prof_update = {.name="update()"},
274 vg_prof_render = {.name="render()"};
275
276 #define VG_GAMELOOP
277 static void vg_register(void) VG_GAMELOOP;
278 static void vg_start(void) VG_GAMELOOP;
279
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;
283
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;
287
288 static void vg_checkgl( const char *src_info )
289 {
290 int fail = 0;
291
292 GLenum err;
293 while( (err = glGetError()) != GL_NO_ERROR )
294 {
295 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
296 fail = 1;
297 }
298
299 if( fail )
300 vg_fatal_exit_loop( "OpenGL Error" );
301 }
302
303 void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
304 {
305 vg.mouse[0] = xpos;
306 vg.mouse[1] = ypos;
307 }
308
309 void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
310 {
311 vg.mouse_wheel[0] += xoffset;
312 vg.mouse_wheel[1] += yoffset;
313 }
314
315 void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
316 {
317 if( !w || !h )
318 {
319 vg_warn( "Got a invalid framebuffer size: %dx%d... ignoring\n", w, h );
320 return;
321 }
322
323 vg.window_x = w;
324 vg.window_y = h;
325
326 vg_framebuffer_resize(w,h);
327 }
328
329 static int vg_bake_shaders(void)
330 {
331 vg_acquire_thread_sync();
332
333 vg_function_push( (struct vg_cmd)
334 {
335 .name = "shaders",
336 .function = vg_shaders_live_recompile
337 });
338
339 if( !vg_shaders_recompile() )
340 {
341 vg_shaders_free(NULL);
342 vg_release_thread_sync();
343 return 0;
344 }
345 else
346 {
347 vg_release_thread_sync();
348 vg_loader_highwater( NULL, vg_shaders_free, NULL );
349 return 1;
350 }
351 }
352
353 void vg_preload(void);
354 void vg_load(void);
355 static void vg_load_full(void)
356 {
357 vg_preload();
358
359 /* internal */
360 vg_loader_highwater( vg_gamepad_init, NULL, NULL );
361 vg_loader_highwater( vg_lines_init, vg_lines_free, NULL );
362 vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
363 vg_loader_highwater( vg_profiler_init, NULL, NULL );
364
365 /* client */
366 vg_load();
367
368 vg_acquire_thread_sync();
369 vg.is_loaded = 1;
370 vg_release_thread_sync();
371 }
372
373 static void vg_enter( int argc, char *argv[], const char *window_name )
374 {
375 char *arg;
376 while( vg_argp( argc, argv ) )
377 {
378 if( (arg = vg_opt_arg( 'w' )) )
379 {
380 vg.window_x = atoi( arg );
381 }
382
383 if( (arg = vg_opt_arg( 'h' )) )
384 {
385 vg.window_y = atoi( arg );
386 }
387
388 if( (arg = vg_long_opt_arg( "samples" )) )
389 {
390 vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
391 }
392 }
393
394 vg_log_init();
395 vg_console_init();
396
397 glfwInit();
398 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
399 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
400 glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE );
401 glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE );
402 glfwWindowHint( GLFW_CONTEXT_RELEASE_BEHAVIOR, GLFW_RELEASE_BEHAVIOR_FLUSH );
403
404 glfwWindowHint( GLFW_RESIZABLE, GLFW_FALSE );
405 glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
406
407 glfwWindowHint( GLFW_SAMPLES, vg.samples );
408
409 GLFWmonitor *monitor_primary = glfwGetPrimaryMonitor();
410
411 const GLFWvidmode *mode = glfwGetVideoMode( monitor_primary );
412 glfwWindowHint( GLFW_RED_BITS, mode->redBits );
413 glfwWindowHint( GLFW_GREEN_BITS, mode->greenBits );
414 glfwWindowHint( GLFW_BLUE_BITS, mode->blueBits );
415
416 glfwWindowHint( GLFW_REFRESH_RATE, mode->refreshRate );
417
418 if( !vg.window_x )
419 vg.window_x = mode->width;
420
421 if( !vg.window_y )
422 vg.window_y = mode->height;
423
424 vg.refresh_rate = mode->refreshRate;
425
426 if( (vg.window = glfwCreateWindow( vg.window_x, vg.window_y,
427 window_name, monitor_primary, NULL)) )
428 {
429 glfwGetFramebufferSize( vg.window, &vg.window_x, &vg.window_y );
430 vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y );
431 }
432 else
433 {
434 vg_error( "GLFW Failed to initialize\n" );
435 return;
436 }
437
438 /* We need 3.1.2 for correct VSync on windows */
439 {
440 int vmaj, vmin, vrev;
441 glfwGetVersion( &vmaj, &vmin, &vrev );
442
443 if( vmaj < 3 ||
444 (vmaj == 3 && vmin < 1) ||
445 (vmaj == 3 && vmin == 1 && vrev < 2 ) )
446 {
447 vg_error( "GLFW out of date (%d.%d.%d); (3.1.2 is required)\n",
448 vmaj, vmin, vrev );
449
450 glfwTerminate();
451 return;
452 }
453
454 vg_success( "GLFW Version %d.%d.%d\n", vmaj, vmin, vrev );
455 }
456
457 glfwMakeContextCurrent( vg.window );
458 glfwSwapInterval( 1 );
459
460 glfwSetWindowSizeLimits( vg.window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
461 glfwSetFramebufferSizeCallback( vg.window, vg_framebuffer_resize_callback );
462
463 glfwSetCursorPosCallback( vg.window, vg_mouse_callback );
464 glfwSetScrollCallback( vg.window, vg_scroll_callback );
465
466 glfwSetCharCallback( vg.window, console_proc_wchar );
467 glfwSetKeyCallback( vg.window, console_proc_key );
468 #if 0
469 glfwSetInputMode(vg_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
470 #endif
471
472 if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) )
473 {
474 vg_error( "Glad Failed to initialize\n" );
475 glfwTerminate();
476 return;
477 }
478
479 const unsigned char* glver = glGetString( GL_VERSION );
480 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
481 vg_run_gfx_diagnostics();
482
483 if( !ui_default_init() )
484 goto il_exit_ui;
485
486 if( !vg_loader_init() )
487 goto il_exit_loader;
488
489 vg_mutex_init( &vg.mux_engine_status );
490 vg.engine_status = k_engine_status_running;
491
492 vg_opengl_sync_init();
493 vg_loader_start();
494
495 vg.accumulator = 0.75f * (1.0f/60.0f);
496
497 int loaded = 0;
498 while(1)
499 {
500 if( glfwWindowShouldClose( vg.window ) )
501 break;
502
503 v2_copy( (v2f){ 0.0f, 0.0f }, vg.mouse_wheel );
504 glfwPollEvents();
505
506 vg.time_last = vg.time;
507 vg.time = glfwGetTime();
508 vg.time_delta = vg.time-vg.time_last;
509
510 if( vg.is_loaded )
511 {
512 glClearColor( 0.0f,sinf(vg.time*20.0)*0.5f+0.5f,0.0f,1.0f );
513 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
514
515 if( !loaded )
516 {
517 vg_start();
518 loaded = 1;
519 }
520 }
521 else
522 {
523 vg_loader_render();
524 }
525
526 /*
527 * Game logic
528 * -------------------------------------------------------
529 */
530 vg_profile_begin( &vg_prof_update );
531 vg_update_inputs();
532
533 vg.engine_stage = k_engine_stage_update;
534 vg_update( loaded );
535
536 /* Fixed update loop */
537 vg.engine_stage = k_engine_stage_update_fixed;
538 vg.accumulator += vg.time_delta;
539
540 vg.fixed_iterations = 0;
541 while( vg.accumulator >= (VG_TIMESTEP_FIXED-0.00125) )
542 {
543 vg_update_fixed( loaded );
544
545 vg.accumulator -= VG_TIMESTEP_FIXED;
546 vg.accumulator = VG_MAX( 0.0, vg.accumulator );
547
548 vg.fixed_iterations ++;
549 if( vg.fixed_iterations == 8 )
550 {
551 break;
552 }
553 }
554
555 /*
556 * Rendering
557 * ---------------------------------------------
558 */
559 vg.engine_stage = k_engine_stage_update;
560 vg_update_post( loaded );
561 vg_profile_end( &vg_prof_update );
562
563 vg_profile_begin( &vg_prof_render );
564
565 if( loaded )
566 {
567 /* render */
568 vg.engine_stage = k_engine_stage_rendering;
569 vg_render();
570
571 /* ui */
572 vg.engine_stage = k_engine_stage_ui;
573 {
574 ui_begin( &ui_global_ctx, vg.window_x, vg.window_y );
575 ui_set_mouse( &ui_global_ctx, vg.mouse[0], vg.mouse[1],
576 vg_get_button_state( "primary" ) );
577
578 vg_profile_drawn(
579 (struct vg_profile *[]){&vg_prof_update,&vg_prof_render}, 2,
580 (1.0f/(float)vg.refresh_rate)*1000.0f,
581 (ui_rect){ 4, 4, 250, 0 }, 0
582 );
583
584 if( vg_profiler )
585 {
586
587 char perf[128];
588
589 snprintf( perf, 127,
590 "x: %d y: %d\n"
591 "refresh: %.1f (%.1fms)\n"
592 "samples: %d\n"
593 "iterations: %d (acc: %.3fms%%)\n",
594 vg.window_x, vg.window_y,
595 vg.refresh_rate, (1.0f/vg.refresh_rate)*1000.0f,
596 vg.samples,
597 vg.fixed_iterations,
598 (vg.accumulator/VG_TIMESTEP_FIXED)*100.0f );
599
600 ui_text( &ui_global_ctx, (ui_rect){258, 4+24+12,0,0},perf, 1,0);
601 }
602
603 audio_debug_ui( vg.pv );
604 vg_ui();
605 vg_console_draw();
606
607 ui_resolve( &ui_global_ctx );
608 ui_draw( &ui_global_ctx, NULL );
609 }
610 }
611
612 vg_profile_end( &vg_prof_render );
613
614 glfwSwapBuffers( vg.window );
615 vg_run_synced_content();
616 }
617
618 vg_console_write_persistent();
619
620 vg_mutex_lock( &vg.mux_engine_status );
621 vg.engine_status = k_engine_status_none;
622 vg_mutex_unlock( &vg.mux_engine_status );
623
624 vg_loader_free();
625
626 il_exit_loader:
627 ui_default_free();
628
629 il_exit_ui:
630 glfwTerminate();
631 }
632
633 /*
634 * Immediately transfer away from calling thread into a safe loop, signal for
635 * others to shutdown, then free everything once the user closes the window.
636 *
637 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, only
638 * when calling the program from outside its normal directory.
639 *
640 * A workaround is placed to skip the error loop on windows builds
641 */
642 static void vg_fatal_exit_loop( const char *error )
643 {
644 vg_error( "Fatal error: %s\n", error );
645 assert( vg_semaphore_trywait( &vg.sem_fatal ) );
646
647 vg_mutex_lock( &vg.mux_engine_status );
648
649 if( vg.engine_status == k_engine_status_none )
650 {
651 vg_mutex_unlock( &vg.mux_engine_status );
652
653 /* TODO: Correct shutdown before other systems */
654 exit(0);
655 }
656 else
657 {
658 vg_mutex_unlock( &vg.mux_engine_status );
659
660 /*
661 * if main
662 * if loader running
663 * wait until loader checks in, it will die
664 * else
665 * pass immediately
666 * else
667 * if have context
668 * pass immediately
669 * else
670 * wait for main to get to us, it will never be used again
671 *
672 * undefined behaviour:
673 * fatal_exit_loop is called in both threads, preventing an appropriate
674 * reaction to the crash. This *should* be made
675 * obvious by the assertion
676 */
677 vg_acquire_thread_sync();
678
679 vg_mutex_lock( &vg.mux_engine_status );
680 vg.engine_status = k_engine_status_crashed;
681 vg.str_const_engine_err = error;
682 vg_mutex_unlock( &vg.mux_engine_status );
683
684 /*
685 * Wait for loader to finish what it was doing, if it was running.
686 * Then we can continue in our nice error screen
687 */
688 if( vg_thread_info.purpose == k_thread_purpose_main )
689 {
690 vg_semaphore_wait( &vg.sem_loader );
691 }
692 vg_audio_free(NULL);
693
694 #ifndef _WIN32
695 while(1)
696 {
697 if( glfwWindowShouldClose( vg.window ) )
698 break;
699
700 if( glfwGetKey( vg.window, GLFW_KEY_ESCAPE ) )
701 break;
702
703 glfwPollEvents();
704
705 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
706 glEnable(GL_BLEND);
707 glDisable(GL_DEPTH_TEST);
708 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
709 glBlendEquation(GL_FUNC_ADD);
710
711 glClearColor( 0.15f + sinf(glfwGetTime())*0.1f, 0.0f, 0.0f,1.0f );
712 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
713 glViewport( 0,0, vg.window_x, vg.window_y );
714
715 vg_render_log();
716
717 glfwSwapBuffers( vg.window );
718 }
719 #endif
720
721 /* Can now shutdown and EXIT */
722 vg_loader_free();
723 ui_default_free();
724
725 glfwTerminate();
726 exit(0);
727 }
728 }
729
730 #else
731
732 static void vg_fatal_exit_loop( const char *error )
733 {
734 vg_error( "Fatal error: %s\n", error );
735 exit(0);
736 }
737
738 #endif
739
740 /*
741 * Graphic cards will check these to force it to use the GPU
742 */
743 u32 NvOptimusEnablement = 0x00000001;
744 int AmdPowerXpressRequestHighPerformance = 1;
745
746 #endif