#define AL_LOG_SECTION "glfw" #include #include #include "window_glfw.h" #include "common.h" #ifdef STELA_API_OPENGL #include "gl_common.h" #endif #ifdef STELA_API_OPENGL #define STELA_GL_API GLFW_OPENGL_API #define STELA_GLES_API GLFW_OPENGL_ES_API // Unused. #define STELA_GL_BIT 0 #define STELA_GLES2_BIT 0 #define STELA_GLES3_BIT 0 #define STELA_GL_CORE_PROFILE_BIT GLFW_OPENGL_CORE_PROFILE #include "gl_versions.h" PASTE_GL_VERSIONS() #endif f64 stl_scale_scroll(f64 v, f64 factor) { return v / (1.5 * factor); } static struct stl_glfw_global global = { 0 }; void stl_set_output_discovered_callback(void (*output_discovered)(void *, struct stl_monitor *), void *userdata) { (void)output_discovered; (void)userdata; } static void error_callback(s32 error_code, const char *description) { log_error("GLFW Error %d: %s.", error_code, description); } bool stl_global_init(bool skip_graphics) { (void)skip_graphics; glfwSetErrorCallback(error_callback); if (!glfwInit()) { log_error("Failed to initialize GLFW."); return false; } global.master_window = NULL; global.cursors[STELA_CURSOR_NORMAL] = NULL; global.cursors[STELA_CURSOR_MOVE] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); global.cursors[STELA_CURSOR_CROSSHAIR] = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR); return true; } s32 stl_global_get_poll_fd(void) { al_assert_and_return(-1); } void stl_global_process_events(void) { al_assert(false); } s32 stl_swallow_main(u32 argc, str *argv, s32 (*main)(u32, str *, void *), void *extra) { return main(argc, argv, extra); } void stl_global_close(void) { if (global.cursors[STELA_CURSOR_MOVE]) { glfwDestroyCursor(global.cursors[STELA_CURSOR_MOVE]); } if (global.cursors[STELA_CURSOR_CROSSHAIR]) { glfwDestroyCursor(global.cursors[STELA_CURSOR_CROSSHAIR]); } glfwTerminate(); } static void resize_callback(GLFWwindow *window, s32 event_width, s32 event_height) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); if (event_width <= 0 || event_height <= 0) { // This happens on Windows when minimizing. return; } u32 width = (u32)event_width; u32 height = (u32)event_height; if (width == glfw->w.width && height == glfw->w.height) { return; } glfw->w.width = (u32)width; glfw->w.height = (u32)height; stl_window_send_resize_event(&glfw->w, glfw->w.width, glfw->w.height); } static void content_scale_callback(GLFWwindow *window, f32 xscale, f32 yscale) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); glfw->scale_x = xscale; glfw->scale_y = yscale; } static void cursor_position_callback(GLFWwindow *window, f64 xpos, f64 ypos) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); xpos *= glfw->scale_x; ypos *= glfw->scale_y; stl_window_send_pointer_pos_event(&glfw->w, xpos, ypos); } // @TODO: Test behavior on x11, Windows? static void cursor_enter_callback(GLFWwindow *window, s32 entered) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); if (entered) { if (glfwGetKey(glfw->window, GLFW_KEY_LEFT_CONTROL)) { } } } static void scroll_callback(GLFWwindow *window, f64 xoffset, f64 yoffset) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); (void)xoffset; stl_window_send_scroll_event(&glfw->w, yoffset); } static void mouse_button_callback(GLFWwindow *window, s32 button, s32 action, s32 mods) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); (void)mods; switch (button) { case GLFW_MOUSE_BUTTON_1: if (action == GLFW_PRESS) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_PRESSED, STELA_MOUSE1); } else if (action == GLFW_RELEASE) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_RELEASED, STELA_MOUSE1); } break; case GLFW_MOUSE_BUTTON_2: if (action == GLFW_PRESS) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_PRESSED, STELA_MOUSE2); } else if (action == GLFW_RELEASE) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_RELEASED, STELA_MOUSE2); } break; case GLFW_MOUSE_BUTTON_3: if (action == GLFW_PRESS) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_PRESSED, STELA_MOUSE3); } else if (action == GLFW_RELEASE) { stl_window_send_mouse_button_event(&glfw->w, STELA_BUTTON_RELEASED, STELA_MOUSE3); } break; } } static void key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 mods) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); (void)scancode; (void)mods; al_assert(key >= 0 && key <= UINT16_MAX); if (key == GLFW_KEY_F25 || key == GLFW_KEY_WORLD_1 || key == GLFW_KEY_WORLD_2) { log_warn("Unmapped GLFW key pressed (%hu).", (u16)key); return; } u8 mapped_state = action == GLFW_PRESS ? STELA_BUTTON_PRESSED : STELA_BUTTON_RELEASED; stl_window_send_key_immediate_event(&glfw->w, mapped_state, (u16)key); stl_window_send_key_event(&glfw->w, mapped_state, (u16)key); } static void close_callback(GLFWwindow *window) { struct stl_window_glfw *glfw = glfwGetWindowUserPointer(window); stl_window_send_should_close_event(&glfw->w); } static bool window_glfw_create_window(struct stl_window *window, u32 width, u32 height, u32 flags, const char *monitor, const char *name, const char *app_id) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; (void)monitor; #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4) glfwWindowHintString(GLFW_WAYLAND_APP_ID, app_id); #endif glfwWindowHintString(GLFW_X11_CLASS_NAME, app_id); #if defined STELA_API_VULKAN if (!glfwVulkanSupported()) { log_error("Vulkan not supported."); return false; } glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); #elif defined STELA_API_DX11 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); #elif defined STELA_API_OPENGL #ifdef STELA_USE_EGL glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); #else glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); #endif for (u32 i = 0; i < ARRAY_SIZE(stl_gl_vers); i++) { glfwWindowHint(GLFW_CLIENT_API, stl_gl_vers[i].api); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, stl_gl_vers[i].major); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, stl_gl_vers[i].minor); if (stl_gl_vers[i].api == STELA_GL_API) { glfwWindowHint(GLFW_OPENGL_PROFILE, stl_gl_vers[i].profile); } if (stl_gl_vers[i].major >= 3) { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); } log_debug("Attempting to create a %s context of version %d.%d (GLSL %d).", stl_gl_vers[i].api == STELA_GL_API ? "GL" : "GLES", stl_gl_vers[i].major, stl_gl_vers[i].minor, stl_gl_vers[i].glsl_ver); #endif al_assert(width >= STELA_MIN_WIDTH && height >= STELA_MIN_HEIGHT); glfw->window = glfwCreateWindow(width, height, name, NULL, global.master_window); #ifdef STELA_API_OPENGL if (glfw->window) break; } #endif if (!glfw->window) { log_info("Failed to create window/context."); return false; } if (!global.master_window) { // This doesn't work right, at least on wayland. global.master_window = glfw->window; } /* if (glfwRawMouseMotionSupported()) { glfwSetInputMode(glfw->window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); } */ #if defined STELA_API_VULKAN (void)flags; #elif defined STELA_API_DX11 (void)flags; glfw->w.win32_window = glfwGetWin32Window(glfw->window); #elif defined STELA_API_OPENGL #ifdef STELA_USE_EGL glfw->w.egl_display = glfwGetEGLDisplay(); glfw->w.egl_context = glfwGetEGLContext(glfw->window); #endif glfwMakeContextCurrent(glfw->window); glfwSwapInterval((flags & STELA_WINDOW_VSYNC) ? 1 : 0); glfwMakeContextCurrent(NULL); #else (void)flags; #endif s32 buffer_width, buffer_height; //glfwGetFramebufferSize(glfw->window, &buffer_width, &buffer_height); glfwGetWindowSize(glfw->window, &buffer_width, &buffer_height); al_assert(buffer_width > 0 && buffer_height > 0); stl_window_created(&glfw->w, (u32)buffer_width, (u32)buffer_height, flags); glfwSetWindowSizeLimits(glfw->window, STELA_MIN_WIDTH, STELA_MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE); glfw->scale_x = 1.f; glfw->scale_y = 1.f; glfw->prev.x = 0; glfw->prev.y = 0; glfw->prev.width = 0; glfw->prev.height = 0; glfwSetWindowUserPointer(glfw->window, glfw); //glfwSetFramebufferSizeCallback(glfw->window, resize_callback); glfwSetWindowSizeCallback(glfw->window, resize_callback); glfwSetWindowContentScaleCallback(glfw->window, content_scale_callback); glfwSetCursorPosCallback(glfw->window, cursor_position_callback); glfwSetCursorEnterCallback(glfw->window, cursor_enter_callback); glfwSetScrollCallback(glfw->window, scroll_callback); glfwSetMouseButtonCallback(glfw->window, mouse_button_callback); glfwSetKeyCallback(glfw->window, key_callback); // A Wayland window will trigger CloseCallback if it's lagging too hard. glfwSetWindowCloseCallback(glfw->window, close_callback); return true; } static void window_glfw_resize_internal(struct stl_window *window, u32 width, u32 height) { (void)window; (void)width; (void)height; } static void window_glfw_resize(struct stl_window *window, u32 width, u32 height) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; glfwSetWindowAspectRatio(glfw->window, width, height); glfwSetWindowSize(glfw->window, width, height); } // https://stackoverflow.com/a/31526753 static GLFWmonitor *get_current_monitor(GLFWwindow *window, s32 wx, s32 wy) { #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4) if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { // Calculating the current monitor doesn't work on Wayland because // we can't know the position of the window. return glfwGetPrimaryMonitor(); } #endif s32 monitor_count; GLFWmonitor **monitors = glfwGetMonitors(&monitor_count); if (monitor_count == 0) return NULL; GLFWmonitor *best_monitor = monitors[0]; s32 ww, wh; glfwGetWindowSize(window, &ww, &wh); const GLFWvidmode *mode; s32 mx, my; s32 mw, mh; s32 overlap; s32 best_overlap = 0; for (s32 i = 0; i < monitor_count; i++) { mode = glfwGetVideoMode(monitors[i]); glfwGetMonitorPos(monitors[i], &mx, &my); mw = mode->width; mh = mode->height; s32 dx = MIN(wx + ww, mx + mw) - MAX(wx, mx); s32 dy = MIN(wy + wh, my + mh) - MAX(wy, my); overlap = MAX(0, dx) * MAX(0, dy); if (best_overlap < overlap) { best_overlap = overlap; best_monitor = monitors[i]; } } return best_monitor; } static void window_glfw_toggle_fullscreen(struct stl_window *window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; if (!glfw->w.fullscreen) { #if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4) if (glfwGetPlatform() != GLFW_PLATFORM_WAYLAND) { glfwGetWindowPos(glfw->window, &glfw->prev.x, &glfw->prev.y); } #endif GLFWmonitor *monitor = get_current_monitor(glfw->window, glfw->prev.x, glfw->prev.y); if (!monitor) return; const GLFWvidmode *mode = glfwGetVideoMode(monitor); glfw->prev.width = glfw->w.width; glfw->prev.height = glfw->w.height; glfwSetWindowMonitor(glfw->window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE); } else { glfwSetWindowMonitor(glfw->window, NULL, glfw->prev.x, glfw->prev.y, glfw->prev.width, glfw->prev.height, GLFW_DONT_CARE); } glfw->w.fullscreen = !glfw->w.fullscreen; } static void window_glfw_set_cursor(struct stl_window *window, u8 shape) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; if (shape == STELA_CURSOR_HIDDEN) { glfwSetCursor(glfw->window, NULL); glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); return; } glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glfwSetCursor(glfw->window, global.cursors[shape]); } static void window_glfw_set_decorations(struct stl_window *window, bool enabled) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; glfwSetWindowAttrib(glfw->window, GLFW_DECORATED, enabled ? GLFW_TRUE : GLFW_FALSE); } #ifdef STELA_POLL_INLINE #ifdef STELA_EVENT_BUFFER #error "window_glfw_poll() is incompatible with event buffer mode." #endif static bool window_glfw_poll(struct stl_window *window, bool block) { (void)window; if (block) glfwWaitEvents(); else glfwPollEvents(); return true; } #ifdef STELA_PAUSE static void window_glfw_wake(struct stl_window *window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; glfw->w.pending_refresh = true; glfwPostEmptyEvent(); } #endif #endif static void window_glfw_process_events(struct stl_window *window) { (void)window; } #ifdef STELA_PAUSE static bool window_glfw_should_refresh(struct stl_window *window) { return stl_should_refresh_common(window); } #endif static void window_glfw_free(struct stl_window **window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)*window; if (glfw->window) { glfwDestroyWindow(glfw->window); } al_free(glfw); *window = NULL; } #if defined STELA_API_VULKAN static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_glfw_get_vk_proc_address(VkInstance inst, const char *name) { return (PFN_vkVoidFunction)glfwGetInstanceProcAddress(inst, name); } static VkResult window_glfw_vk_create_surface(void *window, VkInstance inst, VkSurfaceKHR *surface) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; return glfwCreateWindowSurface(inst, glfw->window, NULL, surface); } static VkResult window_glfw_vk_destroy_surface(VkInstance inst, VkSurfaceKHR surface) { return stl_vk_destroy_surface(inst, surface); } static const char *const *window_glfw_vk_get_extensions(u32 *num) { return glfwGetRequiredInstanceExtensions(num); } #elif defined STELA_API_OPENGL static bool window_glfw_gl_loader_load(void *window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; return stl_gl_loader_load(glfw->w.get_gl_proc_address); } static bool window_glfw_gl_make_current(void *window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; glfwMakeContextCurrent(glfw->window); return true; } static void window_glfw_gl_release_current(void *window) { (void)window; glfwMakeContextCurrent(NULL); } static void window_glfw_gl_swap_buffers(void *window) { struct stl_window_glfw *glfw = (struct stl_window_glfw *)window; glfwSwapBuffers(glfw->window); } #endif struct stl_window *stl_window_create(void) { log_info("Creating GLFW3 window."); struct stl_window_glfw *glfw = al_alloc_object(struct stl_window_glfw); stl_window_init(&glfw->w); glfw->w.fullscreen = false; glfw->w.create_window = window_glfw_create_window; glfw->w.resize_internal = window_glfw_resize_internal; glfw->w.resize = window_glfw_resize; glfw->w.toggle_fullscreen = window_glfw_toggle_fullscreen; glfw->w.set_cursor = window_glfw_set_cursor; glfw->w.set_decorations = window_glfw_set_decorations; #ifdef STELA_POLL_INLINE glfw->w.poll = window_glfw_poll; #ifdef STELA_PAUSE glfw->w.wake = window_glfw_wake; #endif #endif glfw->w.process_events = window_glfw_process_events; #ifdef STELA_PAUSE glfw->w.should_refresh = window_glfw_should_refresh; #endif glfw->w.free = window_glfw_free; #if defined STELA_API_VULKAN glfw->w.get_vk_proc_address = window_glfw_get_vk_proc_address; glfw->w.vk_create_surface = window_glfw_vk_create_surface; glfw->w.vk_destroy_surface = window_glfw_vk_destroy_surface; glfw->w.vk_get_extensions = window_glfw_vk_get_extensions; #elif defined STELA_API_OPENGL glfw->w.get_gl_proc_address = glfwGetProcAddress; glfw->w.gl_loader_load = window_glfw_gl_loader_load; glfw->w.gl_make_current = window_glfw_gl_make_current; glfw->w.gl_release_current = window_glfw_gl_release_current; glfw->w.gl_swap_buffers = window_glfw_gl_swap_buffers; #endif return (struct stl_window *)glfw; }