error screen is a bit silly but works
[vg.git] / src / vg / vg.h
1 /* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */
2
3 #ifndef VG_HEADER_H
4 #define VG_HEADER_H
5
6 #include "vg_platform.h"
7
8
9 #if defined(VG_SERVER) || defined(VG_TOOLS)
10 #define VG_NON_CLIENT
11 #endif
12
13 #ifndef VG_SERVER
14 #include "../../dep/glad/glad.h"
15 #include "../../dep/glfw/glfw3.h"
16 #endif
17
18 #include "vg_stdint.h"
19
20 void vg_register_exit( void( *funcptr )(void), const char *name );
21
22 #include "vg_m.h"
23 #include "vg_io.h"
24 #include "vg_log.h"
25
26 #ifdef VG_STEAM
27 //#include "vg_steamworks.h"
28 #include "vg_steam.h"
29 #endif
30
31 #ifndef VG_NON_CLIENT
32 #include "vg_gldiag.h"
33 #endif
34
35 #ifndef VG_NON_CLIENT
36
37 m4x4f vg_pv;
38
39 #ifdef VG_CAPTURE_MODE
40 int vg_window_x = 1920;
41 int vg_window_y = 1080;
42 #else
43 int vg_window_x = 1366;
44 int vg_window_y = 768;
45 #endif
46
47 v2f vg_mouse;
48 v2f vg_mouse_wheel;
49 v3f vg_mouse_ws;
50
51 double vg_time,
52 vg_time_last,
53 vg_time_delta;
54
55 struct vg
56 {
57 /* Engine sync */
58 GLFWwindow* window;
59
60 vg_mutex mux_context;
61 vg_semaphore sem_allow_exec,
62 sem_exec_finished,
63 sem_loader,
64 sem_fatal;
65 int exec_context;
66
67 vg_mutex mux_engine_status;
68 enum engine_status
69 {
70 k_engine_status_none,
71 k_engine_status_running,
72 k_engine_status_crashed
73 }
74 engine_status;
75 const char *str_const_engine_err;
76
77 int is_loaded;
78
79 /* Gamepad */
80 GLFWgamepadstate gamepad;
81 int gamepad_ready;
82 const char *gamepad_name;
83 int gamepad_id;
84 }
85 static vg;
86
87 struct vg_thread_info
88 {
89 enum vg_thread_purpose
90 {
91 k_thread_purpose_nothing,
92 k_thread_purpose_main,
93 k_thread_purpose_loader
94 }
95 purpose;
96
97 int gl_context_level;
98 };
99
100 static VG_THREAD_LOCAL struct vg_thread_info vg_thread_info;
101
102
103 //#define VG_SYNC_DEBUG
104
105 #ifdef VG_SYNC_DEBUG
106 #define VG_SYNC_LOG(STR,...) vg_info(STR,vg_thread_info.purpose,##__VA_ARGS__)
107 #else
108 #define VG_SYNC_LOG(...)
109 #endif
110
111 static void vg_fatal_exit_loop( const char *error );
112
113 static void vg_ensure_engine_running(void)
114 {
115 /* Check if the engine is no longer running */
116 vg_mutex_lock( &vg.mux_engine_status );
117 if( vg.engine_status != k_engine_status_running )
118 {
119 VG_SYNC_LOG( "[%d] Engine is no longer running\n");
120 vg_mutex_unlock( &vg.mux_engine_status );
121
122 /* Safe to disregard loader thread from this point on, elswhere */
123 if( vg_thread_info.purpose == k_thread_purpose_loader )
124 {
125 vg_semaphore_post( &vg.sem_loader );
126 }
127
128 VG_SYNC_LOG( "[%d] about to kill\n");
129 vg_thread_exit();
130 }
131 vg_mutex_unlock( &vg.mux_engine_status );
132 }
133
134 /*
135 * Sync execution so that the OpenGL context is switched onto this thread.
136 * Anything after this call will be in a valid context.
137 */
138 static void vg_acquire_thread_sync(void)
139 {
140 /* We dont want to do anything if this is the main thread */
141 if( vg_thread_info.purpose == k_thread_purpose_main )
142 return;
143
144 assert( vg_thread_info.purpose == k_thread_purpose_loader );
145
146 vg_ensure_engine_running();
147
148 /* Check if thread already has the context */
149 if( vg_thread_info.gl_context_level )
150 {
151 vg_thread_info.gl_context_level ++;
152 VG_SYNC_LOG( "[%d] We already have sync here\n" );
153 return;
154 }
155
156 vg_mutex_lock( &vg.mux_context );
157 VG_SYNC_LOG( "[%d] Signal to sync.\n" );
158 vg.exec_context = 1;
159 vg_mutex_unlock( &vg.mux_context );
160
161 /* wait until told we can go */
162 VG_SYNC_LOG( "[%d] Waiting to acuire sync.\n" );
163 vg_semaphore_wait( &vg.sem_allow_exec );
164 glfwMakeContextCurrent( vg.window );
165
166 /* context now valid to work in while we hold up main thread */
167 VG_SYNC_LOG( "[%d] Context acquired.\n" );
168 vg_thread_info.gl_context_level ++;
169 }
170
171 /*
172 * Signify that we are done with the OpenGL context in this thread.
173 * Anything after this call will be in an undefined context.
174 */
175 static void vg_release_thread_sync(void)
176 {
177 if( vg_thread_info.purpose == k_thread_purpose_main )
178 return;
179
180 assert( vg_thread_info.purpose == k_thread_purpose_loader );
181
182 /* signal that we are done */
183 vg_thread_info.gl_context_level --;
184
185 if( !vg_thread_info.gl_context_level )
186 {
187 VG_SYNC_LOG( "[%d] Releasing context.\n" );
188 glfwMakeContextCurrent( NULL );
189 vg_semaphore_post( &vg.sem_exec_finished );
190 }
191 }
192
193 static void vg_run_synced_content(void)
194 {
195 assert( vg_thread_info.purpose == k_thread_purpose_main );
196
197 vg_mutex_lock( &vg.mux_context );
198
199 if( vg.exec_context != 0 )
200 {
201 VG_SYNC_LOG( "[%d] Allowing content (%d).\n", vg.exec_context );
202
203 /* allow operations to go */
204 vg_thread_info.gl_context_level = 0;
205 glfwMakeContextCurrent( NULL );
206 vg_semaphore_post( &vg.sem_allow_exec );
207
208 /* wait for operations to complete */
209 VG_SYNC_LOG( "[%d] Waiting for content (%d).\n", vg.exec_context );
210 vg_semaphore_wait( &vg.sem_exec_finished );
211
212 /* check if we killed the engine */
213 vg_ensure_engine_running();
214
215 /* re-engage main thread */
216 VG_SYNC_LOG( "[%d] Re-engaging.\n" );
217 vg.exec_context = 0;
218 glfwMakeContextCurrent( vg.window );
219 vg_thread_info.gl_context_level = 1;
220 }
221
222 vg_mutex_unlock( &vg.mux_context );
223 }
224
225 static void vg_opengl_sync_init(void)
226 {
227 vg_semaphore_init( &vg.sem_allow_exec, 0 );
228 vg_semaphore_init( &vg.sem_exec_finished, 0 );
229 vg_semaphore_init( &vg.sem_loader, 1 );
230 vg_semaphore_init( &vg.sem_fatal, 1 );
231 vg_mutex_init( &vg.mux_context );
232
233 vg_set_thread_name( "[vg] Main" );
234 vg_thread_info.purpose = k_thread_purpose_main;
235 vg_thread_info.gl_context_level = 1;
236 }
237
238 static void vg_checkgl( const char *src_info );
239 #define VG_STRINGIT( X ) #X
240 #define VG_CHECK_GL_ERR() vg_checkgl( __FILE__ ":L" VG_STRINGIT(__LINE__) )
241
242 #include "vg_audio.h"
243 #include "vg_shader.h"
244 #include "vg_tex.h"
245 #include "vg_input.h"
246 #include "vg_ui.h"
247 #include "vg_console.h"
248 #include "vg_lines.h"
249 #include "vg_debug.h"
250 #include "vg_loader.h"
251
252 #define VG_GAMELOOP
253 static void vg_register(void) VG_GAMELOOP;
254 static void vg_start(void) VG_GAMELOOP;
255 static void vg_update(int loaded) VG_GAMELOOP;
256 static void vg_framebuffer_resize(int w, int h) VG_GAMELOOP;
257 static void vg_render(void) VG_GAMELOOP;
258 static void vg_ui(void) VG_GAMELOOP;
259
260 static void vg_checkgl( const char *src_info )
261 {
262 int fail = 0;
263
264 GLenum err;
265 while( (err = glGetError()) != GL_NO_ERROR )
266 {
267 vg_error( "(%s) OpenGL Error: #%d\n", src_info, err );
268 fail = 1;
269 }
270
271 if( fail )
272 vg_fatal_exit_loop( "OpenGL Error" );
273 }
274
275 void vg_mouse_callback( GLFWwindow* ptrW, double xpos, double ypos )
276 {
277 vg_mouse[0] = xpos;
278 vg_mouse[1] = ypos;
279 }
280
281 void vg_scroll_callback( GLFWwindow* ptrW, double xoffset, double yoffset )
282 {
283 vg_mouse_wheel[0] += xoffset;
284 vg_mouse_wheel[1] += yoffset;
285 }
286
287 void vg_framebuffer_resize_callback( GLFWwindow *ptrW, int w, int h )
288 {
289 vg_window_x = w;
290 vg_window_y = h;
291
292 vg_framebuffer_resize(w,h);
293 }
294
295 static int vg_bake_shaders(void)
296 {
297 vg_acquire_thread_sync();
298
299 if( !vg_shaders_recompile() )
300 {
301 vg_shaders_free(NULL);
302 vg_release_thread_sync();
303 return 0;
304 }
305 else
306 {
307 vg_release_thread_sync();
308 vg_loader_highwater( NULL, vg_shaders_free, NULL );
309 return 1;
310 }
311 }
312
313 void vg_preload(void);
314 void vg_load(void);
315 static void vg_load_full(void)
316 {
317 vg_preload();
318
319 /* internal */
320 vg_loader_highwater( vg_gamepad_init, NULL, NULL );
321 vg_loader_highwater( vg_lines_init, vg_lines_free, NULL );
322 vg_loader_highwater( vg_audio_init, vg_audio_free, NULL );
323
324 /* client */
325 vg_load();
326
327 vg_acquire_thread_sync();
328 vg.is_loaded = 1;
329 vg_release_thread_sync();
330 }
331
332 static void vg_enter( int argc, char *argv[], const char *window_name )
333 {
334 vg_log_init();
335 vg_console_init();
336
337 glfwInit();
338 glfwWindowHint( GLFW_CONTEXT_VERSION_MAJOR, 3 );
339 glfwWindowHint( GLFW_CONTEXT_VERSION_MINOR, 3 );
340 glfwWindowHint( GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE );
341 glfwWindowHint( GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE );
342
343 glfwWindowHint( GLFW_RESIZABLE, GLFW_TRUE );
344 glfwWindowHint( GLFW_DOUBLEBUFFER, GLFW_TRUE );
345
346 #if 0
347 glfwWindowHint(GLFW_SAMPLES,4);
348 #endif
349
350 GLFWmonitor *monitor_primary = glfwGetPrimaryMonitor();
351
352 const GLFWvidmode *mode = glfwGetVideoMode( monitor_primary );
353 glfwWindowHint( GLFW_RED_BITS, mode->redBits );
354 glfwWindowHint( GLFW_GREEN_BITS, mode->greenBits );
355 glfwWindowHint( GLFW_BLUE_BITS, mode->blueBits );
356
357 /* This is set like this because of an OS issue */
358 int refresh_rate = mode->refreshRate;
359 if( refresh_rate < 28 || refresh_rate >= 144 )
360 refresh_rate = 60;
361 glfwWindowHint( GLFW_REFRESH_RATE, refresh_rate );
362
363 if( !(vg.window = glfwCreateWindow( vg_window_x, vg_window_y,
364 window_name, NULL, NULL)) )
365 {
366 vg_error( "GLFW Failed to initialize\n" );
367 return;
368 }
369
370 glfwMakeContextCurrent( vg.window );
371 glfwSwapInterval( 1 );
372
373 glfwSetWindowSizeLimits( vg.window, 800, 600, GLFW_DONT_CARE,GLFW_DONT_CARE);
374 glfwSetFramebufferSizeCallback( vg.window, vg_framebuffer_resize_callback );
375
376 glfwSetCursorPosCallback( vg.window, vg_mouse_callback );
377 glfwSetScrollCallback( vg.window, vg_scroll_callback );
378
379 glfwSetCharCallback( vg.window, console_proc_wchar );
380 glfwSetKeyCallback( vg.window, console_proc_key );
381 #if 0
382 glfwSetInputMode(vg_window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
383 #endif
384
385 if( !gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) )
386 {
387 vg_error( "Glad Failed to initialize\n" );
388 glfwTerminate();
389 return;
390 }
391
392 const unsigned char* glver = glGetString( GL_VERSION );
393 vg_success( "Load setup complete, OpenGL version: %s\n", glver );
394 vg_run_gfx_diagnostics();
395
396 if( !ui_default_init() )
397 goto il_exit_ui;
398
399 if( !vg_loader_init() )
400 goto il_exit_loader;
401
402 vg_mutex_init( &vg.mux_engine_status );
403 vg.engine_status = k_engine_status_running;
404
405 vg_opengl_sync_init();
406 vg_loader_start();
407
408 int loaded = 0;
409 int counter =0;
410 while(1)
411 {
412 if( glfwWindowShouldClose( vg.window ) )
413 break;
414
415 v2_copy( (v2f){ 0.0f, 0.0f }, vg_mouse_wheel );
416 glfwPollEvents();
417
418 vg_time_last = vg_time;
419 vg_time = glfwGetTime();
420 vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
421
422 if( vg.is_loaded )
423 {
424 glClearColor( 0.0f,sinf(vg_time*20.0)*0.5f+0.5f,0.0f,1.0f );
425 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
426
427 if( !loaded )
428 {
429 vg_start();
430 loaded = 1;
431 }
432 }
433 else
434 {
435 vg_loader_render();
436 }
437
438 vg_update_inputs();
439 vg_update( loaded );
440
441 counter ++;
442
443 #if 0
444 if( counter == 30 )
445 vg_fatal_exit_loop( "Test crash from main, while loading (-O0)" );
446 #endif
447
448 if( loaded )
449 {
450 vg_render();
451
452 /* ui */
453 {
454 ui_begin( &ui_global_ctx, vg_window_x, vg_window_y );
455 ui_set_mouse( &ui_global_ctx, vg_mouse[0], vg_mouse[1],
456 vg_get_button_state( "primary" ) );
457
458 audio_debug_ui( vg_pv );
459 vg_ui();
460 vg_console_draw();
461
462 ui_resolve( &ui_global_ctx );
463 ui_draw( &ui_global_ctx, NULL );
464 }
465 }
466
467 glfwSwapBuffers( vg.window );
468 vg_run_synced_content();
469 }
470
471 vg_console_write_persistent();
472
473 vg_mutex_lock( &vg.mux_engine_status );
474 vg.engine_status = k_engine_status_none;
475 vg_mutex_unlock( &vg.mux_engine_status );
476
477 vg_loader_free();
478
479 il_exit_loader:
480 ui_default_free();
481
482 il_exit_ui:
483 glfwTerminate();
484 }
485
486 /*
487 * Immediately transfer away from calling thread into a safe loop, signal for
488 * others to shutdown, then free everything once the user closes the window.
489 */
490 static void vg_fatal_exit_loop( const char *error )
491 {
492 vg_error( "Fatal error: %s\n", error );
493 assert( vg_semaphore_trywait( &vg.sem_fatal ) );
494
495 vg_mutex_lock( &vg.mux_engine_status );
496
497 if( vg.engine_status == k_engine_status_none )
498 {
499 vg_mutex_unlock( &vg.mux_engine_status );
500
501 /* TODO: Correct shutdown before other systems */
502 exit(0);
503 }
504 else
505 {
506 vg_mutex_unlock( &vg.mux_engine_status );
507
508 /*
509 * if main
510 * if loader running
511 * wait until loader checks in, it will die
512 * else
513 * pass immediately
514 * else
515 * if have context
516 * pass immediately
517 * else
518 * wait for main to get to us, it will never be used again
519 *
520 * undefined behaviour:
521 * fatal_exit_loop is called in both threads, preventing an appropriate
522 * reaction to the crash. This *should* be made
523 * obvious by the assertion
524 */
525 vg_acquire_thread_sync();
526
527 vg_mutex_lock( &vg.mux_engine_status );
528 vg.engine_status = k_engine_status_crashed;
529 vg.str_const_engine_err = error;
530 vg_mutex_unlock( &vg.mux_engine_status );
531
532 /*
533 * Wait for loader to finish what it was doing, if it was running.
534 * Then we can continue in our nice error screen
535 */
536 if( vg_thread_info.purpose == k_thread_purpose_main )
537 {
538 vg_semaphore_wait( &vg.sem_loader );
539 }
540 vg_audio_free(NULL);
541
542 /*
543 * todo: draw error loop
544 */
545 while(1)
546 {
547 if( glfwWindowShouldClose( vg.window ) )
548 break;
549
550 glfwPollEvents();
551
552 vg_time = glfwGetTime();
553 vg_time_delta = vg_minf( vg_time - vg_time_last, 0.1f );
554
555 glClearColor( sinf(vg_time*20.0)*0.5f+0.5f, 0.0f, 0.0f,1.0f );
556 glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT );
557 vg_render_log();
558
559 glfwSwapBuffers( vg.window );
560 }
561
562 /* Can now shutdown and EXIT */
563 vg_loader_free();
564 ui_default_free();
565
566 glfwTerminate();
567 exit(0);
568 }
569 }
570
571 #endif
572
573 /*
574 * Graphic cards will check these to force it to use the GPU
575 */
576 u32 NvOptimusEnablement = 0x00000001;
577 int AmdPowerXpressRequestHighPerformance = 1;
578
579 #endif