api
[vg.git] / vg.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 /*
4
5 .-. VG Event loop
6 | 0 |
7 | | .---------------------------------------------------------.
8 |API| | vg_enter( int argc, char *argv[], const char *window_name |
9 | | '---------------------------------------------------------'
10 | | |
11 | | v
12 |IMP| vg_launch_opt(void) <--.
13 | | | |
14 | | |'---------------'
15 | | | .-.
16 | | |'-----------------------------------| 1 |------.
17 | | | | | |
18 | | | | | v
19 | | | |IMP| vg_preload(void)
20 | | | | | |
21 | | .-----+. | | v
22 | | | | |IMP| vg_load(void)
23 | | | v '___' |
24 |IMP| | vg_framebuffer_resize(void) |
25 | | | | |
26 |IMP| | |.------------- vg_start(void) ---------------'
27 | | | |
28 | | | v
29 |IMP| | vg_update( int loaded )
30 | | | |
31 | | | .-----+.
32 | | | | |
33 | | | | v
34 |IMP| | '- vg_update_fixed( int loaded )
35 | | | |
36 | | | .-'
37 | | | |
38 | | | v
39 |IMP| | vg_update_post(void)
40 | | | |
41 | | | v
42 |IMP| | vg_render(void)
43 | | | |
44 | | | v
45 |IMP| | vg_ui(void)
46 | | | |
47 | | '----'
48 '___'
49
50 .-.
51 | ? |
52 | | .-------------------------------------.
53 |API| | vg_fatal_exit_loop( const char *err ) |
54 | | '-------------------------------------'
55 | | |
56 | | .------+.
57 | | | |
58 | | | v
59 |IMP| '- vg_framebuffer_resize(void)
60 '___'
61
62 */
63
64 #ifndef VG_STATIC
65 #define VG_STATIC
66 #endif
67
68 /* API */
69 VG_STATIC void vg_enter( int argc, char *argv[], const char *window_name );
70
71 /* Thread 1 */
72 VG_STATIC void vg_preload(void);
73 VG_STATIC void vg_load(void);
74
75 /* Main thread */
76 VG_STATIC void vg_launch_opt(void);
77 VG_STATIC void vg_start(void);
78
79 VG_STATIC void vg_framebuffer_resize(int w, int h);
80 VG_STATIC void vg_update(int loaded);
81 VG_STATIC void vg_update_fixed(int loaded);
82 VG_STATIC void vg_update_post(int loaded);
83
84 VG_STATIC void vg_render(void);
85 VG_STATIC void vg_ui(void);
86
87 #ifndef VG_HEADER_H
88 #define VG_HEADER_H
89
90 #include "vg_platform.h"
91 #include "vg_mem.h"
92
93 #ifndef _WIN32
94 #include <execinfo.h>
95 #endif
96
97 #ifdef VG_GAME
98 #include "dep/glad/glad.h"
99 #include "submodules/SDL/include/SDL.h"
100 #include "vg_stdint.h"
101
102 void vg_register_exit( void( *funcptr )(void), const char *name );
103
104 #include "vg_m.h"
105 #include "vg_io.h"
106 #include "vg_log.h"
107 #include "vg_steam.h"
108
109 //#define VG_SYNC_DEBUG
110 #ifdef VG_SYNC_DEBUG
111 #define VG_SYNC_LOG(STR,...) \
112 vg_info(STR,SDL_GetThreadID(NULL),##__VA_ARGS__)
113 #else
114 #define VG_SYNC_LOG(...)
115 #endif
116
117 struct vg
118 {
119 /* Engine sync */
120 SDL_Window *window;
121 SDL_GLContext gl_context;
122
123 SDL_SpinLock sl_context;
124 SDL_sem *sem_allow_exec,
125 *sem_exec_finished,
126 *sem_loader;
127
128 SDL_threadID thread_id_main,
129 thread_id_loader,
130 thread_id_with_opengl_context;
131 int context_ownership_refcount;
132
133 int exec_context;
134
135 enum engine_status
136 {
137 k_engine_status_none,
138 k_engine_status_running,
139 k_engine_status_crashed
140 }
141 engine_status;
142 const char *str_const_engine_err;
143 int is_loaded;
144
145 /* Window information */
146 int window_x,
147 window_y,
148 samples,
149 window_should_close;
150
151 float refresh_rate;
152
153 double mouse_pos[2];
154 v2f mouse_delta,
155 mouse_wheel;
156
157 /* Runtime */
158 double time,
159 time_delta,
160 frame_delta,
161 time_real,
162 time_real_last,
163 time_rate,
164 accumulator;
165
166 int fixed_iterations;
167
168 enum engine_stage
169 {
170 k_engine_stage_none,
171 k_engine_stage_update,
172 k_engine_stage_update_fixed,
173 k_engine_stage_rendering,
174 k_engine_stage_ui
175 }
176 engine_stage;
177
178 /* graphics */
179 m4x4f pv;
180 enum quality_profile
181 {
182 k_quality_profile_high = 0,
183 k_quality_profile_low = 1,
184 }
185 quality_profile;
186 }
187 VG_STATIC vg = { .time_rate = 1.0 };
188
189 struct vg_thread_info
190 {
191 enum vg_thread_purpose
192 {
193 k_thread_purpose_nothing,
194 k_thread_purpose_main,
195 k_thread_purpose_loader
196 }
197 purpose;
198
199 int gl_context_level;
200 };
201
202 VG_STATIC void vg_fatal_exit_loop( const char *error );
203
204 VG_STATIC void vg_ensure_engine_running(void)
205 {
206 VG_SYNC_LOG( "[%d] Checks if engine is running\n" );
207
208 /* Check if the engine is no longer running */
209 SDL_AtomicLock( &vg.sl_context );
210
211 if( vg.engine_status != k_engine_status_running )
212 {
213 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
214
215 /* Safe to disregard loader thread from this point on, elswhere */
216 if( vg.thread_id_loader == SDL_GetThreadID(NULL) )
217 {
218 SDL_SemPost( vg.sem_loader );
219 }
220
221 SDL_AtomicUnlock( &vg.sl_context );
222
223 VG_SYNC_LOG( "[%d] is just going to hang around and wait to die\n");
224 while(1) SDL_Delay(1000);
225 }
226
227 SDL_AtomicUnlock( &vg.sl_context );
228 }
229
230 /*
231 * Sync execution so that the OpenGL context is switched onto this thread.
232 * Anything after this call will be in a valid context.
233 */
234 VG_STATIC void vg_acquire_thread_sync(void)
235 {
236 VG_SYNC_LOG( "[%d] Starts acquiring sync\n" );
237
238 /* We dont want to do anything if this is the main thread */
239 SDL_AtomicLock( &vg.sl_context );
240 if( vg.thread_id_main == SDL_GetThreadID(NULL) )
241 {
242 SDL_AtomicUnlock( &vg.sl_context );
243 return;
244 }
245 SDL_AtomicUnlock( &vg.sl_context );
246
247 vg_ensure_engine_running();
248
249 /* Check if thread already has the context */
250 SDL_AtomicLock( &vg.sl_context );
251 if( vg.thread_id_with_opengl_context == SDL_GetThreadID(NULL) )
252 {
253 vg.context_ownership_refcount ++;
254 SDL_AtomicUnlock( &vg.sl_context );
255
256 VG_SYNC_LOG( "[%d] We already have sync here\n" );
257 return;
258 }
259
260 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
261 vg.exec_context = 1;
262 SDL_AtomicUnlock( &vg.sl_context );
263
264 /* wait until told we can go */
265 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
266
267 SDL_SemWait( vg.sem_allow_exec );
268 SDL_GL_MakeCurrent( vg.window, vg.gl_context );
269
270 VG_SYNC_LOG( "[%d] Allow exec passed\n" );
271
272 /* context now valid to work in while we hold up main thread */
273 SDL_AtomicLock( &vg.sl_context );
274 vg.context_ownership_refcount ++;
275 SDL_AtomicUnlock( &vg.sl_context );
276
277 VG_SYNC_LOG( "[%d] Context acquired.\n" );
278 }
279
280 /*
281 * Signify that we are done with the OpenGL context in this thread.
282 * Anything after this call will be in an undefined context.
283 */
284 VG_STATIC void vg_release_thread_sync(void)
285 {
286 VG_SYNC_LOG( "[%d] Releases sync\n" );
287
288 SDL_AtomicLock( &vg.sl_context );
289 if( vg.thread_id_main == SDL_GetThreadID(NULL) )
290 {
291 SDL_AtomicUnlock( &vg.sl_context );
292 return;
293 }
294
295 /* signal that we are done */
296 vg.context_ownership_refcount --;
297 if( !vg.context_ownership_refcount )
298 {
299 SDL_AtomicUnlock( &vg.sl_context );
300
301 VG_SYNC_LOG( "[%d] Releasing context.\n" );
302
303 SDL_GL_MakeCurrent( NULL, NULL );
304 SDL_SemPost( vg.sem_exec_finished );
305 }
306 else
307 SDL_AtomicUnlock( &vg.sl_context );
308 }
309
310 VG_STATIC void vg_run_synced_content(void)
311 {
312 SDL_AtomicLock( &vg.sl_context );
313
314 if( vg.exec_context != 0 )
315 {
316 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
317 vg.exec_context = 0;
318 SDL_AtomicUnlock( &vg.sl_context );
319
320 /* allow operations to go */
321 SDL_GL_MakeCurrent( NULL, NULL );
322 SDL_SemPost( vg.sem_allow_exec );
323
324 /* wait for operations to complete */
325 VG_SYNC_LOG( "[%d] Waiting for content.\n" );
326 SDL_SemWait( vg.sem_exec_finished );
327
328 /* check if we killed the engine */
329 vg_ensure_engine_running();
330
331 /* re-engage main thread */
332 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
333 SDL_GL_MakeCurrent( vg.window, vg.gl_context );
334 }
335 else
336 SDL_AtomicUnlock( &vg.sl_context );
337 }
338
339 VG_STATIC void vg_opengl_sync_init(void)
340 {
341 vg.sem_allow_exec = SDL_CreateSemaphore(0);
342 vg.sem_exec_finished = SDL_CreateSemaphore(0);
343 vg.sem_loader = SDL_CreateSemaphore(1);
344 }
345
346 VG_STATIC void vg_checkgl( const char *src_info );
347 #define VG_STRINGIT( X ) #X
348 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
349
350 #include "vg_console.h"
351 #include "vg_profiler.h"
352 #include "vg_audio.h"
353 #include "vg_shader.h"
354 #include "vg_tex.h"
355 #include "vg_input.h"
356 #include "vg_ui.h"
357 #include "vg_lines.h"
358 #include "vg_loader.h"
359 #include "vg_opt.h"
360
361 /* Diagnostic */
362 VG_STATIC struct vg_profile vg_prof_update = {.name="update()"},
363 vg_prof_render = {.name="render()"};
364
365 VG_STATIC void vg_checkgl( const char *src_info )
366 {
367 int fail = 0;
368
369 GLenum err;
370 while( (err = glGetError()) != GL_NO_ERROR )
371 {
372 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
373 fail = 1;
374 }
375
376 if( fail )
377 vg_fatal_exit_loop( "OpenGL Error" );
378 }
379
380 VG_STATIC void vg_bake_shaders(void)
381 {
382 vg_acquire_thread_sync();
383
384 #if 0
385 vg_function_push( (struct vg_cmd)
386 {
387 .name = "shaders",
388 .function = vg_shaders_live_recompile
389 });
390 #endif
391
392 vg_shaders_compile();
393 vg_release_thread_sync();
394 }
395
396 VG_STATIC void vg_load_full(void)
397 {
398 vg_preload();
399
400 /* internal */
401 vg_loader_highwater( vg_input_init, vg_input_free, NULL );
402 vg_loader_highwater( vg_lines_init, NULL, NULL );
403 vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
404 vg_loader_highwater( vg_profiler_init, NULL, NULL );
405
406 /* client */
407 vg_load();
408
409 vg_acquire_thread_sync();
410 vg.is_loaded = 1;
411 vg_release_thread_sync();
412 }
413
414 VG_STATIC void vg_process_events(void)
415 {
416 /* Update timers */
417 vg.time_real_last = vg.time_real;
418 vg.time_real = (double)SDL_GetTicks64() / 1000.0;
419 vg.frame_delta = vg.time_real-vg.time_real_last;
420
421 v2_zero( vg.mouse_wheel );
422 v2_zero( vg.mouse_delta );
423
424 /* SDL event loop */
425 SDL_Event event;
426 while( SDL_PollEvent( &event ) )
427 {
428 if( event.type == SDL_KEYDOWN )
429 {
430 console_proc_key( event.key.keysym );
431 }
432 else if( event.type == SDL_MOUSEWHEEL )
433 {
434 vg.mouse_wheel[0] += event.wheel.preciseX;
435 vg.mouse_wheel[1] += event.wheel.preciseY;
436 }
437 else if( event.type == SDL_CONTROLLERAXISMOTION ||
438 event.type == SDL_CONTROLLERBUTTONDOWN ||
439 event.type == SDL_CONTROLLERBUTTONUP ||
440 event.type == SDL_CONTROLLERDEVICEADDED ||
441 event.type == SDL_CONTROLLERDEVICEREMOVED
442 )
443 {
444 vg_input_controller_event( &event );
445 }
446 else if( event.type == SDL_MOUSEMOTION )
447 {
448 vg.mouse_delta[0] += event.motion.xrel;
449 vg.mouse_delta[1] += event.motion.yrel;
450 }
451 else if( event.type == SDL_WINDOWEVENT )
452 {
453 if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED )
454 {
455 int w, h;
456 SDL_GL_GetDrawableSize( vg.window, &w, &h );
457
458 if( !w || !h )
459 {
460 vg_warn( "Got a invalid framebuffer size: "
461 "%dx%d... ignoring\n", w, h );
462 }
463 else
464 {
465 vg.window_x = w;
466 vg.window_y = h;
467
468 vg_framebuffer_resize(w,h);
469 }
470 }
471 else if( event.window.event == SDL_WINDOWEVENT_CLOSE )
472 {
473 vg.window_should_close = 1;
474 }
475 }
476 else if( event.type == SDL_TEXTINPUT )
477 {
478 console_proc_utf8( event.text.text );
479 }
480 }
481
482 vg.mouse_pos[0] += vg.mouse_delta[0];
483 vg.mouse_pos[1] += vg.mouse_delta[1];
484
485 /* Update input */
486 vg_update_inputs();
487 }
488
489 VG_STATIC void vg_gameloop_update( int post_start )
490 {
491 vg_profile_begin( &vg_prof_update );
492
493 vg.engine_stage = k_engine_stage_update;
494 vg_update( post_start );
495
496 /* Fixed update loop */
497 vg.engine_stage = k_engine_stage_update_fixed;
498 vg.accumulator += vg.time_delta;
499
500 vg.fixed_iterations = 0;
501 vg_lines.allow_input = 1;
502 while( vg.accumulator >= (VG_TIMESTEP_FIXED-0.00125) )
503 {
504 vg_update_fixed( post_start );
505 vg_lines.allow_input = 0;
506
507 vg.accumulator -= VG_TIMESTEP_FIXED;
508 vg.accumulator = VG_MAX( 0.0, vg.accumulator );
509
510 vg.fixed_iterations ++;
511 if( vg.fixed_iterations == 8 )
512 {
513 break;
514 }
515 }
516 vg_lines.allow_input = 1;
517
518 vg.engine_stage = k_engine_stage_update;
519 vg_update_post( post_start );
520 vg_profile_end( &vg_prof_update );
521 }
522
523 VG_STATIC void vg_gameloop_render( int post_start )
524 {
525 vg_profile_begin( &vg_prof_render );
526
527 if( post_start )
528 {
529 /* render */
530 vg.engine_stage = k_engine_stage_rendering;
531 vg_render();
532
533 /* ui */
534 vg.engine_stage = k_engine_stage_ui;
535 {
536 ui_begin( vg.window_x, vg.window_y );
537
538 /* TODO */
539 ui_set_mouse( vg.mouse_pos[0], vg.mouse_pos[1], 0 );
540
541 vg_profile_drawn(
542 (struct vg_profile *[]){&vg_prof_update,&vg_prof_render}, 2,
543 (1.0f/(float)vg.refresh_rate)*1000.0f,
544 (ui_rect){ 4, 4, 250, 0 }, 0
545 );
546
547 if( vg_profiler )
548 {
549
550 char perf[128];
551
552 snprintf( perf, 127,
553 "x: %d y: %d\n"
554 "refresh: %.1f (%.1fms)\n"
555 "samples: %d\n"
556 "iterations: %d (acc: %.3fms%%)\n",
557 vg.window_x, vg.window_y,
558 vg.refresh_rate, (1.0f/vg.refresh_rate)*1000.0f,
559 vg.samples,
560 vg.fixed_iterations,
561 (vg.accumulator/VG_TIMESTEP_FIXED)*100.0f );
562
563 ui_text( (ui_rect){258, 4+24+12,0,0},perf, 1,0);
564 }
565
566 audio_debug_ui( vg.pv );
567 vg_ui();
568 vg_console_draw();
569
570 ui_resolve();
571 ui_draw( NULL );
572 }
573 }
574
575 vg_profile_end( &vg_prof_render );
576 }
577
578 VG_STATIC void vg_gameloop(void)
579 {
580 vg.accumulator = 0.75f * (1.0f/60.0f);
581
582 int post_start = 0;
583 while(1)
584 {
585 vg_process_events();
586
587 if( vg.window_should_close )
588 break;
589
590 /* scaled time */
591 vg.time_delta = vg.frame_delta * vg.time_rate;
592 vg.time += vg.time_delta;
593
594 if( vg.is_loaded )
595 {
596 if( !post_start )
597 {
598 vg_start();
599 post_start = 1;
600 }
601 }
602 else
603 {
604 vg_loader_render();
605 }
606
607 vg_gameloop_update( post_start );
608 vg_gameloop_render( post_start );
609
610 SDL_GL_SwapWindow( vg.window );
611 vg_run_synced_content();
612 }
613 }
614
615 VG_STATIC void vg_process_launch_opts_internal( int argc, char *argv[] )
616 {
617 char *arg;
618 while( vg_argp( argc, argv ) )
619 {
620 if( (arg = vg_opt_arg( 'w' )) )
621 {
622 vg.window_x = atoi( arg );
623 }
624
625 if( (arg = vg_opt_arg( 'h' )) )
626 {
627 vg.window_y = atoi( arg );
628 }
629
630 if( (arg = vg_long_opt_arg( "samples" )) )
631 {
632 vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
633 }
634
635 if( vg_long_opt( "use-libc-malloc" ) )
636 {
637 vg_mem.use_libc_malloc = atoi( arg );
638 }
639
640 if( vg_long_opt( "high-performance" ) )
641 {
642 vg.quality_profile = k_quality_profile_low;
643 }
644
645 vg_launch_opt();
646 }
647 }
648
649 VG_STATIC void vg_init_window( const char *window_name )
650 {
651 if( SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_GAMECONTROLLER) != 0 )
652 {
653 vg_error( "SDL_Init failed: %s\n", SDL_GetError() );
654 exit(0);
655 }
656
657 SDL_GL_SetSwapInterval( 1 );
658 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
659 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
660 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
661 SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,
662 SDL_GL_CONTEXT_PROFILE_CORE );
663
664 SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 1 );
665 SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
666 SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
667 SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
668 SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
669
670 if( vg.samples > 1 )
671 {
672 SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 1 );
673 SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, vg.samples );
674 }
675
676 SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR,
677 SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH );
678
679 /*
680 * Get monitor information
681 */
682 int display_count = 0, display_index = 0, mode_index = 0;
683 if( (display_count = SDL_GetNumVideoDisplays()) < 1 )
684 {
685 vg_error( "SDL_GetNumVideoDisplays returned: %i\n", display_count );
686 exit(0);
687 }
688
689 /* TODO: Allow chosing the modes at startup */
690 SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 };
691 if( SDL_GetDisplayMode( display_index, mode_index, &mode ) != 0 )
692 {
693 vg_error( "SDL_GetDisplayMode failed: %s", SDL_GetError() );
694 exit(0);
695 }
696
697 vg.refresh_rate = mode.refresh_rate;
698
699 /* TODO: Allow selecting closest video mode from launch opts */
700 if((vg.window = SDL_CreateWindow( window_name,
701 SDL_WINDOWPOS_UNDEFINED,
702 SDL_WINDOWPOS_UNDEFINED,
703 mode.w, mode.h,
704
705 SDL_WINDOW_FULLSCREEN |
706 SDL_WINDOW_OPENGL |
707 SDL_WINDOW_INPUT_GRABBED )))
708 {
709 }
710 else
711 {
712 vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() );
713 exit(0);
714 }
715
716 /*
717 * OpenGL loading
718 */
719 if( (vg.gl_context = SDL_GL_CreateContext(vg.window) ))
720 {
721 SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y );
722 vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y );
723 }
724 else
725 {
726 vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
727 SDL_Quit();
728 exit(0);
729 }
730
731 if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) )
732 {
733 vg_error( "Glad Failed to initialize\n" );
734 SDL_GL_DeleteContext( vg.gl_context );
735 SDL_Quit();
736 exit(0);
737 }
738
739 const unsigned char* glver = glGetString( GL_VERSION );
740 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
741 }
742
743 VG_STATIC void vg_enter( int argc, char *argv[], const char *window_name )
744 {
745 vg_process_launch_opts_internal( argc, argv );
746
747 /* Systems init */
748 vg_alloc_quota();
749 vg_log_init();
750 vg_console_init();
751 vg_init_window( window_name );
752
753 SDL_SetRelativeMouseMode(1);
754 vg.thread_id_main = SDL_GetThreadID(NULL);
755
756 /* Opengl-required systems */
757 ui_init_context();
758 vg_loader_init();
759
760 vg.engine_status = k_engine_status_running;
761
762 vg_opengl_sync_init();
763 vg_loader_start();
764
765 /* main */
766 vg_gameloop();
767
768 /* Shutdown */
769 vg_console_write_persistent();
770
771 SDL_AtomicLock( &vg.sl_context );
772 vg.engine_status = k_engine_status_none;
773 SDL_AtomicUnlock( &vg.sl_context );
774
775 vg_loader_free();
776
777 vg_success( "If you see this it means everything went.. \"well\".....\n" );
778
779 SDL_GL_DeleteContext( vg.gl_context );
780 SDL_Quit();
781 }
782
783 /*
784 * Immediately transfer away from calling thread into a safe loop, signal for
785 * others to shutdown, then free everything once the user closes the window.
786 *
787 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, ONLY
788 * when calling the program from outside its normal directory.
789 */
790 VG_STATIC void vg_fatal_exit_loop( const char *error )
791 {
792 /*
793 * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
794 * thanks gnu <3
795 *
796 * TODO: this on windows?
797 */
798
799 #ifndef _WIN32
800
801 void *array[20];
802 char **strings;
803 int size, i;
804
805 size = backtrace( array, 20 );
806 strings = backtrace_symbols( array, size );
807
808 if( strings != NULL )
809 {
810 vg_error( "---------------- gnu backtrace -------------\n" );
811
812 for( int i=0; i<size; i++ )
813 vg_info( "%s\n", strings[i] );
814
815 vg_error( "---------------- gnu backtrace -------------\n" );
816 }
817
818 free( strings );
819
820 #endif
821
822 vg_error( "Fatal error: %s\n", error );
823
824 SDL_AtomicLock( &vg.sl_context );
825 if( vg.engine_status == k_engine_status_none )
826 {
827 SDL_AtomicUnlock( &vg.sl_context );
828
829 /* TODO: Correct shutdown before other systems */
830 exit(0);
831 }
832 else
833 {
834 SDL_AtomicUnlock( &vg.sl_context );
835
836 /*
837 * if main
838 * if loader running
839 * wait until loader checks in, it will die
840 * else
841 * pass immediately
842 * else
843 * if have context
844 * pass immediately
845 * else
846 * wait for main to get to us, it will never be used again
847 *
848 * undefined behaviour:
849 * fatal_exit_loop is called in both threads, preventing an appropriate
850 * reaction to the crash. This *should* be made
851 * obvious by the assertion
852 */
853 vg_acquire_thread_sync();
854
855 SDL_AtomicUnlock( &vg.sl_context );
856 vg.engine_status = k_engine_status_crashed;
857 vg.str_const_engine_err = error;
858
859 /*
860 * Wait for loader to finish what it was doing, if it was running.
861 * Then we can continue in our nice error screen
862 */
863 if( vg.thread_id_main == SDL_GetThreadID(NULL) )
864 {
865 SDL_AtomicUnlock( &vg.sl_context );
866 SDL_SemWait( vg.sem_loader );
867 }
868 else
869 SDL_AtomicUnlock( &vg.sl_context );
870
871 vg_audio_free(NULL);
872
873 while(1)
874 {
875 vg_process_events();
876 if( vg.window_should_close )
877 break;
878
879 if( vg_getkey( SDLK_ESCAPE ) )
880 break;
881
882 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
883 glEnable(GL_BLEND);
884 glDisable(GL_DEPTH_TEST);
885 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
886 glBlendEquation(GL_FUNC_ADD);
887
888 glClearColor( 0.15f + sinf(vg.time_real)*0.1f, 0.0f, 0.0f,1.0f );
889 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
890 glViewport( 0,0, vg.window_x, vg.window_y );
891
892 vg_render_log();
893 SDL_GL_SwapWindow( vg.window );
894 }
895
896 /* Can now shutdown and EXIT */
897 vg_loader_free();
898 SDL_GL_DeleteContext( vg.gl_context );
899 SDL_Quit();
900 exit(0);
901 }
902 }
903
904 #else /* VG_GAME */
905
906 VG_STATIC void vg_fatal_exit_loop( const char *error )
907 {
908 vg_error( "Fatal error: %s\n", error );
909 exit(0);
910 }
911
912 #endif /* VG_GAME */
913
914 /*
915 * Graphic cards will check these to force it to use the GPU
916 */
917 u32 NvOptimusEnablement = 0x00000001;
918 int AmdPowerXpressRequestHighPerformance = 1;
919
920 #endif /* VG_HEADER_H */