basic async mechanism
[vg.git] / vg.h
1 /* Copyright (C) 2021-2023 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(void)
30 | | | |
31 | | | .-----+.
32 | | | | |
33 | | | | v
34 |IMP| | '- vg_update_fixed(void)
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_HEADER_H
65 #define VG_HEADER_H
66
67 #include "vg_platform.h"
68 #include "vg_mem.h"
69
70 #ifndef _WIN32
71 #include <execinfo.h>
72 #endif
73
74 VG_STATIC void vg_print_backtrace(void)
75 {
76 #ifndef _WIN32
77
78 void *array[20];
79 char **strings;
80 int size, i;
81
82 size = backtrace( array, 20 );
83 strings = backtrace_symbols( array, size );
84
85 if( strings != NULL ){
86 vg_error( "---------------- gnu backtrace -------------\n" );
87
88 for( int i=0; i<size; i++ )
89 vg_info( "%s\n", strings[i] );
90
91 vg_error( "---------------- gnu backtrace -------------\n" );
92 }
93
94 free( strings );
95
96 #endif
97 }
98
99 #ifdef VG_GAME
100 #include "dep/glad/glad.h"
101 #include "submodules/SDL/include/SDL.h"
102 #include "vg_stdint.h"
103
104 void vg_register_exit( void( *funcptr )(void), const char *name );
105
106 #include "vg_m.h"
107 #include "vg_io.h"
108 #include "vg_log.h"
109 #include "vg_async.h"
110 #include "vg_steam.h"
111
112 //#define VG_SYNC_DEBUG
113 #ifdef VG_SYNC_DEBUG
114 #define VG_SYNC_LOG(STR,...) \
115 vg_info(STR,SDL_GetThreadID(NULL),##__VA_ARGS__)
116 #else
117 #define VG_SYNC_LOG(...)
118 #endif
119
120 /* API */
121 VG_STATIC void vg_enter( int argc, char *argv[], const char *window_name );
122
123 /* Thread 1 */
124 VG_STATIC void vg_preload(void);
125 VG_STATIC void vg_load(void);
126
127 /* Main thread */
128 VG_STATIC void vg_launch_opt(void);
129 VG_STATIC void vg_start(void);
130
131 VG_STATIC void vg_framebuffer_resize(int w, int h);
132 VG_STATIC void vg_update(void);
133 VG_STATIC void vg_update_fixed(void);
134 VG_STATIC void vg_update_post(void);
135
136 VG_STATIC void vg_render(void);
137 VG_STATIC void vg_ui(void);
138
139 struct vg
140 {
141 /* Engine sync */
142 SDL_Window *window;
143 SDL_GLContext gl_context;
144
145 SDL_SpinLock sl_context;
146 SDL_sem *sem_allow_exec,
147 *sem_exec_finished,
148 *sem_loader;
149
150 SDL_threadID thread_id_main,
151 thread_id_loader,
152 thread_id_with_opengl_context;
153 int context_ownership_depth;
154
155 int exec_context;
156
157 enum engine_status
158 {
159 k_engine_status_none,
160 k_engine_status_running,
161 k_engine_status_crashed
162 }
163 engine_status;
164 const char *str_const_engine_err;
165 int is_loaded;
166
167 /* Window information */
168 int window_x,
169 window_y,
170 samples,
171 window_should_close;
172
173 int display_refresh_rate,
174 fps_limit; /* 0: use vsync, >0: cap fps to this, no vsync */
175
176 enum vsync_feature
177 {
178 k_vsync_feature_disabled=0,
179 k_vsync_feature_enabled=1,
180 k_vsync_feature_enabled_adaptive=2,
181 k_vsync_feature_error=3
182 }
183 vsync_feature;
184
185 double mouse_pos[2];
186 v2f mouse_delta,
187 mouse_wheel;
188
189 /* Runtime */
190 double time,
191 time_delta,
192 time_rate,
193
194 time_fixed_accumulator,
195 time_fixed_extrapolate,
196 time_frame_delta;
197
198 u64 time_hp, time_hp_last, time_spinning;
199
200 int fixed_iterations;
201
202 enum engine_stage
203 {
204 k_engine_stage_none,
205 k_engine_stage_update,
206 k_engine_stage_update_fixed,
207 k_engine_stage_rendering,
208 k_engine_stage_ui
209 }
210 engine_stage;
211
212 /* graphics */
213 m4x4f pv;
214 enum quality_profile
215 {
216 k_quality_profile_high = 0,
217 k_quality_profile_low = 1,
218 }
219 quality_profile;
220 }
221 VG_STATIC vg = { .time_rate = 1.0 };
222
223 enum vg_thread_purpose
224 {
225 k_thread_purpose_nothing,
226 k_thread_purpose_main,
227 k_thread_purpose_loader
228 };
229
230 VG_STATIC void vg_fatal_exit_loop( const char *error );
231
232 /*
233 * Checks if the engine is running
234 */
235 VG_STATIC void _vg_ensure_engine_running(void)
236 {
237 /* Check if the engine is no longer running */
238 SDL_AtomicLock( &vg.sl_context );
239 enum engine_status status = vg.engine_status;
240 SDL_AtomicUnlock( &vg.sl_context );
241
242 if( status != k_engine_status_running ){
243 while(1) {
244 VG_SYNC_LOG( "[%d] No longer running...\n");
245 SDL_Delay(1000);
246 }
247 }
248 }
249
250 VG_STATIC enum vg_thread_purpose vg_thread_purpose(void)
251 {
252 SDL_AtomicLock( &vg.sl_context );
253
254 if( vg.thread_id_main == SDL_GetThreadID(NULL) ){
255 SDL_AtomicUnlock( &vg.sl_context );
256 return k_thread_purpose_main;
257 }
258 else{
259 SDL_AtomicUnlock( &vg.sl_context );
260 return k_thread_purpose_loader;
261 }
262 }
263
264 /*
265 * Sync execution so that the OpenGL context is switched onto this thread.
266 * Anything after this call will be in a valid context.
267 */
268 VG_STATIC void vg_acquire_thread_sync(void)
269 {
270 /* We dont want to do anything if this is the main thread */
271
272 if( vg_thread_purpose() == k_thread_purpose_loader ){
273 VG_SYNC_LOG( "[%d] vg_acquire_thread_sync()\n" );
274 _vg_ensure_engine_running();
275
276 SDL_AtomicLock( &vg.sl_context );
277 if( vg.context_ownership_depth == 0 ){
278 vg.context_ownership_depth ++;
279 vg.exec_context = 1;
280 SDL_AtomicUnlock( &vg.sl_context );
281
282 /* wait until told we can go */
283 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
284 SDL_SemWait( vg.sem_allow_exec );
285
286 _vg_ensure_engine_running();
287
288 SDL_GL_MakeCurrent( vg.window, vg.gl_context );
289 VG_SYNC_LOG( "[%d] granted\n" );
290 }
291 else{
292 vg.context_ownership_depth ++;
293 VG_SYNC_LOG( "[%d] granted\n" );
294 SDL_AtomicUnlock( &vg.sl_context );
295 }
296 }
297 }
298
299 /*
300 * Signify that we are done with the OpenGL context in this thread.
301 * Anything after this call will be in an undefined context.
302 */
303 VG_STATIC void vg_release_thread_sync(void)
304 {
305 if( vg_thread_purpose() == k_thread_purpose_loader ){
306 VG_SYNC_LOG( "[%d] vg_release_thread_sync()\n" );
307
308 SDL_AtomicLock( &vg.sl_context );
309 vg.context_ownership_depth --;
310
311 if( vg.context_ownership_depth == 0 ){
312 SDL_AtomicUnlock( &vg.sl_context );
313 VG_SYNC_LOG( "[%d] Releasing context.\n" );
314 SDL_GL_MakeCurrent( NULL, NULL );
315 SDL_SemPost( vg.sem_exec_finished );
316 }
317 else
318 SDL_AtomicUnlock( &vg.sl_context );
319 }
320 }
321
322 VG_STATIC void _vg_run_synced(void)
323 {
324 SDL_AtomicLock( &vg.sl_context );
325
326 if( vg.exec_context != 0 ){
327 VG_SYNC_LOG( "[%d] _vg_run_synced() (%d).\n", vg.exec_context );
328 vg.exec_context = 0;
329 SDL_AtomicUnlock( &vg.sl_context );
330
331 /* allow operations to go */
332 SDL_GL_MakeCurrent( NULL, NULL );
333 SDL_SemPost( vg.sem_allow_exec );
334
335 /* wait for operations to complete */
336 VG_SYNC_LOG( "[%d] Waiting for content.\n" );
337 SDL_SemWait( vg.sem_exec_finished );
338
339 /* check if we killed the engine */
340 _vg_ensure_engine_running();
341
342 /* re-engage main thread */
343 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
344 SDL_GL_MakeCurrent( vg.window, vg.gl_context );
345 }
346 else{
347 VG_SYNC_LOG( "[%d] Nothing to do.\n" );
348 SDL_AtomicUnlock( &vg.sl_context );
349 }
350 }
351
352 VG_STATIC void _vg_opengl_sync_init(void)
353 {
354 vg.sem_allow_exec = SDL_CreateSemaphore(0);
355 vg.sem_exec_finished = SDL_CreateSemaphore(0);
356 vg.sem_loader = SDL_CreateSemaphore(1);
357 }
358
359 VG_STATIC void vg_checkgl( const char *src_info );
360 #define VG_STRINGIT( X ) #X
361 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
362
363 #include "vg_console.h"
364 #include "vg_profiler.h"
365 #include "vg_audio.h"
366 #include "vg_shader.h"
367 #include "vg_tex.h"
368 #include "vg_input.h"
369 #include "vg_ui.h"
370 #include "vg_lines.h"
371 #include "vg_loader.h"
372 #include "vg_opt.h"
373
374 /* Diagnostic */
375 VG_STATIC struct vg_profile vg_prof_update = {.name="update()"},
376 vg_prof_render = {.name="render()"},
377 vg_prof_swap = {.name="swap"};
378
379 VG_STATIC void vg_checkgl( const char *src_info )
380 {
381 int fail = 0;
382
383 GLenum err;
384 while( (err = glGetError()) != GL_NO_ERROR ){
385 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
386 fail = 1;
387 }
388
389 if( fail )
390 vg_fatal_exit_loop( "OpenGL Error" );
391 }
392
393 VG_STATIC void vg_bake_shaders(void)
394 {
395 vg_acquire_thread_sync();
396 vg_console_reg_cmd( "reload_shaders", vg_shaders_live_recompile, NULL );
397
398 vg_shaders_compile();
399 vg_release_thread_sync();
400 }
401
402 void test_async_runner( void *payload, u32 size )
403 {
404 vg_success( "Async call test (%p, %u)\n", payload, size );
405 }
406
407 VG_STATIC void _vg_load_full(void)
408 {
409 vg_preload();
410
411 /* internal */
412 vg_loader_step( vg_input_init, vg_input_free );
413 vg_loader_step( vg_lines_init, NULL );
414 vg_loader_step( vg_audio_init, vg_audio_free );
415 vg_loader_step( vg_profiler_init, NULL );
416
417 /* client */
418 vg_load();
419
420 vg_async_item *test_async = vg_async_alloc( 0 );
421 vg_async_dispatch( test_async, test_async_runner );
422 }
423
424 VG_STATIC void _vg_process_events(void)
425 {
426 /* Update timers */
427 v2_zero( vg.mouse_wheel );
428 v2_zero( vg.mouse_delta );
429
430 /* SDL event loop */
431 SDL_Event event;
432 while( SDL_PollEvent( &event ) ){
433 if( event.type == SDL_KEYDOWN ){
434 console_proc_key( event.key.keysym );
435 }
436 else if( event.type == SDL_MOUSEWHEEL ){
437 vg.mouse_wheel[0] += event.wheel.preciseX;
438 vg.mouse_wheel[1] += event.wheel.preciseY;
439 }
440 else if( event.type == SDL_CONTROLLERAXISMOTION ||
441 event.type == SDL_CONTROLLERBUTTONDOWN ||
442 event.type == SDL_CONTROLLERBUTTONUP ||
443 event.type == SDL_CONTROLLERDEVICEADDED ||
444 event.type == SDL_CONTROLLERDEVICEREMOVED
445 )
446 {
447 vg_input_controller_event( &event );
448 }
449 else if( event.type == SDL_MOUSEMOTION ){
450 vg.mouse_delta[0] += event.motion.xrel;
451 vg.mouse_delta[1] += event.motion.yrel;
452 }
453 else if( event.type == SDL_WINDOWEVENT ){
454 if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ){
455 int w, h;
456 SDL_GL_GetDrawableSize( vg.window, &w, &h );
457
458 if( !w || !h ){
459 vg_warn( "Got a invalid framebuffer size: "
460 "%dx%d... ignoring\n", w, h );
461 }
462 else{
463 vg.window_x = w;
464 vg.window_y = h;
465
466 vg_framebuffer_resize(w,h);
467 }
468 }
469 else if( event.window.event == SDL_WINDOWEVENT_CLOSE ){
470 vg.window_should_close = 1;
471 }
472 }
473 else if( event.type == SDL_TEXTINPUT ){
474 console_proc_utf8( event.text.text );
475 }
476 }
477
478 vg.mouse_pos[0] += vg.mouse_delta[0];
479 vg.mouse_pos[1] += vg.mouse_delta[1];
480
481 /* Update input */
482 vg_update_inputs();
483 }
484
485 VG_STATIC void _vg_gameloop_update(void)
486 {
487 vg_profile_begin( &vg_prof_update );
488
489 vg.engine_stage = k_engine_stage_update;
490 vg_update();
491
492 /* Fixed update loop */
493 vg.engine_stage = k_engine_stage_update_fixed;
494
495 vg.fixed_iterations = 0;
496 vg_lines.allow_input = 1;
497 vg.time_fixed_accumulator += vg.time_delta;
498
499 while( vg.time_fixed_accumulator >= VG_TIMESTEP_FIXED ){
500 vg_update_fixed();
501 vg_lines.allow_input = 0;
502
503 vg.time_fixed_accumulator -= VG_TIMESTEP_FIXED;
504 //vg.accumulator = VG_MAX( 0.0, vg.accumulator );
505
506 vg.fixed_iterations ++;
507 if( vg.fixed_iterations == 8 ){
508 break;
509 }
510 }
511 vg_lines.allow_input = 1;
512 vg.time_fixed_extrapolate = vg.time_fixed_accumulator / VG_TIMESTEP_FIXED;
513
514 vg.engine_stage = k_engine_stage_update;
515 vg_update_post();
516 vg_profile_end( &vg_prof_update );
517 }
518
519 VG_STATIC void _vg_gameloop_render(void)
520 {
521 vg_profile_begin( &vg_prof_render );
522
523 if( vg.is_loaded ){
524 /* render */
525 vg.engine_stage = k_engine_stage_rendering;
526 vg_render();
527
528 /* ui */
529 vg.engine_stage = k_engine_stage_ui;
530 {
531 ui_begin( vg.window_x, vg.window_y );
532
533 /* TODO */
534 ui_set_mouse( vg.mouse_pos[0], vg.mouse_pos[1], 0 );
535
536 int frame_target = vg.display_refresh_rate;
537
538 if( vg.fps_limit > 0 ){
539 frame_target = vg.fps_limit;
540 }
541
542 vg_profile_drawn(
543 (struct vg_profile *[]){
544 &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3,
545 (1.0f/(float)frame_target)*1000.0f,
546 (ui_rect){ 4, 4, 250, 0 }, 0
547 );
548
549 if( vg_profiler ){
550 char perf[256];
551
552 snprintf( perf, 255,
553 "x: %d y: %d\n"
554 "refresh: %d (%.1fms)\n"
555 "samples: %d\n"
556 "iterations: %d (acc: %.3fms%%)\n"
557 "time: real(%.2f) delta(%.2f) rate(%.2f)\n"
558 #ifdef _WIN32
559 " extrap(%.2f) frame(%.2f) spin( %llu )\n",
560 #else
561 " extrap(%.2f) frame(%.2f) spin( %lu )\n",
562 #endif
563 vg.window_x, vg.window_y,
564 frame_target, (1.0f/(float)frame_target)*1000.0f,
565 vg.samples,
566 vg.fixed_iterations,
567 (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f,
568 vg.time, vg.time_delta, vg.time_rate,
569 vg.time_fixed_extrapolate, vg.time_frame_delta,
570 vg.time_spinning );
571
572 ui_text( (ui_rect){258, 4+24+12+12,0,0},perf, 1,0);
573 }
574
575 /* FIXME */
576 audio_debug_ui( vg.pv );
577 vg_ui();
578 _vg_console_draw();
579
580 ui_resolve();
581 ui_draw( NULL );
582 }
583 }
584
585 vg_profile_end( &vg_prof_render );
586 }
587
588 VG_STATIC int vg_framefilter( double dt )
589 {
590 if( (vg.fps_limit <= 0) && (vg.vsync_feature != k_vsync_feature_error) ){
591 /* turn on vsync if not enabled */
592
593 enum vsync_feature requested = k_vsync_feature_enabled;
594 if( vg.fps_limit < 0 ) requested = k_vsync_feature_enabled_adaptive;
595
596 if( vg.vsync_feature != requested ){
597 vg_info( "Setting swap interval\n" );
598
599 int swap_interval = 1;
600 if( requested == k_vsync_feature_enabled_adaptive ) swap_interval = -1;
601
602 if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){
603 if( requested == k_vsync_feature_enabled ){
604 vg_error( "Vsync is not supported by your system\n" );
605 vg_warn( "You may be overriding it in your"
606 " graphics control panel.\n" );
607 }
608 else{
609 vg_error( "Adaptive Vsync is not supported by your system\n" );
610 }
611
612 vg.vsync_feature = k_vsync_feature_error;
613 vg.fps_limit = vg.display_refresh_rate;
614
615 /* TODO: Make popup to notify user that this happened */
616 return 1;
617 }
618 else{
619 vg_success( "Vsync enabled (%d)\n", requested );
620 vg.vsync_feature = requested;
621 }
622 }
623
624 return 0;
625 }
626
627 if( vg.vsync_feature != k_vsync_feature_disabled ){
628 SDL_GL_SetSwapInterval( 0 );
629 vg.vsync_feature = k_vsync_feature_disabled;
630 }
631
632 if( vg.fps_limit < 25 ) vg.fps_limit = 25;
633 if( vg.fps_limit > 300 ) vg.fps_limit = 300;
634
635 double min_frametime = 1.0/(double)vg.fps_limit;
636 if( vg.time_frame_delta < min_frametime ){
637 /* TODO: we can use high res nanosleep on Linux here */
638 double sleep_ms = (min_frametime-vg.time_frame_delta) * 1000.0;
639 u32 ms = (u32)floor( sleep_ms );
640
641 if( ms ){
642 SDL_Delay( ms );
643 }
644 else{
645 vg.time_spinning ++;
646 }
647
648 return 1;
649 }
650
651 return 0;
652 }
653
654 VG_STATIC void _vg_gameloop(void)
655 {
656 //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f);
657
658 vg.time_hp = SDL_GetPerformanceCounter();
659 vg.time_hp_last = vg.time_hp;
660
661 int post_start = 0;
662 while(1){
663
664 vg.time_hp = SDL_GetPerformanceCounter();
665 u64 udt = vg.time_hp - vg.time_hp_last;
666 vg.time_hp_last = vg.time_hp;
667
668 double dt = (double)udt / (double)SDL_GetPerformanceFrequency();
669
670 vg.time_frame_delta += dt;
671
672 if( vg_framefilter( dt ) )
673 continue;
674
675 vg_profile_begin( &vg_prof_swap );
676 SDL_GL_SwapWindow( vg.window );
677 _vg_run_synced();
678 vg_profile_end( &vg_prof_swap );
679
680 vg.time_delta = vg.time_frame_delta * vg.time_rate;
681 vg.time += vg.time_delta;
682
683 vg_run_async_checked();
684 _vg_process_events();
685
686 if( vg.window_should_close )
687 break;
688
689 if( vg.is_loaded ){
690 if( !post_start ){
691 vg_start();
692 post_start = 1;
693 }
694 }
695 else{
696 _vg_loader_render();
697 }
698
699 _vg_gameloop_update();
700 _vg_gameloop_render();
701
702 vg.time_frame_delta = 0.0;
703 vg.time_spinning = 0;
704 }
705 }
706
707 VG_STATIC void _vg_process_launch_opts_internal( int argc, char *argv[] )
708 {
709 char *arg;
710 while( vg_argp( argc, argv ) ){
711 if( (arg = vg_opt_arg( 'w' )) ){
712 vg.window_x = atoi( arg );
713 }
714
715 if( (arg = vg_opt_arg( 'h' )) ){
716 vg.window_y = atoi( arg );
717 }
718
719 if( (arg = vg_long_opt_arg( "samples" )) ){
720 vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
721 }
722
723 if( vg_long_opt( "use-libc-malloc" ) ){
724 vg_mem.use_libc_malloc = 1;
725 }
726
727 if( vg_long_opt( "high-performance" ) ){
728 vg.quality_profile = k_quality_profile_low;
729 }
730
731 vg_launch_opt();
732 }
733 }
734
735 VG_STATIC void _vg_init_window( const char *window_name )
736 {
737 vg_info( "SDL_INIT\n" );
738
739 if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){
740 vg_error( "SDL_Init failed: %s\n", SDL_GetError() );
741 exit(0);
742 }
743
744 SDL_InitSubSystem( SDL_INIT_AUDIO );
745 SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER );
746
747 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
748 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
749 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
750 SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,
751 SDL_GL_CONTEXT_PROFILE_CORE );
752
753 SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR,
754 SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH );
755
756 SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
757 SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
758 SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
759 SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
760 SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 );
761
762 /*
763 * Get monitor information
764 */
765 vg_info( "Getting display count\n" );
766 int display_count = 0,
767 display_index = 0,
768 mode_index = 0;
769
770 #ifdef VG_DEVWINDOW
771 vg.window_x = 1600;
772 vg.window_y = 1000;
773 #else
774
775 SDL_DisplayMode video_mode;
776 if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){
777 vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() );
778 SDL_Quit();
779 exit(0);
780 }
781
782 vg.display_refresh_rate = video_mode.refresh_rate;
783 vg.window_x = video_mode.w;
784 vg.window_y = video_mode.h;
785 #endif
786
787 #ifndef _WIN32
788 SDL_SetHint( "SDL_VIDEO_X11_XINERAMA", "1" );
789 SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "0" );
790 SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "0" );
791 #endif
792
793 vg_info( "CreateWindow( %d %d @%dhz )\n", vg.window_x, vg.window_y,
794 vg.display_refresh_rate );
795
796 /* TODO: Allow selecting closest video mode from launch opts */
797 if((vg.window = SDL_CreateWindow( window_name,
798
799 #ifdef VG_DEVWINDOW
800 0, 0, vg.window_x, vg.window_y,
801 SDL_WINDOW_BORDERLESS|SDL_WINDOW_OPENGL|SDL_WINDOW_INPUT_GRABBED
802 ))){}
803 #else
804 0, 0,
805 vg.window_x, vg.window_y,
806
807 SDL_WINDOW_FULLSCREEN_DESKTOP |
808 SDL_WINDOW_OPENGL |
809 SDL_WINDOW_INPUT_GRABBED
810 )))
811 {
812 if( SDL_SetWindowDisplayMode( vg.window, &video_mode ) ){
813 vg_error( "SDL_SetWindowDisplayMode failed: %s", SDL_GetError() );
814 SDL_Quit();
815 exit(0);
816 }
817 }
818 #endif
819 else{
820 vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() );
821 exit(0);
822 }
823
824 SDL_RaiseWindow( vg.window );
825
826 vg_info( "CreateContext\n" );
827
828 /*
829 * OpenGL loading
830 */
831 if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){
832 SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y );
833 vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y );
834 }
835 else{
836 vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
837 SDL_Quit();
838 exit(0);
839 }
840
841 if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) {
842 vg_error( "Glad Failed to initialize\n" );
843 SDL_GL_DeleteContext( vg.gl_context );
844 SDL_Quit();
845 exit(0);
846 }
847
848 const unsigned char* glver = glGetString( GL_VERSION );
849 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
850
851 SDL_GL_SetSwapInterval(0); /* disable vsync while loading */
852
853 SDL_DisplayMode dispmode;
854 if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){
855 if( dispmode.refresh_rate ){
856 vg.display_refresh_rate = dispmode.refresh_rate;
857 }
858 }
859
860 if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){
861 vg.display_refresh_rate = 60;
862 }
863
864 vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate );
865
866 #ifdef _WIN32
867 vg.fps_limit = vg.display_refresh_rate;
868 #else
869 /* request vsync by default on linux to avoid screen tearing.
870 * this does have its own issues with compositing on X11. */
871 vg.fps_limit = 0;
872 #endif
873 }
874
875 VG_STATIC void vg_enter( int argc, char *argv[], const char *window_name )
876 {
877 _vg_process_launch_opts_internal( argc, argv );
878
879 /* Systems init */
880 vg_alloc_quota();
881 _vg_console_init();
882
883 vg_console_reg_var( "fps_limit", &vg.fps_limit, k_var_dtype_i32, 0 );
884 _vg_init_window( window_name );
885
886 vg_async_init();
887
888 SDL_SetRelativeMouseMode(1);
889 vg.thread_id_main = SDL_GetThreadID(NULL);
890
891 /* Opengl-required systems */
892 _vg_ui_init();
893 _vg_loader_init();
894
895 vg.engine_status = k_engine_status_running;
896
897 _vg_opengl_sync_init();
898 vg_loader_start( _vg_load_full );
899 _vg_gameloop();
900
901 /* Shutdown */
902 _vg_console_write_persistent();
903
904 SDL_AtomicLock( &vg.sl_context );
905 vg.engine_status = k_engine_status_none;
906 SDL_AtomicUnlock( &vg.sl_context );
907
908 _vg_loader_free();
909
910 vg_success( "If you see this it means everything went.. \"well\".....\n" );
911
912 SDL_GL_DeleteContext( vg.gl_context );
913 SDL_Quit();
914 }
915
916 /*
917 * Immediately transfer away from calling thread into a safe loop, signal for
918 * others to shutdown, then free everything once the user closes the window.
919 *
920 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, ONLY
921 * when calling the program from outside its normal directory.
922 */
923 VG_STATIC void vg_fatal_exit_loop( const char *error )
924 {
925 /*
926 * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
927 * thanks gnu <3
928 *
929 * TODO: this on windows?
930 */
931
932 vg_print_backtrace();
933 vg_error( "Fatal error: %s\n", error );
934
935 SDL_AtomicLock( &vg.sl_context );
936 if( vg.engine_status == k_engine_status_none ){
937 SDL_AtomicUnlock( &vg.sl_context );
938
939 /* TODO: Correct shutdown before other systems */
940 exit(0);
941 }
942 else{
943 SDL_AtomicUnlock( &vg.sl_context );
944
945 vg_acquire_thread_sync();
946
947 SDL_AtomicLock( &vg.sl_context );
948 vg.engine_status = k_engine_status_crashed;
949 vg.str_const_engine_err = error;
950 SDL_AtomicUnlock( &vg.sl_context );
951
952 /* Notify other thread for curtosey */
953 if( vg_thread_purpose() == k_thread_purpose_main ){
954 SDL_AtomicLock( &vg.sl_context );
955
956 if( vg.exec_context != 0 )
957 SDL_SemPost( vg.sem_allow_exec );
958
959 SDL_AtomicUnlock( &vg.sl_context );
960 }
961
962 vg_audio_free();
963
964 while(1){
965 _vg_process_events();
966 if( vg.window_should_close )
967 break;
968
969 if( vg_getkey( SDLK_ESCAPE ) )
970 break;
971
972 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
973 glEnable(GL_BLEND);
974 glDisable(GL_DEPTH_TEST);
975 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
976 glBlendEquation(GL_FUNC_ADD);
977
978 glClearColor( 0.15f + sinf(vg.time)*0.1f, 0.0f, 0.0f,1.0f );
979 glClear( GL_COLOR_BUFFER_BIT );
980 glViewport( 0,0, vg.window_x, vg.window_y );
981
982 _vg_render_log();
983 SDL_GL_SwapWindow( vg.window );
984 }
985
986 /* Can now shutdown and EXIT */
987 _vg_loader_free();
988 SDL_GL_DeleteContext( vg.gl_context );
989 SDL_Quit();
990 exit(0);
991 }
992 }
993
994 #else /* VG_GAME */
995
996 VG_STATIC void vg_fatal_exit_loop( const char *error )
997 {
998 vg_error( "Fatal error: %s\n", error );
999 exit(0);
1000 }
1001
1002 #endif /* VG_GAME */
1003
1004 /*
1005 * Graphic cards will check these to force it to use the GPU
1006 */
1007 u32 NvOptimusEnablement = 0x00000001;
1008 int AmdPowerXpressRequestHighPerformance = 1;
1009
1010 #endif /* VG_HEADER_H */