diff --git a/sfw/application/fwk_window.c b/sfw/application/fwk_window.c deleted file mode 100644 index 4a59bd9..0000000 --- a/sfw/application/fwk_window.c +++ /dev/null @@ -1,1085 +0,0 @@ -//----------------------------------------------------------------------------- -// 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); Sleep( tt > 0 ? tt/1e6 : 0 ); - #else - sleep_ns( (float)tt ); - #endif - 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( 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 - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); - - //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; -} - -void glNewFrame() { - // @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; - - // blending defaults - glEnable(GL_BLEND); - - // culling defaults -// glEnable(GL_CULL_FACE); -// glCullFace(GL_BACK); -// glFrontFace(GL_CCW); - - // depth-testing defaults - glEnable(GL_DEPTH_TEST); -// glDepthFunc(GL_LESS); - - // depth-writing defaults -// glDepthMask(GL_TRUE); - - // seamless cubemaps -// glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - - glViewport(0, 0, window_width(), window_height()); - - // GLfloat bgColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, bgColor); - glClearColor(winbgcolor.r, winbgcolor.g, winbgcolor.b, window_has_transparent() ? 0 : winbgcolor.a); // @transparent - //glClearColor(0.15,0.15,0.15,1); - //glClearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); -} - -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(); - fwk_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; - 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; - - 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 ) { - 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", 1) || 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_DISABLED ? 0 : 1); - 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); - } - #endif - - g->ctx = ui_ctx; - g->nk_glfw = &nk_glfw; - g->window = window; - g->width = window_width(); - g->height = window_height(); - - // window_cursor(flags & WINDOW_NO_MOUSE ? false : true); - glfwSetDropCallback(window, window_drop_callback); - - // camera inits for fwk_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; - fwk_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); - - fwk_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); - } - } - - // 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; - } - 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; -} - -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; -} - -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, yscale; - GLFWmonitor *monitor = glfwGetPrimaryMonitor(); - glfwGetMonitorContentScale(monitor, &xscale, &yscale); - return maxi(xscale, yscale); -} diff --git a/sfw/application/fwk_window.h b/sfw/application/fwk_window.h deleted file mode 100644 index 3af2246..0000000 --- a/sfw/application/fwk_window.h +++ /dev/null @@ -1,99 +0,0 @@ -// ----------------------------------------------------------------------------- -// window framework -// - rlyeh, public domain -// -// @todo: window_cursor(ico); -// @todo: if WINDOW_PORTRAIT && exist portrait monitor, use that instead of primary one -// @todo: WINDOW_TRAY - -enum WINDOW_FLAGS { - WINDOW_MSAA2 = 0x02, - WINDOW_MSAA4 = 0x04, - WINDOW_MSAA8 = 0x08, - - WINDOW_SQUARE = 0x20, - WINDOW_PORTRAIT = 0x40, - WINDOW_LANDSCAPE = 0x80, - WINDOW_ASPECT = 0x100, // keep aspect - WINDOW_FIXED = 0x200, // disable resizing - WINDOW_TRANSPARENT = 0x400, - WINDOW_BORDERLESS = 0x800, - - WINDOW_VSYNC = 0, - WINDOW_VSYNC_ADAPTIVE = 0x1000, - WINDOW_VSYNC_DISABLED = 0x2000, -}; - -API bool window_create(float scale, unsigned flags); -API bool window_create_from_handle(void *handle, float scale, unsigned flags); -API void window_reload(); - -API int window_frame_begin(); -API void window_frame_end(); -API void window_frame_swap(); -API int window_swap(); // single function that combines above functions (desktop only) - -API void window_loop(void (*function)(void* loopArg), void* loopArg ); // run main loop function continuously (emscripten only) -API void window_loop_exit(); // exit from main loop function (emscripten only) - -API void window_title(const char *title); -API void window_color(unsigned color); -API vec2 window_canvas(); -API void* window_handle(); -API char* window_stats(); - -API uint64_t window_frame(); -API int window_width(); -API int window_height(); -API double window_time(); -API double window_delta(); - -// API bool window_hook(void (*func)(), void* userdata); // deprecated -// API void window_unhook(void (*func)()); // deprecated - -API void window_focus(); // window attribute api using haz catz language for now -API int window_has_focus(); -API void window_fullscreen(int enabled); -API int window_has_fullscreen(); -API void window_cursor(int visible); -API int window_has_cursor(); -API void window_pause(int paused); -API int window_has_pause(); -API void window_visible(int visible); -API int window_has_visible(); -API void window_maximize(int enabled); -API int window_has_maximize(); -API void window_transparent(int enabled); -API int window_has_transparent(); -API void window_icon(const char *file_icon); -API int window_has_icon(); - -API double window_aspect(); -API void window_aspect_lock(unsigned numer, unsigned denom); -API void window_aspect_unlock(); - -API double window_fps(); -API double window_fps_target(); -API void window_fps_lock(float fps); -API void window_fps_unlock(); - -API void window_screenshot(const char* outfile_png); // , bool record_cursor -API int window_record(const char *outfile_mp4); // , bool record_cursor - -API vec2 window_dpi(); - -enum CURSOR_SHAPES { - CURSOR_NONE, - CURSOR_HW_ARROW, // default - CURSOR_HW_IBEAM, // i-beam text cursor - CURSOR_HW_HDRAG, // horizontal drag/resize - CURSOR_HW_VDRAG, // vertical drag/resize - CURSOR_HW_HAND, // hand, clickable - CURSOR_HW_CROSS, // crosshair - CURSOR_SW_AUTO, // software cursor, ui driven. note: this is the only icon that may be recorded or snapshotted -}; - -API void window_cursor_shape(unsigned shape); - -API const char *window_clipboard(); -API void window_setclipboard(const char *text); diff --git a/sfw/application/window.cpp b/sfw/application/window.cpp new file mode 100644 index 0000000..d0feb16 --- /dev/null +++ b/sfw/application/window.cpp @@ -0,0 +1,1153 @@ +//----------------------------------------------------------------------------- +// fps locking + +#include "window.h" + +#include "vector4.h" + +#include "3rd_glfw3.h" +#include "stime.h" + +/* +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; + Time::sleep_ms(250); + } else { + timer_counter++; + int64_t tt = (int64_t)(1e9 / (float)framerate) - ns_excess; + uint64_t took = -Time::time_ns(); +#if is(win32) + timeBeginPeriod(1); + Sleep(tt > 0 ? tt / 1e6 : 0); +#else + sleep_ns((float)tt); +#endif + 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(); + Time::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 (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) { + 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 + +Vector4 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 + 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 + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); + + //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; +} + +void glNewFrame() { + // @transparent debug + // if( input_down(KEY_F1) ) transparent(window_has_transparent()^1); + // if( input_down(KEY_F2) ) 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; + + // blending defaults + glEnable(GL_BLEND); + + // culling defaults + // glEnable(GL_CULL_FACE); + // glCullFace(GL_BACK); + // glFrontFace(GL_CCW); + + // depth-testing defaults + glEnable(GL_DEPTH_TEST); + // glDepthFunc(GL_LESS); + + // depth-writing defaults + // glDepthMask(GL_TRUE); + + // seamless cubemaps + // glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + + glViewport(0, 0, Window::get_singleton()->width(), Window::get_singleton()->height()); + + // GLfloat bgColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE, bgColor); + glClearColor(winbgcolor.r, winbgcolor.g, winbgcolor.b, Window::get_singleton()->has_transparent() ? 0 : winbgcolor.a); // @transparent + //glClearColor(0.15,0.15,0.15,1); + //glClearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a ); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +bool Window::create_from_handle(void *handle, float scale, unsigned flags) { + // abort run if any test suite failed in unit-test mode + + glfw_init(); + //fwk_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; + 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 = canvas().w * scale; + int winHeight = canvas().h * scale; + + 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) { + 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 + 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) + fullscreen(1); +#else + int gl_version = gladLoadGL(glfwGetProcAddress); +#endif + + glDebugEnable(); + + //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", 1) || 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_DISABLED ? 0 : 1); + 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); + } +#endif + + g->ctx = ui_ctx; + g->nk_glfw = &nk_glfw; + g->window = window; + g->width = width(); + g->height = height(); + + // cursor(flags & WINDOW_NO_MOUSE ? false : true); + glfwSetDropCallback(window, drop_callback); + + // camera inits for fwk_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; + fwk_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*/ && swap(); frames += cook_progress() >= 100) { + 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 / 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 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(has_visible() && has_focus() ? 8 : 16); + } + // set black screen + glNewFrame(); + swap(); +#if !ENABLE_RETAIL + title(""); +#endif + } + + if (cook_cancelling) + cook_stop(), exit(-1); + + fwk_post_init(mode->refreshRate); + return true; +} + +bool Window::create(float scale, unsigned flags) { + return 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::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); + } + } + + // 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 = stats(); + static double timer = 0; + timer += 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) + 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 + + loop_exit(); // finish emscripten loop automatically + } +} + +int Window::swap() { + // end frame + if (frame_count > 0) { + frame_end(); + frame_swap(); + } + + ++frame_count; + + // begin frame + int ready = frame_begin(); + if (!ready) { + shutdown(); + return 0; + } + return 1; +} + +static void (*window_render_callback)(void *loopArg); + +static Vector2 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); + Vector2 size = 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 = Vector2(w, h); + emscripten_set_canvas_size(w, h); + } +#endif /* __EMSCRIPTEN__ */ +} + +static void Window::loop_wrapper(void *loopArg) { + if (frame_begin()) { + resize(); + render_callback(loopArg); + frame_end(); + frame_swap(); + } else { + do_once shutdown(); + } +} + +void Window::loop(void (*user_function)(void *loopArg), void *loopArg) { +#if is(ems) + 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__ */ +} + +Vector2 Window::canvas() { +#if is(ems) + int width = EM_ASM_INT_V(return canvas.width); + int height = EM_ASM_INT_V(return canvas.height); + return Vector2(width, height); +#else + glfw_init(); + const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + assert(mode); + return Vector2(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; +} + +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; +} + +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 = Vector4(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()) + cursor_shape(CURSOR_SW_AUTO); + else + cursor_shape(CURSOR_HW_ARROW); + return record_active(); +} + +Vector2 Window::dpi() { + Vector2 dpi = Vector2(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 = 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 (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 = 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, yscale; + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + glfwGetMonitorContentScale(monitor, &xscale, &yscale); + return MAX(xscale, yscale); +} + +Window *Window::get_singleton() { + return _singleton; +} + +Window::Window() { + _singleton = this; + + window = NULL; + w = 0; + h = 0; + xpos = 0; + ypos = 0; + paused = 0; + fullscreen = 0; + xprev = 0; + yprev = 0; + wprev = 0; + hprev = 0; + frame_count = 0; + t = 0; + dt = 0; + fps = 0; + hz = 0.00; + locked_aspect_ratio = 0; +} +Window::~Window() { + _singleton = NULL; +} + +Window *Window::_singleton = NULL; diff --git a/sfw/application/window.h b/sfw/application/window.h new file mode 100644 index 0000000..fb3388c --- /dev/null +++ b/sfw/application/window.h @@ -0,0 +1,135 @@ +// ----------------------------------------------------------------------------- +// window framework +// - rlyeh, public domain +// +// @todo: cursor(ico); +// @todo: if WINDOW_PORTRAIT && exist portrait monitor, use that instead of primary one +// @todo: WINDOW_TRAY + +#include "int_types.h" +#include "vector2.h" + +class Window { +public: + enum WINDOW_FLAGS { + WINDOW_MSAA2 = 0x02, + WINDOW_MSAA4 = 0x04, + WINDOW_MSAA8 = 0x08, + + WINDOW_SQUARE = 0x20, + WINDOW_PORTRAIT = 0x40, + WINDOW_LANDSCAPE = 0x80, + WINDOW_ASPECT = 0x100, // keep aspect + WINDOW_FIXED = 0x200, // disable resizing + WINDOW_TRANSPARENT = 0x400, + WINDOW_BORDERLESS = 0x800, + + WINDOW_VSYNC = 0, + WINDOW_VSYNC_ADAPTIVE = 0x1000, + WINDOW_VSYNC_DISABLED = 0x2000, + }; + + bool create(float scale, unsigned flags); + bool create_from_handle(void *handle, float scale, unsigned flags); + void reload(); + + int frame_begin(); + void frame_end(); + void frame_swap(); + int swap(); // single function that combines above functions (desktop only) + + void loop(void (*function)(void *loopArg), void *loopArg); // run main loop function continuously (emscripten only) + void loop_exit(); // exit from main loop function (emscripten only) + + void title(const char *title); + void color(unsigned color); + Vector2 canvas(); + void *handle(); + char *stats(); + + uint64_t frame(); + int width(); + int height(); + double time(); + double delta(); + + // bool hook(void (*func)(), void* userdata); // deprecated + // void unhook(void (*func)()); // deprecated + + void focus(); // window attribute using haz catz language for now + int has_focus(); + void fullscreen(int enabled); + int has_fullscreen(); + void cursor(int visible); + int has_cursor(); + void pause(int paused); + int has_pause(); + void visible(int visible); + int has_visible(); + void maximize(int enabled); + int has_maximize(); + void transparent(int enabled); + int has_transparent(); + void icon(const char *file_icon); + int has_icon(); + + double aspect(); + void aspect_lock(unsigned numer, unsigned denom); + void aspect_unlock(); + + double fps(); + double fps_target(); + void fps_lock(float fps); + void fps_unlock(); + + void screenshot(const char *outfile_png); // , bool record_cursor + int record(const char *outfile_mp4); // , bool record_cursor + + Vector2 dpi(); + + enum CURSOR_SHAPES { + CURSOR_NONE, + CURSOR_HW_ARROW, // default + CURSOR_HW_IBEAM, // i-beam text cursor + CURSOR_HW_HDRAG, // horizontal drag/resize + CURSOR_HW_VDRAG, // vertical drag/resize + CURSOR_HW_HAND, // hand, clickable + CURSOR_HW_CROSS, // crosshair + CURSOR_SW_AUTO, // software cursor, ui driven. note: this is the only icon that may be recorded or snapshotted + }; + + void cursor_shape(unsigned shape); + + const char *clipboard(); + void setclipboard(const char *text); + + static Window *get_singleton(); + + Window(); + ~Window(); + +protected: + static Window *_singleton; + + struct GLFWwindow; + + GLFWwindow *window; + int w; + int h; + int xpos; + int ypos; + int paused; + int fullscreen; + int xprev; + int yprev; + int wprev; + int hprev; + uint64_t frame_count; + double t; + double dt; + double fps; + double hz; + char title[128]; + int locked_aspect_ratio; + Vector4 winbgcolor; +}; \ No newline at end of file diff --git a/sfw/time.cpp b/sfw/time.cpp deleted file mode 100644 index eb621a9..0000000 --- a/sfw/time.cpp +++ /dev/null @@ -1,170 +0,0 @@ -// ---------------------------------------------------------------------------- -// time - -#if 0 -uint64_t time_gpu() { - GLint64 t = 123456789; - glGetInteger64v(GL_TIMESTAMP, &t); - return (uint64_t)t; -} -#endif -uint64_t date() { - time_t epoch = time(0); - struct tm *ti = localtime(&epoch); - return atoi64(va("%04d%02d%02d%02d%02d%02d",ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec)); -} -char *date_string() { - time_t epoch = time(0); - struct tm *ti = localtime(&epoch); - return va("%04d-%02d-%02d %02d:%02d:%02d",ti->tm_year+1900,ti->tm_mon+1,ti->tm_mday,ti->tm_hour,ti->tm_min,ti->tm_sec); -} -uint64_t date_epoch() { - time_t epoch = time(0); - return epoch; -} -#if 0 -double time_ss() { - return glfwGetTime(); -} -double time_ms() { - return glfwGetTime() * 1000.0; -} -uint64_t time_us() { - return (uint64_t)(glfwGetTime() * 1000000.0); // @fixme: use a high resolution timer instead, or time_gpu below -} -uint64_t sleep_us(uint64_t us) { // @fixme: use a high resolution sleeper instead - return sleep_ms( us / 1000.0 ); -} -double sleep_ms(double ms) { - double now = time_ms(); - if( ms <= 0 ) { -#if is(win32) - Sleep(0); // yield -#else - usleep(0); -#endif - } else { -#if is(win32) - Sleep(ms); -#else - usleep(ms * 1000); -#endif - } - return time_ms() - now; -} -double sleep_ss(double ss) { - return sleep_ms( ss * 1000 ) / 1000.0; -} -#endif - -// high-perf functions - -#define TIMER_E3 1000ULL -#define TIMER_E6 1000000ULL -#define TIMER_E9 1000000000ULL - -#ifdef CLOCK_MONOTONIC_RAW -#define TIME_MONOTONIC CLOCK_MONOTONIC_RAW -#elif defined CLOCK_MONOTONIC -#define TIME_MONOTONIC CLOCK_MONOTONIC -#else -// #define TIME_MONOTONIC CLOCK_REALTIME // untested -#endif - -static uint64_t nanotimer(uint64_t *out_freq) { - if( out_freq ) { -#if is(win32) - LARGE_INTEGER li; - QueryPerformanceFrequency(&li); - *out_freq = li.QuadPart; -//#elif is(ANDROID) -// *out_freq = CLOCKS_PER_SEC; -#elif defined TIME_MONOTONIC - *out_freq = TIMER_E9; -#else - *out_freq = TIMER_E6; -#endif - } -#if is(win32) - LARGE_INTEGER li; - QueryPerformanceCounter(&li); - return (uint64_t)li.QuadPart; -//#elif is(ANDROID) -// return (uint64_t)clock(); -#elif defined TIME_MONOTONIC - struct timespec ts; - clock_gettime(TIME_MONOTONIC, &ts); - return (TIMER_E9 * (uint64_t)ts.tv_sec) + ts.tv_nsec; -#else - struct timeval tv; - gettimeofday(&tv, NULL); - return (TIMER_E6 * (uint64_t)tv.tv_sec) + tv.tv_usec; -#endif -} - -uint64_t time_ns() { - static __thread uint64_t epoch = 0; - static __thread uint64_t freq = 0; - if( !freq ) { - epoch = nanotimer(&freq); - } - - uint64_t a = nanotimer(NULL) - epoch; - uint64_t b = TIMER_E9; - uint64_t c = freq; - - // Computes (a*b)/c without overflow, as long as both (a*b) and the overall result fit into 64-bits. - // [ref] https://github.com/rust-lang/rust/blob/3809bbf47c8557bd149b3e52ceb47434ca8378d5/src/libstd/sys_common/mod.rs#L124 - uint64_t q = a / c; - uint64_t r = a % c; - return q * b + r * b / c; -} -uint64_t time_us() { - return time_ns() / TIMER_E3; -} -uint64_t time_ms() { - return time_ns() / TIMER_E6; -} -double time_ss() { - return time_ns() / 1e9; // TIMER_E9; -} -double time_mm() { - return time_ss() / 60; -} -double time_hh() { - return time_mm() / 60; -} - -void sleep_ns( double ns ) { -#if is(win32) - if( ns >= 100 ) { - LARGE_INTEGER li; // Windows sleep in 100ns units - HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); - li.QuadPart = (LONGLONG)(__int64)(-ns/100); // Negative for relative time - SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -#else - if( ns > 0 ) { - struct timespec wait = {0}; - wait.tv_sec = ns / 1e9; - wait.tv_nsec = ns - wait.tv_sec * 1e9; - nanosleep(&wait, NULL); -#endif - } else { -#if is(win32) - Sleep(0); // yield, Sleep(0), SwitchToThread -#else - usleep(0); -#endif - } -} -void sleep_us( double us ) { - sleep_ns(us * 1e3); -} -void sleep_ms( double ms ) { - sleep_ns(ms * 1e6); -} -void sleep_ss( double ss ) { - sleep_ns(ss * 1e9); -} diff --git a/sfw/time.h b/sfw/time.h deleted file mode 100644 index b341250..0000000 --- a/sfw/time.h +++ /dev/null @@ -1,21 +0,0 @@ -// ----------------------------------------------------------------------------- -// time framework utils -// - rlyeh, public domain. - -API uint64_t date(); // YYYYMMDDhhmmss -API uint64_t date_epoch(); // linux epoch -API char* date_string(); // "YYYY-MM-DD hh:mm:ss" -API double time_hh(); -API double time_mm(); -API double time_ss(); -API uint64_t time_ms(); -API uint64_t time_us(); -API uint64_t time_ns(); -API void sleep_ss(double ss); -API void sleep_ms(double ms); -API void sleep_us(double us); -API void sleep_ns(double us); - -API unsigned timer(unsigned ms, unsigned (*callback)(unsigned ms, void *arg), void *arg); -API void timer_destroy(unsigned timer_handle); -