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