update font to include australia title. Update other glyph mappings
[vg.git] / vg_engine.c
1 #include "vg_engine.h"
2 #include "vg_async.h"
3
4 struct vg_engine vg = {
5 .time_rate = 1.0,
6 .time_fixed_delta = VG_TIMESTEP_FIXED
7 };
8
9 #include <string.h>
10
11 enum engine_status _vg_engine_status(void)
12 {
13 SDL_AtomicLock( &vg.sl_status );
14 enum engine_status status = vg.engine_status;
15 SDL_AtomicUnlock( &vg.sl_status );
16
17 return status;
18 }
19
20 enum vg_thread_purpose vg_thread_purpose(void)
21 {
22 SDL_AtomicLock( &vg.sl_status );
23
24 if( vg.thread_id_main == SDL_GetThreadID(NULL) ){
25 SDL_AtomicUnlock( &vg.sl_status );
26 return k_thread_purpose_main;
27 }
28 else{
29 SDL_AtomicUnlock( &vg.sl_status );
30 return k_thread_purpose_loader;
31 }
32 }
33
34 static void vg_assert_thread( enum vg_thread_purpose required )
35 {
36 enum vg_thread_purpose purpose = vg_thread_purpose();
37
38 if( purpose != required ){
39 vg_fatal_error( "thread_purpose must be %u not %u\n", required, purpose );
40 }
41 }
42
43 static void _vg_opengl_sync_init(void)
44 {
45 vg.sem_loader = SDL_CreateSemaphore(1);
46 }
47
48 #include "vg_console.h"
49 #include "vg_profiler.h"
50 #ifndef VG_NO_AUDIO
51 #include "vg_audio.h"
52 #endif
53 #include "vg_shader.h"
54 #include "vg_tex.h"
55 #include "vg_input.h"
56 #include "vg_imgui.h"
57 #include "vg_lines.h"
58 #include "vg_rigidbody_view.h"
59 #include "vg_loader.h"
60 #include "vg_opt.h"
61
62 /* Diagnostic */
63 static struct vg_profile vg_prof_update = {.name="update()"},
64 vg_prof_render = {.name="render()"},
65 vg_prof_swap = {.name="swap"};
66
67 void vg_checkgl( const char *src_info )
68 {
69 int fail = 0;
70
71 GLenum err;
72 while( (err = glGetError()) != GL_NO_ERROR ){
73 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
74 fail = 1;
75 }
76
77 if( fail )
78 vg_fatal_error( "OpenGL Error" );
79 }
80
81 static void async_vg_bake_shaders( void *payload, u32 size )
82 {
83 vg_shaders_compile();
84 }
85
86 void vg_bake_shaders(void)
87 {
88 vg_console_reg_cmd( "reload_shaders", vg_shaders_live_recompile, NULL );
89 vg_async_call( async_vg_bake_shaders, NULL, 0 );
90 }
91
92 void async_internal_complete( void *payload, u32 size )
93 {
94 vg_success( "Internal async setup complete\n" );
95 SDL_AtomicLock( &vg.sl_status );
96
97 if( vg.engine_status == k_engine_status_crashed ){
98 SDL_AtomicUnlock( &vg.sl_status );
99 return;
100 }
101 else{
102 vg.engine_status = k_engine_status_running;
103 }
104
105 SDL_AtomicUnlock( &vg.sl_status );
106 }
107
108 #ifdef VG_CUSTOM_SHADERS
109 void vg_auto_shader_register(void); /* created from codegen */
110 #endif
111
112 static void _vg_load_full( void *data )
113 {
114 vg_preload();
115 vg_tex2d_replace_with_error_async( &vg.tex_missing );
116 vg_async_stall();
117 vg_ui.tex_bg = vg.tex_missing;
118
119 /* internal */
120 vg_loader_step( vg_input_init, vg_input_free );
121 vg_loader_step( vg_lines_init, NULL );
122 vg_loader_step( vg_rb_view_init, NULL );
123 #ifndef VG_NO_AUDIO
124 vg_loader_step( vg_audio_init, vg_audio_free );
125 #endif
126 vg_loader_step( vg_profiler_init, NULL );
127
128 /* client */
129 #ifdef VG_CUSTOM_SHADERS
130 vg_auto_shader_register();
131 #endif
132 vg_load();
133
134 vg_async_call( async_internal_complete, NULL, 0 );
135
136 vg_success( "Client loaded in %fs\n", vg.time_real );
137 }
138
139 static void _vg_process_events(void)
140 {
141 v2_zero( vg.mouse_wheel );
142 v2_zero( vg.mouse_delta );
143
144 /* Update input */
145 vg_process_inputs();
146
147 /* SDL event loop */
148 SDL_Event event;
149 while( SDL_PollEvent( &event ) ){
150 if( event.type == SDL_KEYDOWN ){
151 if( vg_console.enabled &&
152 (vg_ui.focused_control_type != k_ui_control_modal) ){
153 if( event.key.keysym.sym == SDLK_ESCAPE ||
154 event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){
155 vg_console.enabled = 0;
156 ui_defocus_all();
157 }
158 else if( (event.key.keysym.mod & KMOD_CTRL) &&
159 event.key.keysym.sym == SDLK_n ){
160 console_suggest_next();
161 }
162 else if( (event.key.keysym.mod & KMOD_CTRL ) &&
163 event.key.keysym.sym == SDLK_p ){
164 console_suggest_prev();
165 }
166 else{
167 ui_proc_key( event.key.keysym );
168 }
169 }
170 else{
171 if( event.key.keysym.scancode == SDL_SCANCODE_GRAVE ){
172 vg_console.enabled = 1;
173 }
174 else {
175 ui_proc_key( event.key.keysym );
176 }
177 }
178 }
179 else if( event.type == SDL_MOUSEWHEEL ){
180 vg.mouse_wheel[0] += event.wheel.preciseX;
181 vg.mouse_wheel[1] += event.wheel.preciseY;
182 }
183 else if( event.type == SDL_CONTROLLERDEVICEADDED ||
184 event.type == SDL_CONTROLLERDEVICEREMOVED )
185 {
186 vg_input_device_event( &event );
187 }
188 else if( event.type == SDL_CONTROLLERAXISMOTION ||
189 event.type == SDL_CONTROLLERBUTTONDOWN ||
190 event.type == SDL_CONTROLLERBUTTONUP )
191 {
192 vg_input_controller_event( &event );
193 }
194 else if( event.type == SDL_MOUSEMOTION ){
195 vg.mouse_delta[0] += event.motion.xrel;
196 vg.mouse_delta[1] += event.motion.yrel;
197 }
198 else if( event.type == SDL_WINDOWEVENT ){
199 if( event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ){
200 int w, h;
201 SDL_GL_GetDrawableSize( vg.window, &w, &h );
202
203 if( !w || !h ){
204 vg_warn( "Got a invalid framebuffer size: "
205 "%dx%d... ignoring\n", w, h );
206 }
207 else{
208 vg.window_x = w;
209 vg.window_y = h;
210
211 vg_framebuffer_resize(w,h);
212 }
213 }
214 else if( event.window.event == SDL_WINDOWEVENT_CLOSE ){
215 vg.window_should_close = 1;
216 }
217 }
218 else if( event.type == SDL_TEXTINPUT ){
219 ui_proc_utf8( event.text.text );
220 }
221 }
222
223 SDL_GetMouseState( &vg.mouse_pos[0], &vg.mouse_pos[1] );
224 }
225
226 static void _vg_gameloop_update(void)
227 {
228 vg_profile_begin( &vg_prof_update );
229
230 vg.engine_stage = k_engine_stage_update;
231 vg_pre_update();
232
233 /* Fixed update loop */
234 vg.engine_stage = k_engine_stage_update_fixed;
235
236 vg.fixed_iterations = 0;
237 vg_lines.enabled = vg_lines.render;
238 vg.time_fixed_accumulator += vg.time_delta;
239
240 while( vg.time_fixed_accumulator >= vg.time_fixed_delta ){
241 vg_fixed_update();
242 vg_lines.enabled = 0;
243 vg.time_fixed_accumulator -= vg.time_fixed_delta;
244
245 vg.fixed_iterations ++;
246 if( vg.fixed_iterations == 8 ){
247 break;
248 }
249 }
250 vg_lines.enabled = vg_lines.render;
251 vg.time_fixed_extrapolate = vg.time_fixed_accumulator / vg.time_fixed_delta;
252
253 vg.engine_stage = k_engine_stage_update;
254 vg_post_update();
255 vg_profile_end( &vg_prof_update );
256 }
257
258 static void vg_settings_gui(void);
259 static void _vg_gameloop_render(void)
260 {
261 vg_profile_begin( &vg_prof_render );
262
263 /* render */
264 vg.engine_stage = k_engine_stage_rendering;
265 vg_render();
266
267 vg_profile_end( &vg_prof_render );
268
269 /* ui */
270 vg.engine_stage = k_engine_stage_ui;
271 {
272 ui_prerender();
273 if( vg_console.enabled ){
274 vg_ui.ignore_input_frames = 10;
275 vg_gui();
276 vg_ui.ignore_input_frames = 0;
277 vg_ui.wants_mouse = 1;
278 vg_console_draw();
279 }
280 else vg_gui();
281
282 if( vg.settings_open )
283 vg_settings_gui();
284
285 /* vg tools */
286 #ifndef VG_NO_AUDIO
287 audio_debug_ui( vg.pv );
288 #endif
289
290 /* profiling */
291 if( vg_profiler ){
292 int frame_target = vg.display_refresh_rate;
293 if( vg.fps_limit > 0 ) frame_target = vg.fps_limit;
294 vg_profile_drawn(
295 (struct vg_profile *[]){
296 &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3,
297 (1.0f/(float)frame_target)*1000.0f,
298 (ui_rect){ 4, 4, 250, 0 }, 0, 0
299 );
300 char perf[256];
301
302 snprintf( perf, 255,
303 "x: %d y: %d\n"
304 "refresh: %d (%.1fms)\n"
305 "samples: %d\n"
306 "iterations: %d (acc: %.3fms%%)\n"
307 "time: real(%.2f) delta(%.2f) rate(%.2f)\n"
308 " extrap(%.2f) frame(%.2f) spin( "PRINTF_U64" )\n",
309 vg.window_x, vg.window_y,
310 frame_target, (1.0f/(float)frame_target)*1000.0f,
311 vg.samples,
312 vg.fixed_iterations,
313 (vg.time_fixed_accumulator/VG_TIMESTEP_FIXED)*100.0f,
314 vg.time_real, vg.time_delta, vg.time_rate,
315 vg.time_fixed_extrapolate, vg.time_frame_delta,
316 vg.time_spinning );
317
318 ui_text( (ui_rect){258,4,900,900},perf,1,0,k_ui_align_left);
319 }
320 ui_postrender();
321 }
322 }
323
324 static void vg_changevsync(void){
325 if( vg.vsync && (vg.vsync_feature != k_vsync_feature_error) ){
326 /* turn on vsync if not enabled */
327
328 enum vsync_feature requested = k_vsync_feature_enabled;
329 if( vg.vsync < 0 ) requested = k_vsync_feature_enabled_adaptive;
330
331 if( vg.vsync_feature != requested ){
332 vg_info( "Setting swap interval\n" );
333
334 int swap_interval = 1;
335 if( requested == k_vsync_feature_enabled_adaptive )
336 swap_interval = -1;
337
338 if( SDL_GL_SetSwapInterval( swap_interval ) == -1 ){
339 if( requested == k_vsync_feature_enabled ){
340 vg_error( "Vsync is not supported by your system\n" );
341 vg_warn( "You may be overriding it in your"
342 " graphics control panel.\n" );
343 }
344 else{
345 vg_error( "Adaptive Vsync is not supported by your system\n" );
346 }
347
348 vg.vsync_feature = k_vsync_feature_error;
349 vg.vsync = 0;
350 /* TODO: Make popup to notify user that this happened */
351 }
352 else{
353 vg_success( "Vsync enabled (%d)\n", requested );
354 vg.vsync_feature = requested;
355 }
356 }
357 }
358 else {
359 if( vg.vsync_feature != k_vsync_feature_disabled ){
360 SDL_GL_SetSwapInterval( 0 );
361 vg.vsync_feature = k_vsync_feature_disabled;
362 }
363 }
364 }
365
366 static int vg_framefilter( double dt ){
367 if( vg.fps_limit < 24 ) vg.fps_limit = 24;
368 if( vg.fps_limit > 300 ) vg.fps_limit = 300;
369
370 double min_frametime = 1.0/(double)vg.fps_limit;
371 if( vg.time_frame_delta < min_frametime ){
372 /* TODO: we can use high res nanosleep on Linux here */
373 double sleep_ms = (min_frametime-vg.time_frame_delta) * 1000.0;
374 u32 ms = (u32)floor( sleep_ms );
375
376 if( ms ){
377 if( !vg_loader_availible() )
378 SDL_Delay(1);
379 else
380 SDL_Delay(ms);
381 }
382 else{
383 vg.time_spinning ++;
384 }
385
386 return 1;
387 }
388
389 return 0;
390 }
391
392 static int _vg_crashscreen(void)
393 {
394 #if 0
395 if( vg_getkey( SDLK_ESCAPE ) )
396 return 1;
397 #endif
398
399 glBindFramebuffer( GL_FRAMEBUFFER, 0 );
400 glEnable(GL_BLEND);
401 glDisable(GL_DEPTH_TEST);
402 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
403 glBlendEquation(GL_FUNC_ADD);
404
405 glClearColor( 0.15f + sinf(vg.time_real)*0.1f, 0.0f, 0.0f,1.0f );
406 glClear( GL_COLOR_BUFFER_BIT );
407 glViewport( 0,0, vg.window_x, vg.window_y );
408
409 #if 0
410 _vg_render_log();
411 #endif
412
413 return 0;
414 }
415
416 static void _vg_gameloop(void){
417 //vg.time_fixed_accumulator = 0.75f * (1.0f/60.0f);
418
419 vg.time_hp = SDL_GetPerformanceCounter();
420 vg.time_hp_last = vg.time_hp;
421
422 int post_start = 0;
423 while(1){
424 vg.time_hp = SDL_GetPerformanceCounter();
425 u64 udt = vg.time_hp - vg.time_hp_last;
426 vg.time_hp_last = vg.time_hp;
427
428 double dt = (double)udt / (double)SDL_GetPerformanceFrequency();
429
430 vg.time_frame_delta += dt;
431 vg_run_async_checked();
432
433 if( vg_framefilter( dt ) )
434 continue;
435
436 vg_changevsync();
437
438 enum engine_status status = _vg_engine_status();
439 if( status == k_engine_status_running )
440 vg_profile_begin( &vg_prof_swap );
441
442 SDL_GL_SwapWindow( vg.window );
443
444 if( status == k_engine_status_running )
445 vg_profile_end( &vg_prof_swap );
446
447 vg.time_real += vg.time_frame_delta;
448 vg.time_delta = vg.time_frame_delta * vg.time_rate;
449 vg.time += vg.time_delta;
450
451 _vg_process_events();
452
453 if( vg.window_should_close )
454 break;
455
456 if( status == k_engine_status_crashed ){
457 if( _vg_crashscreen() )
458 break;
459 }
460 else{
461 if( status == k_engine_status_running ){
462 _vg_gameloop_update();
463 _vg_gameloop_render();
464 }
465 else{
466 vg_loader_render();
467 }
468 }
469
470 if( vg.loader_ring > 0.01f ){
471 vg_loader_render_ring( vg.loader_ring );
472 vg.loader_ring -= vg.time_frame_delta * 0.5f;
473 }
474
475 vg.time_frame_delta = 0.0;
476 vg.time_spinning = 0;
477 }
478 }
479
480 static void _vg_process_launch_opts_internal( int argc, char *argv[] )
481 {
482 char *arg;
483 while( vg_argp( argc, argv ) ){
484 if( (arg = vg_opt_arg( 'w' )) ){
485 vg.window_x = atoi( arg );
486 }
487
488 if( (arg = vg_opt_arg( 'h' )) ){
489 vg.window_y = atoi( arg );
490 }
491
492 if( (arg = vg_long_opt_arg( "samples" )) ){
493 vg.samples = VG_MAX( 0, VG_MIN( 8, atoi( arg ) ) );
494 }
495
496 if( vg_long_opt( "use-libc-malloc" ) ){
497 vg_mem.use_libc_malloc = 1;
498 }
499
500 if( vg_long_opt( "high-performance" ) ){
501 vg.quality_profile = k_quality_profile_low;
502 }
503
504 vg_launch_opt();
505 }
506 }
507
508 static void _vg_init_window( const char *window_name )
509 {
510 vg_info( "SDL_INIT\n" );
511
512 if( SDL_Init( SDL_INIT_VIDEO ) != 0 ){
513 vg_error( "SDL_Init failed: %s\n", SDL_GetError() );
514 exit(0);
515 }
516
517 #ifndef VG_NO_AUDIO
518 SDL_InitSubSystem( SDL_INIT_AUDIO );
519 #endif
520 SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER );
521
522 char *exe_basepath = SDL_GetBasePath();
523 u32 len = vg_align8( strlen(exe_basepath)+1 );
524 char *dest = vg_linear_alloc( vg_mem.rtmemory, len );
525 strcpy( dest, exe_basepath );
526 SDL_free( exe_basepath );
527 vg.base_path = dest;
528
529 vg_info( "Basepath: %s\n", vg.base_path );
530
531 SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
532 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
533 SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
534 SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK,
535 SDL_GL_CONTEXT_PROFILE_CORE );
536
537 SDL_GL_SetAttribute( SDL_GL_CONTEXT_RELEASE_BEHAVIOR,
538 SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH );
539
540 SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
541 SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
542 SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
543 SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
544 SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0 );
545
546 /*
547 * Get monitor information
548 */
549 vg_info( "Getting display count\n" );
550 int display_count = 0,
551 display_index = 0,
552 mode_index = 0;
553
554 SDL_DisplayMode video_mode;
555 if( SDL_GetDesktopDisplayMode( display_index, &video_mode ) ){
556 vg_error( "SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError() );
557 SDL_Quit();
558 exit(0);
559 }
560
561 vg.display_refresh_rate = video_mode.refresh_rate;
562 vg.window_x = video_mode.w;
563 vg.window_y = video_mode.h;
564
565 if( vg.screen_mode == 2 ){
566 vg.window_x = 1280;
567 vg.window_y = 720;
568 }
569
570 #ifndef _WIN32
571 SDL_SetHint( "SDL_VIDEO_X11_XINERAMA", "1" );
572 SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "0" );
573 SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "0" );
574 #endif
575
576 u32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_INPUT_GRABBED |
577 SDL_WINDOW_RESIZABLE;
578
579 if( vg.screen_mode == 1 )
580 flags |= SDL_WINDOW_FULLSCREEN;
581 else if( vg.screen_mode == 0 )
582 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
583
584 vg_info( "CreateWindow( %d %d %u )\n", vg.window_x, vg.window_y, flags );
585
586 if((vg.window = SDL_CreateWindow( window_name, 0, 0,
587 vg.window_x, vg.window_y, flags ))){
588 if( vg.screen_mode == 2 )
589 SDL_SetWindowPosition( vg.window, video_mode.w-vg.window_x, 0 );
590 }
591 else{
592 vg_error( "SDL_CreateWindow failed: %s", SDL_GetError() );
593 exit(0);
594 }
595
596 SDL_RaiseWindow( vg.window );
597 SDL_SetWindowMinimumSize( vg.window, 1280, 720 );
598 SDL_SetWindowMaximumSize( vg.window, 4096, 4096 );
599
600 vg_info( "CreateContext\n" );
601
602 /* ????? */
603 if( SDL_IsTextInputActive() ) SDL_StopTextInput();
604
605 /*
606 * OpenGL loading
607 */
608 if( (vg.gl_context = SDL_GL_CreateContext(vg.window) )){
609 SDL_GL_GetDrawableSize( vg.window, &vg.window_x, &vg.window_y );
610 vg_success( "Window created (%dx%d)\n", vg.window_x, vg.window_y );
611 }
612 else{
613 vg_error( "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
614 SDL_Quit();
615 exit(0);
616 }
617
618 if( !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress) ) {
619 vg_error( "Glad Failed to initialize\n" );
620 SDL_GL_DeleteContext( vg.gl_context );
621 SDL_Quit();
622 exit(0);
623 }
624
625 const unsigned char* glver = glGetString( GL_VERSION );
626 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
627
628 SDL_GL_SetSwapInterval(0); /* disable vsync while loading */
629
630 SDL_DisplayMode dispmode;
631 if( !SDL_GetWindowDisplayMode( vg.window, &dispmode ) ){
632 if( dispmode.refresh_rate ){
633 vg.display_refresh_rate = dispmode.refresh_rate;
634 }
635 }
636
637 if( vg.display_refresh_rate < 25 || vg.display_refresh_rate > 300 ){
638 vg.display_refresh_rate = 60;
639 }
640
641 vg_info( "Display refresh rate: %d\n", dispmode.refresh_rate );
642 if( !vg.fps_limit) vg.fps_limit = vg.display_refresh_rate;
643 }
644
645 static void _vg_terminate(void)
646 {
647 /* Shutdown */
648 vg_console_write_persistent();
649
650 SDL_AtomicLock( &vg.sl_status );
651 vg.engine_status = k_engine_status_none;
652 SDL_AtomicUnlock( &vg.sl_status );
653
654 vg_loader_free();
655
656 vg_success( "If you see this it means everything went.. \"well\".....\n" );
657
658 SDL_GL_DeleteContext( vg.gl_context );
659 SDL_Quit();
660 exit(0);
661 }
662
663 static int cmd_vg_settings_toggle( int argc, const char *argv[] );
664 void vg_enter( int argc, char *argv[], const char *window_name )
665 {
666 vg_rand_seed( &vg.rand, 461 );
667 _vg_process_launch_opts_internal( argc, argv );
668
669 /* Systems init */
670 vg_alloc_quota();
671 vg_console_init();
672
673 vg_console_reg_var( "vg_fps_limit", &vg.fps_limit,
674 k_var_dtype_i32, VG_VAR_PERSISTENT );
675 vg_console_reg_var( "vg_vsync", &vg.vsync,
676 k_var_dtype_i32, VG_VAR_PERSISTENT );
677 vg_console_reg_var( "vg_quality", &vg.quality_profile,
678 k_var_dtype_i32, VG_VAR_PERSISTENT );
679 vg_console_reg_var( "vg_screen_mode", &vg.screen_mode,
680 k_var_dtype_i32, VG_VAR_PERSISTENT );
681 vg_audio_register();
682 vg_console_load_autos();
683
684 vg_console_reg_cmd( "vg_settings", cmd_vg_settings_toggle, NULL );
685 _vg_init_window( window_name );
686
687 vg_async_init();
688 SDL_SetRelativeMouseMode(1);
689
690 vg.thread_id_main = SDL_GetThreadID(NULL);
691
692 /* Opengl-required systems */
693 vg_ui_init();
694 vg_loader_init();
695
696 vg.engine_status = k_engine_status_load_internal;
697
698 _vg_opengl_sync_init();
699 vg_loader_start( _vg_load_full, NULL );
700 _vg_gameloop();
701 _vg_terminate();
702 }
703
704 void vg_fatal_error( const char *fmt, ... )
705 {
706 va_list args;
707 va_start( args, fmt );
708 _vg_logx_va( stderr, NULL, "fatal", KRED, fmt, args );
709 va_end( args );
710
711 vg_print_backtrace();
712
713 SDL_AtomicLock( &vg.sl_status );
714 vg.engine_status = k_engine_status_crashed;
715 SDL_AtomicUnlock( &vg.sl_status );
716
717 if( vg_thread_purpose() == k_thread_purpose_loader )
718 {
719 longjmp( vg.env_loader_exit, 1 );
720 }
721 else
722 {
723 vg_error( "There is no jump to the error runner thing yet! bai bai\n" );
724 _vg_terminate();
725 }
726 }
727
728 /*
729 * settings menu
730 * ---------------------------------------------------------------------------
731 */
732
733 #ifdef VG_GAME_SETTINGS
734 extern void vg_game_settings_gui( ui_rect panel ) ;
735 extern void vg_game_settings_init(void);
736 #endif
737
738 struct ui_enum_opt vg_settings_vsync_enum[] = {
739 { 0, "None" },
740 { 1, "On" },
741 {-1, "Adaptive" },
742 };
743
744 struct ui_enum_opt vg_settings_quality_enum[] = {
745 { 0, "High Quality" },
746 { 1, "Faster" },
747 { 2, "Absolute Minimum" },
748 };
749
750 struct ui_enum_opt vg_settings_screen_mode_enum[] = {
751 { 0, "Fullscreen (desktop)" },
752 { 1, "Fullscreen (native)" },
753 { 2, "Floating Window" }
754 };
755
756 struct ui_enum_opt vg_settings_dsp_enum[] = {
757 { 1, "Enabled" },
758 { 0, "Disabled" },
759 };
760
761 struct {
762 struct vg_setting_ranged_i32 fps_limit;
763 struct vg_setting_enum vsync, quality, screenmode, audio_devices, dsp;
764 i32 temp_audio_choice;
765 }
766 static vg_settings = {
767 .fps_limit = { .label = "Fps Limit",
768 .min=24, .max=300, .actual_value = &vg.fps_limit },
769 .vsync = { .label = "Vsync",
770 .actual_value = &vg.vsync,
771 .options = vg_settings_vsync_enum, .option_count = 3 },
772 .quality = { .label = "Graphic Quality",
773 .actual_value = &vg.quality_profile,
774 .options = vg_settings_quality_enum, .option_count = 3 },
775 .screenmode = { .label = "Type",
776 .actual_value = &vg.screen_mode,
777 .options = vg_settings_screen_mode_enum, .option_count=3 },
778 .audio_devices = { .label = "Audio Device",
779 .actual_value = &vg_settings.temp_audio_choice,
780 .options = NULL, .option_count = 0 },
781 .dsp = { .label = "Audio effects (reverb etc.)",
782 .actual_value = &vg_audio.dsp_enabled,
783 .options = vg_settings_dsp_enum, .option_count=2 },
784 };
785
786 static void vg_settings_ui_draw_diff( ui_rect orig ){
787 ui_rect l,r;
788 ui_split( orig, k_ui_axis_v, -32, 0, l, r );
789 ui_text( r, "*", 1, k_ui_align_middle_center, ui_colour(k_ui_blue) );
790 }
791
792 /* i32 settings
793 * ------------------------------------------------------------------------- */
794
795 static void vg_settings_ui_int( char *buf, u32 len ){
796 for( u32 i=0, j=0; i<len; i ++ ){
797 if( ((buf[i] >= '0') && (buf[i] <= '9')) || (buf[i] == '\0') )
798 buf[j ++] = buf[i];
799 }
800 }
801
802 struct ui_textbox_callbacks static vg_settings_ui_int_callbacks = {
803 .change = vg_settings_ui_int
804 };
805
806 static bool vg_settings_ranged_i32_valid( struct vg_setting_ranged_i32 *prop ){
807 if( prop->new_value < prop->min ) return 0;
808 if( prop->new_value > prop->max ) return 0;
809 return 1;
810 }
811
812 static bool vg_settings_ranged_i32_diff( struct vg_setting_ranged_i32 *prop ){
813 if( prop->new_value != *prop->actual_value ) return 1;
814 else return 0;
815 }
816
817 static bool vg_settings_ui_ranged_i32( struct vg_setting_ranged_i32 *prop,
818 ui_rect rect ){
819 ui_rect orig;
820 rect_copy( rect, orig );
821
822 ui_textbox( rect, prop->label, prop->buf, sizeof(prop->buf),
823 1, 0, &vg_settings_ui_int_callbacks );
824 prop->new_value = atoi( prop->buf );
825
826 if( vg_settings_ranged_i32_diff( prop ) )
827 vg_settings_ui_draw_diff( orig );
828
829 bool valid = vg_settings_ranged_i32_valid( prop );
830 if( !valid ){
831 ui_rect _null, line;
832 ui_split( orig, k_ui_axis_h, -1, 0, _null, line );
833 line[1] += 3;
834
835 ui_fill( line, ui_colour( k_ui_red ) );
836 }
837
838 return valid;
839 }
840
841 void ui_settings_ranged_i32_init( struct vg_setting_ranged_i32 *prop )
842 {
843 vg_str tmp;
844 vg_strnull( &tmp, prop->buf, sizeof(prop->buf) );
845 vg_strcati32( &tmp, *prop->actual_value );
846 prop->new_value = *prop->actual_value;
847 }
848
849 /* enum settings
850 * ------------------------------------------------------------------------- */
851
852 bool vg_settings_enum_diff( struct vg_setting_enum *prop )
853 {
854 if( prop->new_value != *prop->actual_value ) return 1;
855 else return 0;
856 }
857
858 bool vg_settings_enum( struct vg_setting_enum *prop, ui_rect rect )
859 {
860 ui_rect orig;
861 rect_copy( rect, orig );
862
863 ui_enum( rect, prop->label,
864 prop->options, prop->option_count, &prop->new_value );
865
866 if( vg_settings_enum_diff( prop ) )
867 vg_settings_ui_draw_diff( orig );
868
869 return 1;
870 }
871
872 void ui_settings_enum_init( struct vg_setting_enum *prop )
873 {
874 prop->new_value = *prop->actual_value;
875 }
876
877 /* .. */
878
879 void vg_settings_ui_header( ui_rect inout_panel, const char *name )
880 {
881 ui_rect rect;
882 ui_standard_widget( inout_panel, rect, 2 );
883 ui_text( rect, name, 1, k_ui_align_middle_center, ui_colour(k_ui_fg+3) );
884 }
885
886
887 bool vg_settings_apply_button( ui_rect inout_panel, bool validated )
888 {
889 ui_rect last_row;
890 ui_px height = (vg_ui.font->sy + 18) * k_ui_scale;
891 ui_split( inout_panel, k_ui_axis_h, -height, k_ui_padding,
892 inout_panel, last_row );
893
894 const char *string = "Apply";
895 if( validated ){
896 if( ui_button( last_row, string ) == 1 )
897 return 1;
898 }
899 else{
900 ui_rect rect;
901 ui_standard_widget( last_row, rect, 1 );
902 ui_fill( rect, ui_colour( k_ui_bg+1 ) );
903 ui_outline( rect, -1, ui_colour( k_ui_red ), 0 );
904
905 ui_rect t = { 0,0, ui_text_line_width( string ), 14 };
906 ui_rect_center( rect, t );
907 ui_text( t, string, 1, k_ui_align_left, ui_colour(k_ui_fg+3) );
908 }
909
910 return 0;
911 }
912
913 static void vg_settings_video_apply(void){
914 if( vg_settings_enum_diff( &vg_settings.screenmode ) ){
915 vg.screen_mode = vg_settings.screenmode.new_value;
916
917 if( (vg.screen_mode == 0) || (vg.screen_mode == 1) ){
918 SDL_DisplayMode video_mode;
919 if( SDL_GetDesktopDisplayMode( 0, &video_mode ) ){
920 vg_error("SDL_GetDesktopDisplayMode failed: %s\n", SDL_GetError());
921 }
922 else {
923 //vg.display_refresh_rate = video_mode.refresh_rate;
924 vg.window_x = video_mode.w;
925 vg.window_y = video_mode.h;
926 }
927 SDL_SetWindowSize( vg.window, vg.window_x, vg.window_y );
928 }
929
930 if( vg.screen_mode == 0 )
931 SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN_DESKTOP );
932 if( vg.screen_mode == 1 )
933 SDL_SetWindowFullscreen( vg.window, SDL_WINDOW_FULLSCREEN );
934 if( vg.screen_mode == 2 ){
935 SDL_SetWindowFullscreen( vg.window, 0 );
936 SDL_SetWindowSize( vg.window, 1280, 720 );
937 SDL_SetWindowPosition( vg.window, 16, 16 );
938 SDL_SetWindowMinimumSize( vg.window, 1280, 720 );
939 SDL_SetWindowMaximumSize( vg.window, 4096, 4096 );
940 }
941 }
942
943 vg.fps_limit = vg_settings.fps_limit.new_value;
944 vg.quality_profile = vg_settings.quality.new_value;
945 vg.vsync = vg_settings.vsync.new_value;
946 }
947
948 static void vg_settings_video_gui( ui_rect panel ){
949 bool validated = 1;
950 ui_rect rq;
951 ui_standard_widget( panel, rq, 1 );
952 vg_settings_enum( &vg_settings.quality, rq );
953
954 /* FIXME */
955 #if 0
956 if( vg.vsync_feature == k_vsync_feature_error ){
957 ui_info( panel, "There was an error activating vsync feature." );
958 }
959 #endif
960
961 /* frame timing */
962 vg_settings_ui_header( panel, "Frame Timing" );
963 ui_rect duo, d0,d1;
964 ui_standard_widget( panel, duo, 1 );
965 ui_split_ratio( duo, k_ui_axis_v, 0.5f, 16, d0, d1 );
966
967 vg_settings_enum( &vg_settings.vsync, d0 );
968 validated &= vg_settings_ui_ranged_i32( &vg_settings.fps_limit, d1 );
969
970 /* profiler */
971 ui_standard_widget( panel, duo, 10 );
972 int frame_target = vg.display_refresh_rate;
973 if( !vg.vsync ) frame_target = vg.fps_limit;
974 vg_profile_drawn(
975 (struct vg_profile *[]){
976 &vg_prof_update,&vg_prof_render,&vg_prof_swap}, 3,
977 (1.0f/(f32)frame_target)*1500.0f,
978 duo, 1, 0
979 );
980
981 ui_fill( (ui_rect){ duo[0], duo[1]+(duo[3]*2)/3, duo[2], 1 },
982 ui_colour(k_ui_fg) );
983
984 /* window spec */
985 vg_settings_ui_header( panel, "Window Specification" );
986
987 ui_standard_widget( panel, duo, 1 );
988 vg_settings_enum( &vg_settings.screenmode, duo );
989
990 if( vg_settings_apply_button( panel, validated ) )
991 vg_settings_video_apply();
992 }
993
994 static void vg_settings_audio_apply(void){
995 if( vg_settings_enum_diff( &vg_settings.audio_devices ) ){
996 if( vg_audio.sdl_output_device ){
997 vg_info( "Closing audio device %d\n", vg_audio.sdl_output_device );
998 SDL_CloseAudioDevice( vg_audio.sdl_output_device );
999 }
1000
1001 vg_strfree( &vg_audio.device_choice );
1002
1003 if( vg_settings.audio_devices.new_value == -1 ){ }
1004 else if( vg_settings.audio_devices.new_value == -2 ){
1005 vg_fatal_error( "Programming error\n" );
1006 }
1007 else {
1008 struct ui_enum_opt *selected = NULL, *oi;
1009
1010 for( int i=0; i<vg_settings.audio_devices.option_count; i ++ ){
1011 oi = &vg_settings.audio_devices.options[i];
1012
1013 if( oi->value == vg_settings.audio_devices.new_value ){
1014 selected = oi;
1015 break;
1016 }
1017 }
1018
1019 vg_strnull( &vg_audio.device_choice, NULL, -1 );
1020 vg_strcat( &vg_audio.device_choice, oi->alias );
1021 }
1022
1023 vg_audio_device_init();
1024 *vg_settings.audio_devices.actual_value =
1025 vg_settings.audio_devices.new_value;
1026 }
1027
1028 audio_lock();
1029 if( vg_settings_enum_diff( &vg_settings.dsp ) ){
1030 *vg_settings.dsp.actual_value =
1031 vg_settings.dsp.new_value;
1032 }
1033
1034 audio_unlock();
1035 }
1036
1037 static void vg_settings_audio_gui( ui_rect panel ){
1038 ui_rect rq;
1039 ui_standard_widget( panel, rq, 1 );
1040 vg_settings_enum( &vg_settings.audio_devices, rq );
1041
1042 ui_standard_widget( panel, rq, 1 );
1043 vg_settings_enum( &vg_settings.dsp, rq );
1044
1045 if( vg_settings_apply_button( panel, 1 ) )
1046 vg_settings_audio_apply();
1047 }
1048
1049 void vg_settings_open(void)
1050 {
1051 vg.settings_open = 1;
1052
1053 ui_settings_ranged_i32_init( &vg_settings.fps_limit );
1054 ui_settings_enum_init( &vg_settings.vsync );
1055 ui_settings_enum_init( &vg_settings.quality );
1056 ui_settings_enum_init( &vg_settings.screenmode );
1057
1058 /* Create audio options */
1059 int count = SDL_GetNumAudioDevices( 0 );
1060
1061 struct ui_enum_opt *options = malloc( sizeof(struct ui_enum_opt)*(count+1) );
1062 vg_settings.audio_devices.options = options;
1063 vg_settings.audio_devices.option_count = count+1;
1064
1065 struct ui_enum_opt *o0 = &options[0];
1066 o0->alias = "OS Default";
1067 o0->value = -1;
1068
1069 for( int i=0; i<count; i ++ ){
1070 struct ui_enum_opt *oi = &options[i+1];
1071
1072 const char *device_name = SDL_GetAudioDeviceName( i, 0 );
1073 int len = strlen(device_name);
1074
1075 oi->alias = malloc( len+1 );
1076 memcpy( (void *)oi->alias, device_name, len+1 );
1077 oi->value = i;
1078 }
1079
1080 if( vg_audio.device_choice.buffer ){
1081 vg_settings.temp_audio_choice = -2;
1082
1083 for( int i=0; i<count; i ++ ){
1084 struct ui_enum_opt *oi = &options[i+1];
1085 if( !strcmp( oi->alias, vg_audio.device_choice.buffer ) ){
1086 vg_settings.temp_audio_choice = oi->value;
1087 break;
1088 }
1089 }
1090 }
1091 else {
1092 vg_settings.temp_audio_choice = -1;
1093 }
1094
1095 ui_settings_enum_init( &vg_settings.audio_devices );
1096 ui_settings_enum_init( &vg_settings.dsp );
1097
1098 #ifdef VG_GAME_SETTINGS
1099 vg_game_settings_init();
1100 #endif
1101 }
1102
1103 void vg_settings_close(void)
1104 {
1105 vg.settings_open = 0;
1106
1107 struct ui_enum_opt *options = vg_settings.audio_devices.options;
1108 for( int i=1; i < vg_settings.audio_devices.option_count; i ++ )
1109 free( (void *)options[i].alias );
1110 free( vg_settings.audio_devices.options );
1111 }
1112
1113 static void vg_settings_gui(void)
1114 {
1115 ui_rect null;
1116 ui_rect screen = { 0, 0, vg.window_x, vg.window_y };
1117 ui_rect window = { 0, 0, 1000, 700 };
1118 ui_rect_center( screen, window );
1119 vg_ui.wants_mouse = 1;
1120
1121 ui_fill( window, ui_colour( k_ui_bg+1 ) );
1122 ui_outline( window, 1, ui_colour( k_ui_bg+7 ), 0 );
1123
1124 ui_rect title, panel;
1125 ui_split( window, k_ui_axis_h, 28, 0, title, panel );
1126 ui_fill( title, ui_colour( k_ui_bg+7 ) );
1127 ui_text( title, "Settings", 1, k_ui_align_middle_center,
1128 ui_colourcont(k_ui_bg+7) );
1129
1130 ui_rect quit_button;
1131 ui_split( title, k_ui_axis_v, title[2]-title[3], 2, title, quit_button );
1132
1133 if( ui_button_text( quit_button, "X", 1 ) == k_ui_button_click )
1134 {
1135 vg_settings_close();
1136 return;
1137 }
1138
1139 ui_rect_pad( panel, (ui_px[2]){ 8, 8 } );
1140
1141 const char *opts[] = { "video", "audio",
1142 #ifdef VG_GAME_SETTINGS
1143 "game"
1144 #endif
1145 };
1146
1147 static i32 page = 0;
1148 ui_tabs( panel, panel, opts, vg_list_size(opts), &page );
1149
1150 if( page == 0 ){
1151 vg_settings_video_gui( panel );
1152 }
1153 else if( page == 1 )
1154 vg_settings_audio_gui( panel );
1155
1156 #ifdef VG_GAME_SETTINGS
1157 else if( page == 2 )
1158 vg_game_settings_gui( panel );
1159 #endif
1160 }
1161
1162 static int cmd_vg_settings_toggle( int argc, const char *argv[] ){
1163 vg_settings_open();
1164 return 0;
1165 }
1166
1167 /*
1168 * Graphic cards will check these to force it to use the GPU.
1169 * TODO: explicit declexport. since -flto strips these symbols in release.
1170 */
1171 u32 NvOptimusEnablement = 0x00000001;
1172 int AmdPowerXpressRequestHighPerformance = 1;
1173
1174 #include "vg_async.c"
1175 #include "vg_audio.c"
1176 #include "vg_audio_dsp.c"
1177 #include "vg_audio_synth_bird.c"
1178 #include "vg_binstr.c"
1179 #include "vg_bvh.c"
1180 #include "vg_camera.c"
1181 #include "vg_lines.c"
1182 #include "vg_console.c"
1183 #include "vg_imgui.c"
1184 #include "vg_input.c"
1185 #include "vg_io.c"
1186 #include "vg_loader.c"
1187 #include "vg_log.c"
1188 #include "vg_tex.c"
1189 #include "vg_mem.c"
1190 #include "vg_mem_pool.c"
1191 #include "vg_mem_queue.c"
1192 #include "vg_msg.c"
1193 #include "vg_opt.c"
1194 #include "vg_perlin.c"
1195 #include "vg_string.c"
1196 #include "vg_profiler.c"
1197 #include "vg_rigidbody_collision.c"
1198 #include "vg_rigidbody_constraints.c"
1199 #include "vg_rigidbody.c"
1200 #include "vg_rigidbody_view.c"
1201 #include "vg_shader.c"
1202
1203 #ifdef VG_CUSTOM_SHADERS
1204 #include "shaders/impl.c"
1205 #endif