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