# Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # NVIDIA CORPORATION and its licensors retain all intellectual property # and proprietary rights in and to this software, related documentation # and any modifications thereto. Any use, reproduction, disclosure or # distribution of this software and related documentation without an express # license agreement from NVIDIA CORPORATION is strictly prohibited. import time import glfw import OpenGL.GL as gl from . import gl_utils #---------------------------------------------------------------------------- class GlfwWindow: # pylint: disable=too-many-public-methods def __init__(self, *, title='GlfwWindow', window_width=1920, window_height=1080, deferred_show=True, close_on_esc=True): self._glfw_window = None self._drawing_frame = False self._frame_start_time = None self._frame_delta = 0 self._fps_limit = None self._vsync = None self._skip_frames = 0 self._deferred_show = deferred_show self._close_on_esc = close_on_esc self._esc_pressed = False self._drag_and_drop_paths = None self._capture_next_frame = False self._captured_frame = None # Create window. glfw.init() glfw.window_hint(glfw.VISIBLE, False) self._glfw_window = glfw.create_window(width=window_width, height=window_height, title=title, monitor=None, share=None) self._attach_glfw_callbacks() self.make_context_current() # Adjust window. self.set_vsync(False) self.set_window_size(window_width, window_height) if not self._deferred_show: glfw.show_window(self._glfw_window) def close(self): if self._drawing_frame: self.end_frame() if self._glfw_window is not None: glfw.destroy_window(self._glfw_window) self._glfw_window = None #glfw.terminate() # Commented out to play it nice with other glfw clients. def __del__(self): try: self.close() except: pass @property def window_width(self): return self.content_width @property def window_height(self): return self.content_height + self.title_bar_height @property def content_width(self): width, _height = glfw.get_window_size(self._glfw_window) return width @property def content_height(self): _width, height = glfw.get_window_size(self._glfw_window) return height @property def title_bar_height(self): _left, top, _right, _bottom = glfw.get_window_frame_size(self._glfw_window) return top @property def monitor_width(self): _, _, width, _height = glfw.get_monitor_workarea(glfw.get_primary_monitor()) return width @property def monitor_height(self): _, _, _width, height = glfw.get_monitor_workarea(glfw.get_primary_monitor()) return height @property def frame_delta(self): return self._frame_delta def set_title(self, title): glfw.set_window_title(self._glfw_window, title) def set_window_size(self, width, height): width = min(width, self.monitor_width) height = min(height, self.monitor_height) glfw.set_window_size(self._glfw_window, width, max(height - self.title_bar_height, 0)) if width == self.monitor_width and height == self.monitor_height: self.maximize() def set_content_size(self, width, height): self.set_window_size(width, height + self.title_bar_height) def maximize(self): glfw.maximize_window(self._glfw_window) def set_position(self, x, y): glfw.set_window_pos(self._glfw_window, x, y + self.title_bar_height) def center(self): self.set_position((self.monitor_width - self.window_width) // 2, (self.monitor_height - self.window_height) // 2) def set_vsync(self, vsync): vsync = bool(vsync) if vsync != self._vsync: glfw.swap_interval(1 if vsync else 0) self._vsync = vsync def set_fps_limit(self, fps_limit): self._fps_limit = int(fps_limit) def should_close(self): return glfw.window_should_close(self._glfw_window) or (self._close_on_esc and self._esc_pressed) def skip_frame(self): self.skip_frames(1) def skip_frames(self, num): # Do not update window for the next N frames. self._skip_frames = max(self._skip_frames, int(num)) def is_skipping_frames(self): return self._skip_frames > 0 def capture_next_frame(self): self._capture_next_frame = True def pop_captured_frame(self): frame = self._captured_frame self._captured_frame = None return frame def pop_drag_and_drop_paths(self): paths = self._drag_and_drop_paths self._drag_and_drop_paths = None return paths def draw_frame(self): # To be overridden by subclass. self.begin_frame() # Rendering code goes here. self.end_frame() def make_context_current(self): if self._glfw_window is not None: glfw.make_context_current(self._glfw_window) def begin_frame(self): # End previous frame. if self._drawing_frame: self.end_frame() # Apply FPS limit. if self._frame_start_time is not None and self._fps_limit is not None: delay = self._frame_start_time - time.perf_counter() + 1 / self._fps_limit if delay > 0: time.sleep(delay) cur_time = time.perf_counter() if self._frame_start_time is not None: self._frame_delta = cur_time - self._frame_start_time self._frame_start_time = cur_time # Process events. glfw.poll_events() # Begin frame. self._drawing_frame = True self.make_context_current() # Initialize GL state. gl.glViewport(0, 0, self.content_width, self.content_height) gl.glMatrixMode(gl.GL_PROJECTION) gl.glLoadIdentity() gl.glTranslate(-1, 1, 0) gl.glScale(2 / max(self.content_width, 1), -2 / max(self.content_height, 1), 1) gl.glMatrixMode(gl.GL_MODELVIEW) gl.glLoadIdentity() gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) # Pre-multiplied alpha. # Clear. gl.glClearColor(0, 0, 0, 1) gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) def end_frame(self): assert self._drawing_frame self._drawing_frame = False # Skip frames if requested. if self._skip_frames > 0: self._skip_frames -= 1 return # Capture frame if requested. if self._capture_next_frame: self._captured_frame = gl_utils.read_pixels(self.content_width, self.content_height) self._capture_next_frame = False # Update window. if self._deferred_show: glfw.show_window(self._glfw_window) self._deferred_show = False glfw.swap_buffers(self._glfw_window) def _attach_glfw_callbacks(self): glfw.set_key_callback(self._glfw_window, self._glfw_key_callback) glfw.set_drop_callback(self._glfw_window, self._glfw_drop_callback) def _glfw_key_callback(self, _window, key, _scancode, action, _mods): if action == glfw.PRESS and key == glfw.KEY_ESCAPE: self._esc_pressed = True def _glfw_drop_callback(self, _window, paths): self._drag_and_drop_paths = paths #----------------------------------------------------------------------------