l
[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 #ifdef _WIN32
550 " extrap(%.2f) frame(%.2f) spin( %llu )\n",
551 #else
552 " extrap(%.2f) frame(%.2f) spin( %lu )\n",
553 #endif
554 vg.window_x, vg.window_y,
555 frame_target, (1.0f/(float)frame_target)*1000.0f,
556 vg.samples,
557 vg.fixed_iterations,
558 (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f,
559 vg.time, vg.time_delta, vg.time_rate,
560 vg.time_fixed_extrapolate, vg.time_frame_delta,
561 vg.time_spinning );
562
563 ui_text( (ui_rect){258, 4+24+12+12,0,0},perf, 1,0);
564 }
565
566 /* FIXME */
567 audio_debug_ui( vg.pv );
568 vg_ui();
569 _vg_console_draw();
570
571 ui_resolve();
572 ui_draw( NULL );
573 }
574 }
575
576 vg_profile_end( &vg_prof_render );
577 }
578
579 VG_STATIC int vg_framefilter( double dt )
580 {
581 if( (vg.fps_limit <= 0) && (vg.vsync_feature != k_vsync_feature_error) ){
582 /* turn on vsync if not enabled */
583
584 enum vsync_feature requested = k_vsync_feature_enabled;
585 if( vg.fps_limit < 0 ) requested = k_vsync_feature_enabled_adaptive;
586
587 if( vg.vsync_feature != requested ){
588 vg_info( "Setting swap interval\n" );
589
590 int swap_interval = 1;
591 if( requested == k_vsync_feature_enabled_adaptive ) swap_interval = -1;
592
593 if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){
594 if( requested == k_vsync_feature_enabled ){
595 vg_error( "Vsync is not supported by your system\n" );
596 vg_warn( "You may be overriding it in your"
597 " graphics control panel.\n" );
598 }
599 else{
600 vg_error( "Adaptive Vsync is not supported by your system\n" );
601 }
602
603 vg.vsync_feature = k_vsync_feature_error;
604 vg.fps_limit = vg.display_refresh_rate;
605
606 /* TODO: Make popup to notify user that this happened */
607 return 1;
608 }
609 else{
610 vg_success( "Vsync enabled (%d)\n", requested );
611 vg.vsync_feature = requested;
612 }
613 }
614
615 return 0;
616 }
617
618 if( vg.vsync_feature != k_vsync_feature_disabled ){
619 SDL_GL_SetSwapInterval( 0 );
620 vg.vsync_feature = k_vsync_feature_disabled;
621 }
622
623 if( vg.fps_limit < 25 ) vg.fps_limit = 25;
624 if( vg.fps_limit > 300 ) vg.fps_limit = 300;
625
626 double min_frametime = 1.0/(double)vg.fps_limit;
627 if( vg.time_frame_delta < min_frametime ){
628 /* TODO: we can use high res nanosleep on Linux here */
629 double sleep_ms = (min_frametime-vg.time_frame_delta) * 1000.0;
630 u32 ms = (u32)floor( sleep_ms );
631
632 if( ms ){
633 SDL_Delay( ms );
634 }
635 else{
636 vg.time_spinning ++;
637 }
638
639 return 1;
640 }
641
642 return 0;
643 }
644
645 VG_STATIC void _vg_gameloop(void)
646 {
647 //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f);
648
649 vg.time_hp = SDL_GetPerformanceCounter();
650 vg.time_hp_last = vg.time_hp;
651
652 int post_start = 0;
653 while(1){
654
655 vg.time_hp = SDL_GetPerformanceCounter();
656 u64 udt = vg.time_hp - vg.time_hp_last;
657 vg.time_hp_last = vg.time_hp;
658
659 double dt = (double)udt / (double)SDL_GetPerformanceFrequency();
660
661 vg.time_frame_delta += dt;
662
663 if( vg_framefilter( dt ) )
664 continue;
665
666 vg_profile_begin( &vg_prof_swap );
667 SDL_GL_SwapWindow( vg.window );
668 _vg_run_synced();
669 vg_profile_end( &vg_prof_swap );
670
671 vg.time_delta = vg.time_frame_delta * vg.time_rate;
672 vg.time += vg.time_delta;
673
674 _vg_process_events();
675
676 if( vg.window_should_close )
677 break;
678
679 if( vg.is_loaded ){
680 if( !post_start ){
681 vg_start();
682 post_start = 1;
683 }
684 }
685 else{
686 _vg_loader_render();
687 }
688
689 _vg_gameloop_update();
690 _vg_gameloop_render();
691
692 vg.time_frame_delta = 0.0;
693 vg.time_spinning = 0;
694 }
695 }
696
697 VG_STATIC void _vg_process_launch_opts_internal( int argc, char *argv[] )
698 {
699 char *arg;
700 while( vg_argp( argc, argv ) ){
701 if( (arg = vg_opt_arg( 'w' )) ){
702 vg.window_x = atoi( arg );
703 }
704
705 if( (arg = vg_opt_arg( 'h' )) ){
706 vg.window_y = atoi( arg );
707 }
708
709 if( (arg = vg_long_opt_arg( "samples" )) ){
710 vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
711 }
712
713 if( vg_long_opt( "use-libc-malloc" ) ){
714 vg_mem.use_libc_malloc = 1;
715 }
716
717 if( vg_long_opt( "high-performance" ) ){
718 vg.quality_profile = k_quality_profile_low;
719 }
720
721 vg_launch_opt();
722 }
723 }
724
725 VG_STATIC void _vg_init_window( const char *window_name )
726 {
727 vg_info( "SDL_INIT\n" );
728
729 if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){
730 vg_error( "SDL_Init failed: %s\n", SDL_GetError() );
731 exit(0);
732 }
733
734 SDL_InitSubSystem( SDL_INIT_AUDIO );
735 SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER );
736
737 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
738 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
739 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
740 SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,
741 SDL_GL_CONTEXT_PROFILE_CORE );
742
743 SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR,
744 SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH );
745
746 SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
747 SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
748 SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
749 SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
750 SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 );
751
752 /*
753 * Get monitor information
754 */
755 vg_info( "Getting display count\n" );
756 int display_count = 0,
757 display_index = 0,
758 mode_index = 0;
759
760 #ifdef VG_DEVWINDOW
761 vg.window_x = 1600;
762 vg.window_y = 1000;
763 #else
764
765 SDL_DisplayMode video_mode;
766 if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){
767 vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() );
768 SDL_Quit();
769 exit(0);
770 }
771
772 vg.display_refresh_rate = video_mode.refresh_rate;
773 vg.window_x = video_mode.w;
774 vg.window_y = video_mode.h;
775 #endif
776
777 #ifndef _WIN32
778 SDL_SetHint( "SDL_VIDEO_X11_XINERAMA", "1" );
779 SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "0" );
780 SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "0" );
781 #endif
782
783 vg_info( "CreateWindow( %d %d @%dhz )\n", vg.window_x, vg.window_y,
784 vg.display_refresh_rate );
785
786 /* TODO: Allow selecting closest video mode from launch opts */
787 if((vg.window = SDL_CreateWindow( window_name,
788
789 #ifdef VG_DEVWINDOW
790 0, 0, vg.window_x, vg.window_y,
791 SDL_WINDOW_BORDERLESS|SDL_WINDOW_OPENGL|SDL_WINDOW_INPUT_GRABBED
792 ))){}
793 #else
794 0, 0,
795 vg.window_x, vg.window_y,
796
797 SDL_WINDOW_FULLSCREEN_DESKTOP |
798 SDL_WINDOW_OPENGL |
799 SDL_WINDOW_INPUT_GRABBED
800 )))
801 {
802 if( SDL_SetWindowDisplayMode( vg.window, &video_mode ) ){
803 vg_error( "SDL_SetWindowDisplayMode failed: %s", SDL_GetError() );
804 SDL_Quit();
805 exit(0);
806 }
807 }
808 #endif
809 else{
810 vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() );
811 exit(0);
812 }
813
814 SDL_RaiseWindow( vg.window );
815
816 vg_info( "CreateContext\n" );
817
818 /*
819 * OpenGL loading
820 */
821 if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){
822 SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y );
823 vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y );
824 }
825 else{
826 vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
827 SDL_Quit();
828 exit(0);
829 }
830
831 if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) {
832 vg_error( "Glad Failed to initialize\n" );
833 SDL_GL_DeleteContext( vg.gl_context );
834 SDL_Quit();
835 exit(0);
836 }
837
838 const unsigned char* glver = glGetString( GL_VERSION );
839 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
840
841 SDL_GL_SetSwapInterval(0); /* disable vsync while loading */
842
843 SDL_DisplayMode dispmode;
844 if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){
845 if( dispmode.refresh_rate ){
846 vg.display_refresh_rate = dispmode.refresh_rate;
847 }
848 }
849
850 if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){
851 vg.display_refresh_rate = 60;
852 }
853
854 vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate );
855
856 #ifdef _WIN32
857 vg.fps_limit = vg.display_refresh_rate;
858 #else
859 /* request vsync by default on linux to avoid screen tearing.
860 * this does have its own issues with compositing on X11. */
861 vg.fps_limit = 0;
862 #endif
863 }
864
865 VG_STATIC void vg_enter( int argc, char *argv[], const char *window_name )
866 {
867 _vg_process_launch_opts_internal( argc, argv );
868
869 /* Systems init */
870 vg_alloc_quota();
871 _vg_log_init();
872 _vg_console_init();
873
874 vg_console_reg_var( "fps_limit", &vg.fps_limit, k_var_dtype_i32, 0 );
875
876 _vg_init_window( window_name );
877
878 SDL_SetRelativeMouseMode(1);
879 vg.thread_id_main = SDL_GetThreadID(NULL);
880
881 /* Opengl-required systems */
882 _vg_ui_init();
883 _vg_loader_init();
884
885 vg.engine_status = k_engine_status_running;
886
887 _vg_opengl_sync_init();
888 vg_loader_start( _vg_load_full );
889 _vg_gameloop();
890
891 /* Shutdown */
892 _vg_console_write_persistent();
893
894 SDL_AtomicLock( &vg.sl_context );
895 vg.engine_status = k_engine_status_none;
896 SDL_AtomicUnlock( &vg.sl_context );
897
898 _vg_loader_free();
899
900 vg_success( "If you see this it means everything went.. \"well\".....\n" );
901
902 SDL_GL_DeleteContext( vg.gl_context );
903 SDL_Quit();
904 }
905
906 /*
907 * Immediately transfer away from calling thread into a safe loop, signal for
908 * others to shutdown, then free everything once the user closes the window.
909 *
910 * FIXME(bug): glfwWindowShouldClose() never returns 1 in windows via wine, ONLY
911 * when calling the program from outside its normal directory.
912 */
913 VG_STATIC void vg_fatal_exit_loop( const char *error )
914 {
915 /*
916 * https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
917 * thanks gnu <3
918 *
919 * TODO: this on windows?
920 */
921
922 vg_print_backtrace();
923 vg_error( "Fatal error: %s\n", error );
924
925 SDL_AtomicLock( &vg.sl_context );
926 if( vg.engine_status == k_engine_status_none ){
927 SDL_AtomicUnlock( &vg.sl_context );
928
929 /* TODO: Correct shutdown before other systems */
930 exit(0);
931 }
932 else{
933 SDL_AtomicUnlock( &vg.sl_context );
934
935 vg_acquire_thread_sync();
936
937 SDL_AtomicLock( &vg.sl_context );
938 vg.engine_status = k_engine_status_crashed;
939 vg.str_const_engine_err = error;
940 SDL_AtomicUnlock( &vg.sl_context );
941
942 /* Notify other thread for curtosey */
943 if( vg_thread_purpose() == k_thread_purpose_main ){
944 SDL_AtomicLock( &vg.sl_context );
945
946 if( vg.exec_context != 0 )
947 SDL_SemPost( vg.sem_allow_exec );
948
949 SDL_AtomicUnlock( &vg.sl_context );
950 }
951
952 vg_audio_free();
953
954 while(1){
955 _vg_process_events();
956 if( vg.window_should_close )
957 break;
958
959 if( vg_getkey( SDLK_ESCAPE ) )
960 break;
961
962 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
963 glEnable(GL_BLEND);
964 glDisable(GL_DEPTH_TEST);
965 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
966 glBlendEquation(GL_FUNC_ADD);
967
968 glClearColor( 0.15f + sinf(vg.time)*0.1f, 0.0f, 0.0f,1.0f );
969 glClear( GL_COLOR_BUFFER_BIT );
970 glViewport( 0,0, vg.window_x, vg.window_y );
971
972 _vg_render_log();
973 SDL_GL_SwapWindow( vg.window );
974 }
975
976 /* Can now shutdown and EXIT */
977 _vg_loader_free();
978 SDL_GL_DeleteContext( vg.gl_context );
979 SDL_Quit();
980 exit(0);
981 }
982 }
983
984 #else /* VG_GAME */
985
986 VG_STATIC void vg_fatal_exit_loop( const char *error )
987 {
988 vg_error( "Fatal error: %s\n", error );
989 exit(0);
990 }
991
992 #endif /* VG_GAME */
993
994 /*
995 * Graphic cards will check these to force it to use the GPU
996 */
997 u32 NvOptimusEnablement = 0x00000001;
998 int AmdPowerXpressRequestHighPerformance = 1;
999
1000 #endif /* VG_HEADER_H */