# The contents of this file are subject to the Common Public Attribution License # Version 1.0 (the “License”); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla # Public License Version 1.1 but Sections 14 and 15 have been added to cover use # of software over a computer network and provide for limited attribution for # the Original Developer. In addition, Exhibit A has been modified to be # consistent with Exhibit B. # # Software distributed under the License is distributed on an “AS IS” basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for # the specific language governing rights and limitations under the License. # # The Original Code is Myou Engine. # # the Original Developer is the Initial Developer. # # The Initial Developer of the Original Code is the Myou Engine developers. # All portions of the code written by the Myou Engine developers are Copyright # (c) 2024. All Rights Reserved. # # Alternatively, the contents of this file may be used under the terms of the # GNU Affero General Public License version 3 (the [AGPL-3] License), in which # case the provisions of [AGPL-3] License are applicable instead of those above. # # If you wish to allow use of your version of this file only under the terms of # the [AGPL-3] License and not to allow others to use your version of this file # under the CPAL, indicate your decision by deleting the provisions above and # replace them with the notice and other provisions required by the [AGPL-3] # License. If you do not delete the provisions above, a recipient may use your # version of this file under either the CPAL or the [AGPL-3] License. import ../types import vmath except Quat import nglfw as glfw type Window* = glfw.Window # proc make_window*(width, height: int32, title: string): Window import std/strutils import ./gl import ../screen import ../util import ../graphics/render import ../input when compileOption("threads"): from loadable import terminateLoadableWorkerThreads from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads func c_strstr(haystack, needle: cstring): cstring {.importc: "strstr", header: "".} proc screen*(window: Window): Screen {.inline.} = cast[Screen](window.getWindowUserPointer) proc `screen=`*(window: Window, screen: Screen) {.inline.} = window.setWindowUserPointer cast[pointer](screen) if screen == nil: return discard window.setFramebufferSizeCallback proc(window: Window, width, height: cint) {.cdecl.} = window.screen.resize(width.int32, height.int32) var width, height: cint glfw.getFramebufferSize(window, width.addr, height.addr) screen.resize(width.int32, height.int32) var fist_window_is_used = false var windows: seq[Window] var to_be_closed: seq[Window] proc make_window*(width, height: int32, title: string): Window = if windows.len != 0 and not fist_window_is_used: fist_window_is_used = true return windows[0] let window = createWindow(width, height, title, nil, windows.get_or_default(0)) discard glfw.setWindowCloseCallback(window, proc(window: Window) {.cdecl.} = # TODO: flag to prevent it from being closed to_be_closed.add window ) discard glfw.setKeyCallback(window, proc(window: Window, key, scancode, action, mods: int32) {.cdecl.} = let screen = window.screen let e = KeyEvent( pressed: action.bool, repeat: action == 2, shiftKey: (mods and 1).bool, ctrlKey: (mods and 2).bool, altKey: (mods and 4).bool, metaKey: (mods and 8).bool, key: cast[KeyCode](key), ) for cb in screen.key_callbacks: cb(e) if screen.break_current_callbacks: screen.break_current_callbacks = false break ) discard glfw.setCharCallback(window, proc(window: Window, codepoint: uint32) {.cdecl.} = let screen = window.screen for cb in screen.char_callbacks: cb(codepoint) if screen.break_current_callbacks: screen.break_current_callbacks = false break ) discard glfw.setCursorPosCallback(window, proc(window: Window, x, y: float) {.cdecl.} = # TODO: when a mouse button is pressed do we need to poll the position # with glfw.getCursorPos until released? not needed on linux let screen = window.screen let buttons = screen.last_buttons # let mods = screen.last_mods # TODO: use bitpacked structs instead (with {.bitsize.}) let e = MouseMoveEvent( left: (buttons and 1).bool, middle: (buttons and 2).bool, right: (buttons and 4).bool, # shiftKey: (mods and 1).bool, # ctrlKey: (mods and 2).bool, # altKey: (mods and 4).bool, # metaKey: (mods and 8).bool, position: vec2(x,y), movement: vec2(x-screen.last_x, y-screen.last_y), ) for cb in screen.mouse_move_callbacks: cb(e) if screen.break_current_callbacks: screen.break_current_callbacks = false break screen.last_x = x screen.last_y = y ) discard glfw.setMouseButtonCallback(window, proc(window: Window, button, action, mods: int32) {.cdecl.} = let screen = window.screen let e = MouseButtonEvent( pressed: action.bool, button: cast[MouseButton](button), shiftKey: (mods and 1).bool, ctrlKey: (mods and 2).bool, altKey: (mods and 4).bool, metaKey: (mods and 8).bool, position: vec2(screen.last_x, screen.last_y), ) for cb in screen.mouse_button_callbacks: cb(e) if screen.break_current_callbacks: screen.break_current_callbacks = false break if action.bool: screen.last_buttons = screen.last_buttons or (1'i8 shl button) else: screen.last_buttons = screen.last_buttons and not (1'i8 shl button) ) discard glfw.setScrollCallback(window, proc(window: Window, x, y: float) {.cdecl.} = let screen = window.screen let e = MouseWheelEvent( movement: vec2(x,y), ) for cb in window.screen.mouse_wheel_callbacks: cb(e) if screen.break_current_callbacks: screen.break_current_callbacks = false break ) windows.add window return window proc set_vsync*(window: Window, vsync: bool) = # NOTE: it's global, not per window # NOTE: on some systems it may not be re-enabled after it's disabled glfw.swapInterval(if vsync: 1 else: 0) var max_messages = 0 proc init_graphics*(engine: MyouEngine, width, height: int32, title: string, opengl_version = 330, opengl_es = false, ) = # TODO!! Option to delay this to simulate the situation in mobile platforms # Init GLFW if not glfw.init(): raise newException(Exception, "Failed to Initialize GLFW") let major = opengl_version div 100 let minor = opengl_version mod 100 div 10 let rev = opengl_version mod 10 if opengl_es: glfw.windowHint(CLIENT_API, OPENGL_ES_API) glfw.windowHint(CONTEXT_VERSION_MAJOR, major.cint) glfw.windowHint(CONTEXT_VERSION_MINOR, minor.cint) glfw.windowHint(CONTEXT_REVISION, rev.cint) # ignored for ES glfw.windowHint(OPENGL_PROFILE, OPENGL_CORE_PROFILE) glfw.windowHint(OPENGL_FORWARD_COMPAT, 1) let window = make_window(width, height, title) window.makeContextCurrent() if not gladLoadGL(glfw.getProcAddress): echo "Could not initialize OpenGL" quit -1 when not defined(release): proc f(source: GLenum, etype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: cstring, userParam: pointer) {.stdcall.} = if id == 131185: # buffer usage hints return # if message == "GL_INVALID_OPERATION error generated. Target buffer must be bound.": # return if max_messages == 0: return # dump (source, etype, id, severity, length) when defined(myouForceAstc): # Supress warnings when forcing astc on desktop if c_strstr(message, "emulating compressed format") != nil: return echo getStackTrace().rsplit('\n',3)[1] echo "OpenGL error: ", message max_messages -= 1 if max_messages == 0: echo "No more OpenGL messages will be shown" glEnable(GL_DEBUG_OUTPUT) glDebugMessageCallback cast[GLdebugProc](f), nil glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS) max_messages = 100 when defined(GL_TEXTURE_CUBE_MAP_SEAMLESS): glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS) # glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST) # glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST) engine.renderer.initialize() var current_screen: Screen proc platform_switch_screen*(screen: Screen): bool {.inline.} = if current_screen != screen: cast[Window](screen.window).makeContextCurrent() current_screen = screen return true # proc platform_swap_buffers*(screen: Screen) {.inline.} = # cast[Window](screen.window).swapBuffers() proc start_platform_main_loop*(engine: MyouEngine, main_loop: proc(self: MyouEngine)) = while windows.len != 0: for window in to_be_closed: window.screen.destroy() windows.remove window break engine.main_loop() for window in windows: # TODO: move this call to inside main loop after each screen? for i in 0 ..< window.screen.frame_interval: window.swapBuffers() glfw.pollEvents() when compileOption("threads"): terminateLoadableWorkerThreads() terminateTextureWorkerThreads() glfw.terminate() proc myouAndroidGetActivity*(): pointer = assert false, "Not using Android" proc myouSetKeyboardVisible*(show: bool) = discard