2024-08-12 15:11:25 +00:00
//-----------------------------------------------------------------------------
// capture tests
static
uint64_t tests_captureframes ( ) {
static uint64_t capture_target ; do_once capture_target = optioni ( " --capture " , 0 ) ;
return capture_target ;
}
//-----------------------------------------------------------------------------
// fps locking
static volatile float framerate = 0 ;
static volatile unsigned fps_active , timer_counter , loop_counter ;
static
int fps__timing_thread ( void * arg ) {
int64_t ns_excess = 0 ;
while ( fps_active ) {
if ( framerate < = 0 ) {
loop_counter = timer_counter = 0 ;
sleep_ms ( 250 ) ;
} else {
timer_counter + + ;
int64_t tt = ( int64_t ) ( 1e9 / ( float ) framerate ) - ns_excess ;
uint64_t took = - time_ns ( ) ;
# if is(win32)
timeBeginPeriod ( 1 ) ;
# endif
sleep_ns ( tt > 0 ? ( float ) tt : 0.f ) ;
took + = time_ns ( ) ;
ns_excess = took - tt ;
if ( ns_excess < 0 ) ns_excess = 0 ;
//puts( strf("%lld", ns_excess) );
}
}
fps_active = 1 ;
( void ) arg ;
return thread_exit ( 0 ) , 0 ;
}
static
void fps_locker ( int on ) {
if ( on ) {
// private threaded timer
fps_active = 1 , timer_counter = loop_counter = 0 ;
thread_init ( fps__timing_thread , 0 , " fps__timing_thread() " , 0 ) ;
} else {
fps_active = 0 ;
}
}
// function that locks render to desired `framerate` framerate (in FPS).
// - assumes fps_locker() was enabled beforehand.
// - returns true if must render, else 0.
static
int fps_wait ( ) {
if ( framerate < = 0 ) return 1 ;
if ( ! fps_active ) return 1 ;
// if we throttled too much, cpu idle wait
while ( fps_active & & ( loop_counter > timer_counter ) ) {
//thread_yield();
sleep_ns ( 100 ) ;
}
// max auto frameskip is 10: ie, even if speed is low paint at least one frame every 10
enum { maxframeskip = 10 } ;
if ( timer_counter > loop_counter + maxframeskip ) {
loop_counter = timer_counter ;
}
loop_counter + + ;
// only draw if we are fast enough, otherwise skip the frame
return loop_counter > = timer_counter ;
}
static
void window_vsync ( float hz ) {
if ( tests_captureframes ( ) ) return ;
if ( hz < = 0 ) return ;
do_once fps_locker ( 1 ) ;
framerate = hz ;
fps_wait ( ) ;
}
//-----------------------------------------------------------------------------
#if 0 // deprecated
static void ( * hooks [ 64 ] ) ( ) = { 0 } ;
static void * userdatas [ 64 ] = { 0 } ;
bool window_hook ( void ( * func ) ( ) , void * user ) {
window_unhook ( func ) ;
for ( int i = 0 ; i < 64 ; + + i ) {
if ( ! hooks [ i ] ) {
hooks [ i ] = func ;
userdatas [ i ] = user ;
return true ;
}
}
return false ;
}
void window_unhook ( void ( * func ) ( ) ) {
for ( int i = 0 ; i < 64 ; + + i ) {
if ( hooks [ i ] = = func ) {
hooks [ i ] = 0 ;
userdatas [ i ] = 0 ;
}
}
}
# endif
static GLFWwindow * window ;
static int w , h , xpos , ypos , paused ;
static int fullscreen , xprev , yprev , wprev , hprev ;
static uint64_t frame_count ;
static double t , dt , fps , hz = 0.00 ;
static char title [ 128 ] = { 0 } ;
static char screenshot_file [ DIR_MAX ] ;
static int locked_aspect_ratio = 0 ;
static vec4 winbgcolor = { 0 , 0 , 0 , 1 } ;
vec4 window_getcolor_ ( ) { return winbgcolor ; } // internal
// -----------------------------------------------------------------------------
// glfw
struct app {
GLFWwindow * window ;
int width , height , keep_running ;
unsigned flags ;
struct nk_context * ctx ;
struct nk_glfw * nk_glfw ;
} appHandle = { 0 } , * g ;
static void glfw_error_callback ( int error , const char * description ) {
if ( is ( osx ) & & error = = 65544 ) return ; // whitelisted
PANIC ( " %s (error %x) " , description , error ) ;
}
void glfw_quit ( void ) {
do_once {
glfwTerminate ( ) ;
}
}
void glfw_init ( ) {
do_once {
g = & appHandle ;
glfwSetErrorCallback ( glfw_error_callback ) ;
int ok = ! ! glfwInit ( ) ;
assert ( ok ) ; // if(!ok) PANIC("cannot initialize glfw");
atexit ( glfw_quit ) ; //glfwTerminate);
}
}
void window_drop_callback ( GLFWwindow * window , int count , const char * * paths ) {
// @fixme: win: convert from utf8 to window16 before processing
char pathdir [ DIR_MAX ] ; snprintf ( pathdir , DIR_MAX , " %s/import/%llu_%s/ " , ART , ( unsigned long long ) date ( ) , ifdef ( linux , getlogin ( ) , getenv ( " USERNAME " ) ) ) ;
mkdir ( pathdir , 0777 ) ;
int errors = 0 ;
for ( int i = 0 ; i < count ; + + i ) {
const char * src = paths [ i ] ;
const char * dst = va ( " %s%s " , pathdir , file_name ( src ) ) ;
errors + = file_copy ( src , dst ) ? 0 : 1 ;
}
if ( errors ) PANIC ( " %d errors found during file dropping " , errors ) ;
else window_reload ( ) ;
( void ) window ;
}
void window_hints ( unsigned flags ) {
# ifdef __APPLE__
//glfwInitHint( GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE );
glfwWindowHint ( GLFW_COCOA_RETINA_FRAMEBUFFER , GLFW_FALSE ) ; // @todo: remove silicon mac M1 hack
//glfwWindowHint( GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_FALSE );
//glfwWindowHint( GLFW_COCOA_MENUBAR, GLFW_FALSE );
# endif
# ifdef __APPLE__
/* We need to explicitly ask for a 3.2 context on OS X */
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 3 ) ; // osx
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 2 ) ; // osx, 2:#version150,3:330
# else
// Compute shaders need 4.5 otherwise. But...
// According to the GLFW docs, the context version hint acts as a minimum version.
// i.e, the context you actually get may be a higher or highest version (which is usually the case)
glfwWindowHint ( GLFW_CONTEXT_VERSION_MAJOR , 3 ) ;
glfwWindowHint ( GLFW_CONTEXT_VERSION_MINOR , 2 ) ;
# endif
# ifdef __APPLE__
glfwWindowHint ( GLFW_OPENGL_FORWARD_COMPAT , GL_TRUE ) ; //osx
# endif
glfwWindowHint ( GLFW_OPENGL_PROFILE , GLFW_OPENGL_CORE_PROFILE ) ; //osx+ems
glfwWindowHint ( GLFW_STENCIL_BITS , 8 ) ; //osx
2024-08-15 17:49:41 +00:00
# if DEBUG
2024-08-12 15:11:25 +00:00
glfwWindowHint ( GLFW_OPENGL_DEBUG_CONTEXT , GL_TRUE ) ;
2024-08-15 17:49:41 +00:00
# endif
2024-08-12 15:11:25 +00:00
//glfwWindowHint( GLFW_RED_BITS, 8 );
//glfwWindowHint( GLFW_GREEN_BITS, 8 );
//glfwWindowHint( GLFW_BLUE_BITS, 8 );
//glfwWindowHint( GLFW_ALPHA_BITS, 8 );
//glfwWindowHint( GLFW_DEPTH_BITS, 24 );
//glfwWindowHint(GLFW_AUX_BUFFERS, Nth);
//glfwWindowHint(GLFW_STEREO, GL_TRUE);
glfwWindowHint ( GLFW_DOUBLEBUFFER , GL_TRUE ) ;
// Prevent fullscreen window minimize on focus loss
glfwWindowHint ( GLFW_AUTO_ICONIFY , GL_FALSE ) ;
// Fix SRGB on intels
glfwWindowHint ( GLFW_SRGB_CAPABLE , GLFW_TRUE ) ;
glfwWindowHint ( GLFW_FOCUSED , GLFW_TRUE ) ;
glfwWindowHint ( GLFW_VISIBLE , GLFW_FALSE ) ;
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
// glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // makes it non-resizable
if ( flags & WINDOW_MSAA2 ) glfwWindowHint ( GLFW_SAMPLES , 2 ) ; // x2 AA
if ( flags & WINDOW_MSAA4 ) glfwWindowHint ( GLFW_SAMPLES , 4 ) ; // x4 AA
if ( flags & WINDOW_MSAA8 ) glfwWindowHint ( GLFW_SAMPLES , 8 ) ; // x8 AA
g - > flags = flags ;
}
struct nk_glfw * window_handle_nkglfw ( ) {
return g - > nk_glfw ;
}
static renderstate_t window_rs ;
void glNewFrame ( ) {
do_once {
window_rs = renderstate ( ) ;
window_rs . blend_enabled = 1 ;
window_rs . depth_test_enabled = 1 ;
}
window_rs . clear_color [ 0 ] = winbgcolor . r ;
window_rs . clear_color [ 1 ] = winbgcolor . g ;
window_rs . clear_color [ 2 ] = winbgcolor . b ;
window_rs . clear_color [ 3 ] = window_has_transparent ( ) ? 0 : winbgcolor . a ;
// @transparent debug
// if( input_down(KEY_F1) ) window_transparent(window_has_transparent()^1);
// if( input_down(KEY_F2) ) window_maximize(window_has_maximize()^1);
// @transparent debug
#if 0 // is(ems)
int canvasWidth , canvasHeight ;
emscripten_get_canvas_element_size ( " #canvas " , & canvasWidth , & canvasHeight ) ;
w = canvasWidth ;
h = canvasHeight ;
//printf("%dx%d\n", w, h);
# else
//glfwGetWindowSize(window, &w, &h);
glfwGetFramebufferSize ( window , & w , & h ) ;
//printf("%dx%d\n", w, h);
# endif
g - > width = w ;
g - > height = h ;
renderstate_apply ( & window_rs ) ;
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ) ;
}
static bool cook_done = false ;
bool window_create_from_handle ( void * handle , float scale , unsigned flags ) {
// abort run if any test suite failed in unit-test mode
ifdef ( debug , if ( flag ( " --test " ) ) exit ( test_errors ? - test_errors : 0 ) ) ;
glfw_init ( ) ;
v4k_init ( ) ;
if ( ! t ) t = glfwGetTime ( ) ;
# if is(ems)
scale = 100.f ;
# endif
if ( flag ( " --fullscreen " ) ) scale = 100 ;
scale = ( scale < 1 ? scale * 100 : scale ) ;
bool FLAGS_FULLSCREEN = scale > 100 ;
bool FLAGS_FULLSCREEN_DESKTOP = scale = = 100 ;
bool FLAGS_WINDOWED = scale < 100 ;
2024-08-15 17:49:41 +00:00
bool FLAGS_TRUE_BORDERLESS = flags & WINDOW_TRUE_BORDERLESS ;
2024-08-12 15:11:25 +00:00
bool FLAGS_TRANSPARENT = flag ( " --transparent " ) | | ( flags & WINDOW_TRANSPARENT ) ;
if ( FLAGS_TRANSPARENT ) FLAGS_FULLSCREEN = 0 , FLAGS_FULLSCREEN_DESKTOP = 0 , FLAGS_WINDOWED = 1 ;
scale = ( scale > 100 ? 100 : scale ) / 100.f ;
int winWidth = window_canvas ( ) . w * scale ;
int winHeight = window_canvas ( ) . h * scale ;
2024-08-15 17:49:41 +00:00
if ( FLAGS_TRUE_BORDERLESS ) {
FLAGS_FULLSCREEN = FLAGS_FULLSCREEN_DESKTOP = 0 ;
FLAGS_WINDOWED = 1 ;
flags | = WINDOW_BORDERLESS ;
}
2024-08-12 15:11:25 +00:00
/*
if ( tests_captureframes ( ) ) {
winWidth = 1280 ;
winHeight = 720 ;
}
*/
window_hints ( flags ) ;
GLFWmonitor * monitor = NULL ;
# ifndef __EMSCRIPTEN__
if ( FLAGS_FULLSCREEN | | FLAGS_FULLSCREEN_DESKTOP ) {
monitor = glfwGetPrimaryMonitor ( ) ;
}
if ( FLAGS_FULLSCREEN_DESKTOP ) {
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ) ;
glfwWindowHint ( GLFW_RED_BITS , mode - > redBits ) ;
glfwWindowHint ( GLFW_GREEN_BITS , mode - > greenBits ) ;
glfwWindowHint ( GLFW_BLUE_BITS , mode - > blueBits ) ;
glfwWindowHint ( GLFW_REFRESH_RATE , mode - > refreshRate ) ;
winWidth = mode - > width ;
winHeight = mode - > height ;
}
if ( FLAGS_WINDOWED ) {
# if !is(ems)
if ( FLAGS_TRANSPARENT ) { // @transparent
//glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); // see through. requires undecorated
//glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // always on top
glfwWindowHint ( GLFW_TRANSPARENT_FRAMEBUFFER , GLFW_TRUE ) ;
}
if ( flags & WINDOW_BORDERLESS ) {
2024-08-15 17:49:41 +00:00
// glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
2024-08-12 15:11:25 +00:00
glfwWindowHint ( GLFW_DECORATED , GLFW_FALSE ) ;
}
# endif
// windowed
float ratio = ( float ) winWidth / ( winHeight + ! winHeight ) ;
if ( flags & WINDOW_SQUARE ) winWidth = winHeight = winWidth > winHeight ? winHeight : winWidth ;
//if( flags & WINDOW_LANDSCAPE ) if( winWidth < winHeight ) winHeight = winWidth * ratio;
if ( flags & WINDOW_PORTRAIT ) if ( winWidth > winHeight ) winWidth = winHeight * ( 1.f / ratio ) ;
}
# endif
window = handle ? handle : glfwCreateWindow ( winWidth , winHeight , " " , monitor , NULL ) ;
if ( ! window ) return PANIC ( " GLFW Window creation failed " ) , false ;
glfwGetFramebufferSize ( window , & w , & h ) ; //glfwGetWindowSize(window, &w, &h);
if ( flags & WINDOW_FIXED ) { // disable resizing
glfwSetWindowSizeLimits ( window , w , h , w , h ) ;
}
if ( flags & ( WINDOW_SQUARE | WINDOW_PORTRAIT | WINDOW_LANDSCAPE | WINDOW_ASPECT ) ) { // keep aspect ratio
window_aspect_lock ( w , h ) ;
}
# ifndef __EMSCRIPTEN__
if ( FLAGS_WINDOWED ) {
// center window
monitor = monitor ? monitor : glfwGetPrimaryMonitor ( ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ) ;
int area_width = mode - > width , area_height = mode - > height ;
glfwGetMonitorWorkarea ( monitor , & xpos , & ypos , & area_width , & area_height ) ;
glfwSetWindowPos ( window , xpos = xpos + ( area_width - winWidth ) / 2 , ypos = ypos + ( area_height - winHeight ) / 2 ) ;
//printf("%dx%d @(%d,%d) [res:%dx%d]\n", winWidth, winHeight, xpos,ypos, area_width, area_height );
wprev = w , hprev = h ;
xprev = xpos , yprev = ypos ;
}
# endif
glfwMakeContextCurrent ( window ) ;
# if is(ems)
if ( FLAGS_FULLSCREEN ) window_fullscreen ( 1 ) ;
# else
int gl_version = gladLoadGL ( glfwGetProcAddress ) ;
# endif
2024-08-18 23:13:13 +00:00
// set black screen
glClearColor ( 0 , 0 , 0 , 1 ) ;
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;
glfwSwapBuffers ( window ) ;
2024-08-12 15:11:25 +00:00
glDebugEnable ( ) ;
// setup nuklear ui
ui_ctx = nk_glfw3_init ( & nk_glfw , window , NK_GLFW3_INSTALL_CALLBACKS ) ;
//glEnable(GL_TEXTURE_2D);
// 0:disable vsync, 1:enable vsync, <0:adaptive (allow vsync when framerate is higher than syncrate and disable vsync when framerate drops below syncrate)
flags | = optioni ( " --vsync " , 0 ) | | flag ( " --vsync " ) ? WINDOW_VSYNC : WINDOW_VSYNC_DISABLED ;
flags | = optioni ( " --vsync-adaptive " , 0 ) | | flag ( " --vsync-adaptive " ) ? WINDOW_VSYNC_ADAPTIVE : 0 ;
int has_adaptive_vsync = glfwExtensionSupported ( " WGL_EXT_swap_control_tear " ) | | glfwExtensionSupported ( " GLX_EXT_swap_control_tear " ) | | glfwExtensionSupported ( " EXT_swap_control_tear " ) ;
int wants_adaptive_vsync = ( flags & WINDOW_VSYNC_ADAPTIVE ) ;
int interval = has_adaptive_vsync & & wants_adaptive_vsync ? - 1 : ( flags & WINDOW_VSYNC ? 1 : 0 ) ;
glfwSwapInterval ( interval ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( monitor ? monitor : glfwGetPrimaryMonitor ( ) ) ;
PRINTF ( " Build version: %s \n " , BUILD_VERSION ) ;
PRINTF ( " Monitor: %s (%dHz, vsync=%d) \n " , glfwGetMonitorName ( monitor ? monitor : glfwGetPrimaryMonitor ( ) ) , mode - > refreshRate , interval ) ;
PRINTF ( " GPU device: %s \n " , glGetString ( GL_RENDERER ) ) ;
PRINTF ( " GPU driver: %s \n " , glGetString ( GL_VERSION ) ) ;
# if !is(ems)
PRINTF ( " GPU OpenGL: %d.%d \n " , GLAD_VERSION_MAJOR ( gl_version ) , GLAD_VERSION_MINOR ( gl_version ) ) ;
if ( FLAGS_TRANSPARENT ) { // @transparent
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_FALSE ) ; // @todo: is decorated an attrib or a hint?
if ( scale > = 1 ) glfwMaximizeWindow ( window ) ;
}
2024-08-15 17:49:41 +00:00
if ( FLAGS_TRUE_BORDERLESS ) {
if ( scale > = 1 ) glfwMaximizeWindow ( window ) ;
glfwSetWindowSize ( window , w , h ) ;
}
2024-08-12 15:11:25 +00:00
# endif
g - > ctx = ui_ctx ;
g - > nk_glfw = & nk_glfw ;
g - > window = window ;
g - > width = window_width ( ) ;
g - > height = window_height ( ) ;
2024-08-15 17:49:41 +00:00
PRINTF ( " Window: %dx%d \n " , g - > width , g - > height ) ;
if ( glfwRawMouseMotionSupported ( ) ) {
glfwSetInputMode ( window , GLFW_RAW_MOUSE_MOTION , GLFW_TRUE ) ;
}
2024-08-12 15:11:25 +00:00
// window_cursor(flags & WINDOW_NO_MOUSE ? false : true);
glfwSetDropCallback ( window , window_drop_callback ) ;
// camera inits for v4k_pre_init() -> ddraw_flush() -> get_active_camera()
// static camera_t cam = {0}; id44(cam.view); id44(cam.proj); extern camera_t *last_camera; last_camera = &cam;
v4k_pre_init ( ) ;
// display a progress bar meanwhile cook is working in the background
// Sleep(500);
if ( ! COOK_ON_DEMAND )
if ( have_tools ( ) & & cook_jobs ( ) )
while ( cook_progress ( ) < 100 ) {
for ( int frames = 0 ; frames < 2 /*10*/ & & window_swap ( ) ; frames + = cook_progress ( ) > = 100 ) {
window_title ( va ( " %s %.2d%% " , cook_cancelling ? " Aborting " : " Cooking assets " , cook_progress ( ) ) ) ;
if ( input ( KEY_ESC ) ) cook_cancel ( ) ;
glNewFrame ( ) ;
static float previous [ JOBS_MAX ] = { 0 } ;
# define ddraw_progress_bar(JOB_ID, JOB_MAX, PERCENT) do { \
/* NDC coordinates (2d): bottom-left(-1,-1), center(0,0), top-right(+1,+1) */ \
float progress = ( PERCENT + 1 ) / 100.f ; if ( progress > 1 ) progress = 1 ; \
float speed = progress < 1 ? 0.05f : 0.75f ; \
float smooth = previous [ JOB_ID ] = progress * speed + previous [ JOB_ID ] * ( 1 - speed ) ; \
\
float pixel = 2.f / window_height ( ) , dist = smooth * 2 - 1 , y = pixel * 3 * JOB_ID ; \
if ( JOB_ID = = 0 ) ddraw_line ( vec3 ( - 1 , y - pixel * 2 , 0 ) , vec3 ( 1 , y - pixel * 2 , 0 ) ) ; /* full line */ \
ddraw_line ( vec3 ( - 1 , y - pixel , 0 ) , vec3 ( dist , y - pixel , 0 ) ) ; /* progress line */ \
ddraw_line ( vec3 ( - 1 , y + 0 , 0 ) , vec3 ( dist , y + 0 , 0 ) ) ; /* progress line */ \
ddraw_line ( vec3 ( - 1 , y + pixel , 0 ) , vec3 ( dist , y + pixel , 0 ) ) ; /* progress line */ \
if ( JOB_ID = = JOB_MAX - 1 ) ddraw_line ( vec3 ( - 1 , y + pixel * 2 , 0 ) , vec3 ( 1 , y + pixel * 2 , 0 ) ) ; /* full line */ \
} while ( 0 )
if ( FLAGS_TRANSPARENT ) { } else // @transparent
for ( int i = 0 ; i < cook_jobs ( ) ; + + i ) ddraw_progress_bar ( i , cook_jobs ( ) , jobs [ i ] . progress ) ;
// ddraw_progress_bar(0, 1, cook_progress());
ddraw_flush ( ) ;
do_once window_visible ( 1 ) ;
// render progress bar at 30Hz + give the cook threads more time to actually cook the assets.
// no big deal since progress bar is usually quiet when cooking assets most of the time.
// also, make the delay even larger when window is minimized or hidden.
// shaved cook times: 88s -> 57s (tcc), 50s -> 43s (vc)
sleep_ms ( window_has_visible ( ) & & window_has_focus ( ) ? 8 : 16 ) ;
}
// set black screen
glNewFrame ( ) ;
window_swap ( ) ;
# if !ENABLE_RETAIL
window_title ( " " ) ;
# endif
}
if ( cook_cancelling ) cook_stop ( ) , exit ( - 1 ) ;
cook_done = true ;
v4k_post_init ( mode - > refreshRate ) ;
return true ;
}
bool window_create ( float scale , unsigned flags ) {
return window_create_from_handle ( NULL , scale , flags ) ;
}
2024-08-16 09:27:47 +00:00
void window_destroy ( ) {
if ( ! window ) return ;
glfwSetWindowShouldClose ( window , GL_TRUE ) ;
}
2024-08-12 15:11:25 +00:00
static double boot_time = 0 ;
char * window_stats ( ) {
static double num_frames = 0 , begin = FLT_MAX , prev_frame = 0 ;
double now = time_ss ( ) ;
if ( boot_time < 0 ) boot_time = now ;
if ( begin > now ) {
begin = now ;
num_frames = 0 ;
}
if ( ( now - begin ) > = 0.25f ) {
fps = num_frames * ( 1.f / ( now - begin ) ) ;
}
if ( ( now - begin ) > 1 ) {
begin = now + ( ( now - begin ) - 1 ) ;
num_frames = 0 ;
}
const char * cmdline = app_cmdline ( ) ;
// @todo: print %used/%avail kib mem, %used/%avail objs as well
static char buf [ 256 ] ;
snprintf ( buf , 256 , " %s%s%s%s | boot %.2fs | %5.2ffps (%.2fms)%s%s " ,
title , BUILD_VERSION [ 0 ] ? " ( " : " " , BUILD_VERSION [ 0 ] ? BUILD_VERSION : " " , BUILD_VERSION [ 0 ] ? " ) " : " " ,
! boot_time ? now : boot_time ,
fps , ( now - prev_frame ) * 1000.f ,
cmdline [ 0 ] ? " | " : " " , cmdline [ 0 ] ? cmdline : " " ) ;
prev_frame = now ;
+ + num_frames ;
return buf + strspn ( buf , " " ) ;
}
int window_frame_begin ( ) {
glfwPollEvents ( ) ;
// we cannot simply terminate threads on some OSes. also, aborted cook jobs could leave temporary files on disc.
// so let's try to be polite: we will be disabling any window closing briefly until all cook is either done or canceled.
static bool has_cook ; do_once has_cook = ! COOK_ON_DEMAND & & have_tools ( ) & & cook_jobs ( ) ;
if ( has_cook ) {
has_cook = cook_progress ( ) < 100 ;
if ( glfwWindowShouldClose ( g - > window ) ) cook_cancel ( ) ;
glfwSetWindowShouldClose ( g - > window , GLFW_FALSE ) ;
}
if ( glfwWindowShouldClose ( g - > window ) ) {
return 0 ;
}
glNewFrame ( ) ;
ui_create ( ) ;
# if !ENABLE_RETAIL
bool has_menu = ui_has_menubar ( ) ;
bool may_render_debug_panel = 1 ;
if ( have_tools ( ) ) {
static int cook_has_progressbar ; do_once cook_has_progressbar = ! COOK_ON_DEMAND ;
if ( cook_has_progressbar ) {
// render profiler, unless we are in the cook progress screen
static unsigned frames = 0 ; if ( frames < = 0 ) frames + = cook_progress ( ) > = 100 ;
may_render_debug_panel = ( frames > 0 ) ;
}
}
if ( ! win_debug_visible )
may_render_debug_panel = 0 ;
// generate Debug panel contents
if ( may_render_debug_panel ) {
if ( has_menu ? ui_window ( " Debug " ICON_MD_SETTINGS , 0 ) : ui_panel ( " Debug " ICON_MD_SETTINGS , 0 ) ) {
ui_engine ( ) ;
( has_menu ? ui_window_end : ui_panel_end ) ( ) ;
}
API int engine_tick ( ) ;
engine_tick ( ) ;
}
# endif // ENABLE_RETAIL
#if 0 // deprecated
// run user-defined hooks
for ( int i = 0 ; i < 64 ; + + i ) {
if ( hooks [ i ] ) hooks [ i ] ( userdatas [ i ] ) ;
}
# endif
double now = paused ? t : glfwGetTime ( ) ;
dt = now - t ;
t = now ;
# if !ENABLE_RETAIL
char * st = window_stats ( ) ;
static double timer = 0 ;
timer + = window_delta ( ) ;
if ( timer > = 0.25 ) {
glfwSetWindowTitle ( window , st ) ;
timer = 0 ;
}
# else
glfwSetWindowTitle ( window , title ) ;
# endif
void input_update ( ) ;
input_update ( ) ;
return 1 ;
}
void window_frame_end ( ) {
// flush batching systems that need to be rendered before frame swapping. order matters.
{
font_goto ( 0 , 0 ) ;
touch_flush ( ) ;
sprite_flush ( ) ;
// flush all debugdraw calls before swap
dd_ontop = 0 ;
ddraw_flush ( ) ;
glClear ( GL_DEPTH_BUFFER_BIT ) ;
dd_ontop = 1 ;
ddraw_flush ( ) ;
ui_render ( ) ;
}
# if !is(ems)
// save screenshot if queued
if ( screenshot_file [ 0 ] ) {
int n = 3 ;
void * rgb = screenshot ( n ) ;
stbi_flip_vertically_on_write ( true ) ;
if ( ! stbi_write_png ( screenshot_file , w , h , n , rgb , n * w ) ) {
PANIC ( " !could not write screenshot file `%s` \n " , screenshot_file ) ;
}
screenshot_file [ 0 ] = 0 ;
}
if ( record_active ( ) ) {
void record_frame ( ) ;
record_frame ( ) ;
}
# endif
}
void window_frame_swap ( ) {
// glFinish();
# if !is(ems)
window_vsync ( hz ) ;
# endif
glfwSwapBuffers ( window ) ;
// emscripten_webgl_commit_frame();
static int delay = 0 ; do_once delay = optioni ( " --delay " , 0 ) ;
if ( delay & & ! COOK_ON_DEMAND & & cook_progress ( ) > = 100 ) sleep_ms ( delay ) ;
}
static
void window_shutdown ( ) {
do_once {
# if ENABLE_SELFIES
snprintf ( screenshot_file , DIR_MAX , " %s.png " , app_name ( ) ) ;
int n = 3 ;
void * rgb = screenshot ( n ) ;
stbi_flip_vertically_on_write ( true ) ;
if ( ! stbi_write_png ( screenshot_file , w , h , n , rgb , n * w ) ) {
PANIC ( " !could not write screenshot file `%s` \n " , screenshot_file ) ;
}
screenshot_file [ 0 ] = 0 ;
# endif
window_loop_exit ( ) ; // finish emscripten loop automatically
2024-08-16 09:27:47 +00:00
glfwTerminate ( ) ;
2024-08-12 15:11:25 +00:00
}
}
int window_swap ( ) {
// end frame
if ( frame_count > 0 ) {
window_frame_end ( ) ;
window_frame_swap ( ) ;
}
+ + frame_count ;
// begin frame
int ready = window_frame_begin ( ) ;
if ( ! ready ) {
window_shutdown ( ) ;
return 0 ;
}
static uint64_t capture_frame = 0 ;
if ( cook_done & & + + capture_frame = = tests_captureframes ( ) ) {
mkdir ( " tests/out " , 0777 ) ;
const char * screenshot_file = va ( " tests/out/%s.png " , app_name ( ) ) ;
int n = 3 ;
void * rgb = screenshot ( n ) ;
stbi_flip_vertically_on_write ( true ) ;
if ( ! stbi_write_png ( screenshot_file , w , h , n , rgb , n * w ) ) {
PANIC ( " !could not write screenshot file `%s` \n " , screenshot_file ) ;
}
return 0 ;
}
return 1 ;
}
static
void ( * window_render_callback ) ( void * loopArg ) ;
static vec2 last_canvas_size = { 0 } ;
static
void window_resize ( ) {
# if is(ems)
EM_ASM ( canvas . canResize = 0 ) ;
if ( g - > flags & WINDOW_FIXED ) return ;
EM_ASM ( canvas . canResize = 1 ) ;
vec2 size = window_canvas ( ) ;
if ( size . x ! = last_canvas_size . x | | size . y ! = last_canvas_size . y ) {
w = size . x ;
h = size . y ;
g - > width = w ;
g - > height = h ;
last_canvas_size = vec2 ( w , h ) ;
emscripten_set_canvas_size ( w , h ) ;
}
# endif /* __EMSCRIPTEN__ */
}
static
void window_loop_wrapper ( void * loopArg ) {
if ( window_frame_begin ( ) ) {
window_resize ( ) ;
window_render_callback ( loopArg ) ;
window_frame_end ( ) ;
window_frame_swap ( ) ;
} else {
do_once window_shutdown ( ) ;
}
}
void window_loop ( void ( * user_function ) ( void * loopArg ) , void * loopArg ) {
# if is(ems)
window_render_callback = user_function ;
emscripten_set_main_loop_arg ( window_loop_wrapper , loopArg , 0 , 1 ) ;
# else
g - > keep_running = true ;
while ( g - > keep_running )
user_function ( loopArg ) ;
# endif /* __EMSCRIPTEN__ */
}
void window_loop_exit ( ) {
# if is(ems)
emscripten_cancel_main_loop ( ) ;
# else
g - > keep_running = false ;
# endif /* __EMSCRIPTEN__ */
}
vec2 window_canvas ( ) {
# if is(ems)
int width = EM_ASM_INT_V ( return canvas . width ) ;
int height = EM_ASM_INT_V ( return canvas . height ) ;
return vec2 ( width , height ) ;
# else
glfw_init ( ) ;
const GLFWvidmode * mode = glfwGetVideoMode ( glfwGetPrimaryMonitor ( ) ) ;
assert ( mode ) ;
return vec2 ( mode - > width , mode - > height ) ;
# endif /* __EMSCRIPTEN__ */
}
int window_width ( ) {
return w ;
}
int window_height ( ) {
return h ;
}
double window_time ( ) {
return t ;
}
double window_delta ( ) {
return dt ;
}
void window_debug ( int visible ) {
win_debug_visible = visible ;
}
int window_has_debug ( ) {
return win_debug_visible ;
}
double window_fps ( ) {
return fps ;
}
void window_fps_lock ( float fps ) {
hz = fps ;
}
void window_fps_unlock ( ) {
hz = 0 ;
}
double window_fps_target ( ) {
return hz ;
}
void window_fps_vsync ( int vsync ) {
glfwSwapInterval ( vsync ) ;
}
uint64_t window_frame ( ) {
return frame_count ;
}
void window_title ( const char * title_ ) {
snprintf ( title , 128 , " %s " , title_ ) ;
if ( ! title [ 0 ] ) glfwSetWindowTitle ( window , title ) ;
}
void window_color ( unsigned color ) {
unsigned r = ( color > > 0 ) & 255 ;
unsigned g = ( color > > 8 ) & 255 ;
unsigned b = ( color > > 16 ) & 255 ;
unsigned a = ( color > > 24 ) & 255 ;
winbgcolor = vec4 ( r / 255.0 , g / 255.0 , b / 255.0 , a / 255.0 ) ;
}
static int has_icon ;
int window_has_icon ( ) {
return has_icon ;
}
void window_icon ( const char * file_icon ) {
int len = 0 ;
void * data = vfs_load ( file_icon , & len ) ;
if ( ! data ) data = file_read ( file_icon ) , len = file_size ( file_icon ) ;
if ( data & & len ) {
image_t img = image_from_mem ( data , len , IMAGE_RGBA ) ;
if ( img . w & & img . h & & img . pixels ) {
GLFWimage images [ 1 ] ;
images [ 0 ] . width = img . w ;
images [ 0 ] . height = img . h ;
images [ 0 ] . pixels = img . pixels ;
glfwSetWindowIcon ( window , 1 , images ) ;
has_icon = 1 ;
return ;
}
}
#if 0 // is(win32)
HANDLE hIcon = LoadImageA ( 0 , file_icon , IMAGE_ICON , 0 , 0 , LR_DEFAULTSIZE | LR_LOADFROMFILE ) ;
if ( hIcon ) {
HWND hWnd = glfwGetWin32Window ( window ) ;
SendMessage ( hWnd , WM_SETICON , ICON_SMALL , ( LPARAM ) hIcon ) ;
SendMessage ( hWnd , WM_SETICON , ICON_BIG , ( LPARAM ) hIcon ) ;
SendMessage ( GetWindow ( hWnd , GW_OWNER ) , WM_SETICON , ICON_SMALL , ( LPARAM ) hIcon ) ;
SendMessage ( GetWindow ( hWnd , GW_OWNER ) , WM_SETICON , ICON_BIG , ( LPARAM ) hIcon ) ;
has_icon = 1 ;
return ;
}
# endif
}
void * window_handle ( ) {
return window ;
}
void window_reload ( ) {
// @todo: save_on_exit();
fflush ( 0 ) ;
// chdir(app_path());
execv ( __argv [ 0 ] , __argv ) ;
exit ( 0 ) ;
}
int window_record ( const char * outfile_mp4 ) {
record_start ( outfile_mp4 ) ;
// @todo: if( flags & RECORD_MOUSE )
if ( record_active ( ) ) window_cursor_shape ( CURSOR_SW_AUTO ) ; else window_cursor_shape ( CURSOR_HW_ARROW ) ;
return record_active ( ) ;
}
vec2 window_dpi ( ) {
vec2 dpi = vec2 ( 1 , 1 ) ;
# if !is(ems) && !is(osx) // @todo: remove silicon mac M1 hack
glfwGetMonitorContentScale ( glfwGetPrimaryMonitor ( ) , & dpi . x , & dpi . y ) ;
# endif
return dpi ;
}
// -----------------------------------------------------------------------------
// fullscreen
static
GLFWmonitor * window_find_monitor ( int wx , int wy ) {
GLFWmonitor * monitor = glfwGetPrimaryMonitor ( ) ;
// find best monitor given current window coordinates. @todo: select by ocuppied window area inside each monitor instead.
int num_monitors = 0 ;
GLFWmonitor * * monitors = glfwGetMonitors ( & num_monitors ) ;
# if is(ems)
return * monitors ;
# else
for ( int i = 0 ; i < num_monitors ; + + i ) {
int mx = 0 , my = 0 , mw = 0 , mh = 0 ;
glfwGetMonitorWorkarea ( monitors [ i ] , & mx , & my , & mw , & mh ) ;
monitor = wx > = mx & & wx < = ( mx + mw ) & & wy > = my & & wy < = ( my + mh ) ? monitors [ i ] : monitor ;
}
return monitor ;
# endif
}
#if 0 // to deprecate
void window_fullscreen ( int enabled ) {
fullscreen = ! ! enabled ;
# ifndef __EMSCRIPTEN__
if ( fullscreen ) {
int wx = 0 , wy = 0 ; glfwGetWindowPos ( window , & wx , & wy ) ;
GLFWmonitor * monitor = window_find_monitor ( wx , wy ) ;
wprev = w , hprev = h , xprev = wx , yprev = wy ; // save window context for further restoring
int width , height ;
glfwGetMonitorWorkarea ( monitor , NULL , NULL , & width , & height ) ;
glfwSetWindowMonitor ( window , monitor , 0 , 0 , width , height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( window , NULL , xpos , ypos , wprev , hprev , GLFW_DONT_CARE ) ;
glfwSetWindowPos ( window , xprev , yprev ) ;
}
# endif
}
int window_has_fullscreen ( ) {
return fullscreen ;
}
# else
int window_has_fullscreen ( ) {
# if is(ems)
EmscriptenFullscreenChangeEvent fsce ;
emscripten_get_fullscreen_status ( & fsce ) ;
return ! ! fsce . isFullscreen ;
# else
return ! ! glfwGetWindowMonitor ( g - > window ) ;
# endif /* __EMSCRIPTEN__ */
}
void window_fullscreen ( int enabled ) {
if ( window_has_fullscreen ( ) = = ! ! enabled ) return ;
# if is(ems)
#if 0 // deprecated: crash
if ( enabled ) {
emscripten_exit_soft_fullscreen ( ) ;
/* Workaround https://github.com/kripken/emscripten/issues/5124#issuecomment-292849872 */
EM_ASM ( JSEvents . inEventHandler = true ) ;
EM_ASM ( JSEvents . currentEventHandler = { allowsDeferredCalls : true } ) ;
EmscriptenFullscreenStrategy strategy = { 0 } ;
strategy . scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH ; // _ASPECT
strategy . canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF ; // _NONE _HIDEF
strategy . filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT ; // _NEAREST
emscripten_request_fullscreen_strategy ( NULL , EM_FALSE /*EM_TRUE*/ , & strategy ) ;
//emscripten_enter_soft_fullscreen(NULL, &strategy);
} else {
emscripten_exit_fullscreen ( ) ;
}
# else
if ( enabled )
EM_ASM ( Module . requestFullscreen ( 1 , 1 ) ) ;
else
EM_ASM ( Module . exitFullscreen ( ) ) ;
# endif
# else
#if 0
if ( enabled ) {
/*glfwGetWindowPos(g->window, &g->window_xpos, &g->window_ypos);*/
glfwGetWindowSize ( g - > window , & g - > width , & g - > height ) ;
glfwSetWindowMonitor ( g - > window , glfwGetPrimaryMonitor ( ) , 0 , 0 , g - > width , g - > height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( g - > window , NULL , 0 , 0 , g - > width , g - > height , GLFW_DONT_CARE ) ;
}
# else
if ( enabled ) {
int wx = 0 , wy = 0 ; glfwGetWindowPos ( window , & wx , & wy ) ;
GLFWmonitor * monitor = window_find_monitor ( wx , wy ) ;
wprev = w , hprev = h , xprev = wx , yprev = wy ; // save window context for further restoring
int width , height ;
glfwGetMonitorWorkarea ( monitor , NULL , NULL , & width , & height ) ;
glfwSetWindowMonitor ( window , monitor , 0 , 0 , width , height , GLFW_DONT_CARE ) ;
} else {
glfwSetWindowMonitor ( window , NULL , xpos , ypos , wprev , hprev , GLFW_DONT_CARE ) ;
glfwSetWindowPos ( window , xprev , yprev ) ;
}
# endif
# endif
}
# endif
void window_pause ( int enabled ) {
paused = enabled ;
}
int window_has_pause ( ) {
return paused ;
}
void window_focus ( ) {
glfwFocusWindow ( window ) ;
}
int window_has_focus ( ) {
return ! ! glfwGetWindowAttrib ( window , GLFW_FOCUSED ) ;
}
static int cursorshape = 1 ;
void window_cursor_shape ( unsigned mode ) {
cursorshape = ( mode & = 7 ) ;
static GLFWcursor * cursors [ 7 ] = { 0 } ;
static unsigned enums [ 7 ] = {
0 ,
GLFW_ARROW_CURSOR ,
GLFW_IBEAM_CURSOR ,
GLFW_HRESIZE_CURSOR ,
GLFW_VRESIZE_CURSOR ,
GLFW_HAND_CURSOR ,
GLFW_CROSSHAIR_CURSOR ,
} ;
do_once {
static unsigned pixels [ 16 * 16 ] = { 0x01000000 } ; // ABGR(le) glfw3 note: A(0x00) means 0xFF for some reason
static GLFWimage image = { 16 , 16 , ( void * ) pixels } ;
static GLFWcursor * empty ;
for ( int x = 0 ; x < 16 * 16 ; + + x ) pixels [ x ] = pixels [ 0 ] ;
empty = glfwCreateCursor ( & image , 0 , 0 ) ;
for ( int i = 0 ; i < countof ( enums ) ; + + i ) cursors [ i ] = i ? glfwCreateStandardCursor ( enums [ i ] ) : empty ;
}
if ( mode = = CURSOR_SW_AUTO ) { // UI (nuklear) driven cursor
nk_style_show_cursor ( ui_handle ( ) ) ;
glfwSetCursor ( window , cursors [ 0 ] ) ;
} else {
nk_style_hide_cursor ( ui_handle ( ) ) ;
glfwSetCursor ( window , mode < countof ( enums ) ? cursors [ mode ] : NULL ) ;
}
}
void window_cursor ( int visible ) {
( cursorshape = = CURSOR_SW_AUTO & & visible ? nk_style_show_cursor : nk_style_hide_cursor ) ( ui_handle ( ) ) ;
glfwSetInputMode ( window , GLFW_CURSOR , visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED ) ;
}
int window_has_cursor ( ) {
return glfwGetInputMode ( window , GLFW_CURSOR ) = = GLFW_CURSOR_NORMAL ;
}
void window_visible ( int visible ) {
if ( ! window ) return ;
//if(window) (visible ? glfwRestoreWindow : glfwIconifyWindow)(window);
( visible ? glfwShowWindow : glfwHideWindow ) ( window ) ;
// call glfwpollevents in linux to flush visiblity changes that would happen in next frame otherwise
# if is(linux) || is(osx)
glfwPollEvents ( ) ;
# endif
}
int window_has_visible ( ) {
return glfwGetWindowAttrib ( window , GLFW_VISIBLE ) ;
}
void window_screenshot ( const char * outfile_png ) {
snprintf ( screenshot_file , DIR_MAX , " %s " , outfile_png ? outfile_png : " " ) ;
}
double window_aspect ( ) {
return ( double ) w / ( h + ! h ) ;
}
void window_aspect_lock ( unsigned numer , unsigned denom ) {
if ( ! window ) return ;
if ( numer * denom ) {
glfwSetWindowAspectRatio ( window , numer , denom ) ;
} else {
glfwSetWindowAspectRatio ( window , GLFW_DONT_CARE , GLFW_DONT_CARE ) ;
}
}
void window_aspect_unlock ( ) {
if ( ! window ) return ;
window_aspect_lock ( 0 , 0 ) ;
}
void window_transparent ( int enabled ) {
# if !is(ems)
if ( ! window_has_fullscreen ( ) ) {
if ( enabled ) {
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_FALSE ) ;
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_TRUE);
//glfwMaximizeWindow(window);
} else {
//glfwRestoreWindow(window);
//glfwSetWindowAttrib(window, GLFW_MOUSE_PASSTHROUGH , GLFW_FALSE);
glfwSetWindowAttrib ( window , GLFW_DECORATED , GLFW_TRUE ) ;
}
}
# endif
}
int window_has_transparent ( ) {
return ifdef ( ems , 0 , glfwGetWindowAttrib ( window , GLFW_DECORATED ) ! = GLFW_TRUE ) ;
}
void window_maximize ( int enabled ) {
ifdef ( ems , return ) ;
if ( ! window_has_fullscreen ( ) ) {
if ( enabled ) {
glfwMaximizeWindow ( window ) ;
} else {
glfwRestoreWindow ( window ) ;
}
}
}
int window_has_maximize ( ) {
return ifdef ( ems , 0 , glfwGetWindowAttrib ( window , GLFW_MAXIMIZED ) = = GLFW_TRUE ) ;
}
const char * window_clipboard ( ) {
return glfwGetClipboardString ( window ) ;
}
void window_setclipboard ( const char * text ) {
glfwSetClipboardString ( window , text ) ;
}
static
double window_scale ( ) { // ok? @testme
float xscale = 1 , yscale = 1 ;
# if !is(ems) && !is(osx) // @todo: remove silicon mac M1 hack
GLFWmonitor * monitor = glfwGetPrimaryMonitor ( ) ;
glfwGetMonitorContentScale ( monitor , & xscale , & yscale ) ;
# endif
return maxi ( xscale , yscale ) ;
}