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