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