v4k-git-backup/engine/split/v4k_window.c

1145 lines
36 KiB
C
Raw Normal View History

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
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);
}
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
}
}
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);
}