Skip to content

Commit ee7be81

Browse files
committed
Rewrite: Isolate GLFW to its own window module
We want to support windows using other packages than glfw including offscreen rendering.
1 parent f7f63dc commit ee7be81

File tree

5 files changed

+229
-209
lines changed

5 files changed

+229
-209
lines changed

demosys/context/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import moderngl
2+
from .base import Window
23

34
# Window instance shortcut
45
WINDOW = None # noqa
56

67

8+
def window() -> Window:
9+
"""The window instance we are rendering to"""
10+
return WINDOW
11+
12+
713
def ctx() -> moderngl.Context:
8-
"""Get the moderngl context for the window"""
14+
"""ModernGL context"""
915
return WINDOW.ctx

demosys/context/base.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
1+
import sys
2+
from collections import namedtuple
13

4+
import moderngl as mgl
5+
from demosys.conf import settings
26

3-
class Context:
7+
GLVersion = namedtuple('GLVersion', ['major', 'minor'])
8+
9+
10+
class Window:
11+
12+
def __init__(self):
13+
"""
14+
Base window intializer
15+
16+
:param width: window width
17+
:param height: window height
18+
"""
19+
self.width = settings.WINDOW['size'][0]
20+
self.height = settings.WINDOW['size'][1]
21+
22+
self.buffer_width = self.width
23+
self.buffer_height = self.height
24+
25+
self.fbo = None
26+
self.sys_camera = None
27+
self.timer = None
28+
self.resources = None
29+
30+
self.gl_version = GLVersion(*settings.OPENGL['version'])
31+
self.resizable = settings.WINDOW.get('resizable') or False
32+
self.title = settings.WINDOW.get('title') or "demosys-py"
33+
self.aspect_ratio = settings.WINDOW.get('aspect_ratio', 16 / 9)
34+
35+
self.resizable = settings.WINDOW.get('resizable')
36+
self.fullscreen = settings.WINDOW.get('fullscreen')
37+
self.vsync = settings.WINDOW.get('vsync')
38+
self.cursor = settings.WINDOW.get('cursor')
439

5-
def __init__(self, width=0, height=0):
6-
# window size
7-
self.width = width
8-
self.height = height
9-
# Actual window buffer size. Needs adjustment for retina and 4k resolution by child
10-
self.buffer_width = width
11-
self.buffer_height = height
1240
# ModernGL context
1341
self.ctx = None
1442

43+
def clear(self):
44+
"""Clear the scren"""
45+
self.ctx.clear(
46+
red=0.0, blue=0.0, green=0.0, alpha=0.0, depth=1.0,
47+
viewport=(0, 0, self.buffer_width, self.buffer_height)
48+
)
49+
50+
def viewport(self):
51+
self.fbo.use()
52+
1553
def swap_buffers(self):
1654
"""Swap frame buffer"""
1755
raise NotImplementedError()
@@ -21,12 +59,24 @@ def resize(self, width, height):
2159
raise NotImplementedError()
2260

2361
def close(self):
24-
"""Hits the window to close"""
62+
"""Set the close state"""
2563
raise NotImplementedError()
2664

2765
def should_close(self):
2866
"""Check if window should close"""
2967
raise NotImplementedError()
3068

3169
def terminate(self):
70+
"""Cleanup after close"""
3271
raise NotImplementedError()
72+
73+
def print_context_info(self):
74+
"""Prints out context info"""
75+
print("Context Version:")
76+
print('ModernGL:', mgl.__version__)
77+
print('vendor:', self.ctx.info['GL_VENDOR'])
78+
print('renderer:', self.ctx.info['GL_RENDERER'])
79+
print('version:', self.ctx.info['GL_VERSION'])
80+
print('python:', sys.version)
81+
print('platform:', sys.platform)
82+
print('code:', self.ctx.version_code)

demosys/context/glfw.py

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,33 @@
1-
import sys
2-
31
import glfw
42

53
import moderngl as mgl
6-
from demosys.conf import settings
4+
from demosys.scene import camera
5+
from demosys.view import screenshot
76

8-
from .base import Context
7+
from .base import Window
98

109

11-
class GLTFWindow(Context):
10+
class GLTFWindow(Window):
1211
min_glfw_version = (3, 2, 1)
1312

1413
def __init__(self):
15-
super().__init__(
16-
width=settings.WINDOW['size'][0],
17-
height=settings.WINDOW['size'][1],
18-
)
19-
self.resizable = settings.WINDOW.get('resizable') or False
20-
self.title = settings.WINDOW.get('title') or "demosys-py"
21-
self.aspect_ratio = settings.WINDOW.get('aspect_ratio', 16 / 9)
14+
super().__init__()
2215

2316
if not glfw.init():
2417
raise ValueError("Failed to initialize glfw")
2518

2619
self.check_glfw_version()
2720

28-
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, settings.OPENGL['version'][0])
29-
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, settings.OPENGL['version'][1])
30-
21+
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, self.gl_version.major)
22+
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, self.gl_version.minor)
3123
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
3224
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
33-
34-
if not settings.WINDOW.get('resizable'):
35-
glfw.window_hint(glfw.RESIZABLE, False)
36-
25+
glfw.window_hint(glfw.RESIZABLE, self.resizable)
3726
glfw.window_hint(glfw.DOUBLEBUFFER, True)
3827
glfw.window_hint(glfw.DEPTH_BITS, 24)
3928

4029
monitor = None
41-
if settings.WINDOW.get('fullscreen'):
30+
if self.fullscreen:
4231
# Use the primary monitors current resolution
4332
monitor = glfw.get_primary_monitor()
4433
mode = glfw.get_video_mode(monitor)
@@ -53,33 +42,28 @@ def __init__(self):
5342
glfw.terminate()
5443
raise ValueError("Failed to create window")
5544

56-
if not settings.WINDOW.get('cursor'):
45+
if not self.cursor:
5746
glfw.set_input_mode(self.window, glfw.CURSOR, glfw.CURSOR_DISABLED)
5847

5948
# Get the actual buffer size of the window
6049
# This is important for some displays like Apple's Retina as reported window sizes are virtual
6150
self.buffer_width, self.buffer_height = glfw.get_framebuffer_size(self.window)
6251
print("Frame buffer size:", self.buffer_width, self.buffer_height)
63-
6452
print("Actual window size:", glfw.get_window_size(self.window))
6553

6654
glfw.make_context_current(self.window)
6755

6856
# The number of screen updates to wait from the time glfwSwapBuffers
6957
# was called before swapping the buffers and returning
70-
if settings.WINDOW.get('vsync'):
58+
if self.vsync:
7159
glfw.swap_interval(1)
7260

61+
glfw.set_key_callback(self.window, self.key_event_callback)
62+
glfw.set_cursor_pos_callback(self.window, self.mouse_event_callback)
63+
# glfw.set_window_size_callback(self.window, window_resize_callback)
64+
7365
# Create mederngl context from existing context
7466
self.ctx = mgl.create_context()
75-
print("Context Version:")
76-
print('ModernGL:', mgl.__version__)
77-
print('vendor:', self.ctx.info['GL_VENDOR'])
78-
print('renderer:', self.ctx.info['GL_RENDERER'])
79-
print('version:', self.ctx.info['GL_VERSION'])
80-
print('python:', sys.version)
81-
print('platform:', sys.platform)
82-
print('code:', self.ctx.version_code)
8367

8468
def should_close(self):
8569
return glfw.window_should_close(self.window)
@@ -89,6 +73,7 @@ def close(self):
8973

9074
def swap_buffers(self):
9175
glfw.swap_buffers(self.window)
76+
self.poll_events()
9277

9378
def resize(self, width, height):
9479
self.width = width
@@ -108,3 +93,95 @@ def check_glfw_version(self):
10893
print("glfw version: {} (python wrapper version {})".format(glfw.get_version(), glfw.__version__))
10994
if glfw.get_version() < self.min_glfw_version:
11095
raise ValueError("Please update glfw binaries to version {} or later".format(self.min_glfw_version))
96+
97+
def key_event_callback(self, window, key, scancode, action, mods):
98+
"""
99+
Key event callback for glfw
100+
s
101+
:param window: Window event origin
102+
:param key: The keyboard key that was pressed or released.
103+
:param scancode: The system-specific scancode of the key.
104+
:param action: GLFW_PRESS, GLFW_RELEASE or GLFW_REPEAT
105+
:param mods: Bit field describing which modifier keys were held down.
106+
"""
107+
# print("Key event:", key, scancode, action, mods)
108+
109+
# The well-known standard key for quick exit
110+
if key == glfw.KEY_ESCAPE:
111+
self.close()
112+
return
113+
114+
# Toggle pause time
115+
if key == glfw.KEY_SPACE and action == glfw.PRESS:
116+
self.timer.toggle_pause()
117+
118+
# Camera movement
119+
# Right
120+
if key == glfw.KEY_D:
121+
if action == glfw.PRESS:
122+
self.sys_camera.move_state(camera.RIGHT, True)
123+
elif action == glfw.RELEASE:
124+
self.sys_camera.move_state(camera.RIGHT, False)
125+
# Left
126+
elif key == glfw.KEY_A:
127+
if action == glfw.PRESS:
128+
self.sys_camera.move_state(camera.LEFT, True)
129+
elif action == glfw.RELEASE:
130+
self.sys_camera.move_state(camera.LEFT, False)
131+
# Forward
132+
elif key == glfw.KEY_W:
133+
if action == glfw.PRESS:
134+
self.sys_camera.move_state(camera.FORWARD, True)
135+
if action == glfw.RELEASE:
136+
self.sys_camera.move_state(camera.FORWARD, False)
137+
# Backwards
138+
elif key == glfw.KEY_S:
139+
if action == glfw.PRESS:
140+
self.sys_camera.move_state(camera.BACKWARD, True)
141+
if action == glfw.RELEASE:
142+
self.sys_camera.move_state(camera.BACKWARD, False)
143+
144+
# UP
145+
elif key == glfw.KEY_Q:
146+
if action == glfw.PRESS:
147+
self.sys_camera.move_state(camera.UP, True)
148+
if action == glfw.RELEASE:
149+
self.sys_camera.move_state(camera.UP, False)
150+
151+
# Down
152+
elif key == glfw.KEY_E:
153+
if action == glfw.PRESS:
154+
self.sys_camera.move_state(camera.DOWN, True)
155+
if action == glfw.RELEASE:
156+
self.sys_camera.move_state(camera.DOWN, False)
157+
158+
# Screenshots
159+
if key == glfw.KEY_X and action == glfw.PRESS:
160+
screenshot.create()
161+
162+
if key == glfw.KEY_R and action == glfw.PRESS:
163+
self.resources.shaders.reload()
164+
165+
# Forward the event to the effect manager
166+
self.manager.key_event(key, scancode, action, mods)
167+
168+
def mouse_event_callback(self, window, xpos, ypos):
169+
"""
170+
Mouse event callback from glfw
171+
172+
:param window: The window
173+
:param xpos: viewport x pos
174+
:param ypos: viewport y pos
175+
"""
176+
self.sys_camera.rot_state(xpos, ypos)
177+
178+
def window_resize_callback(self, window, width, height):
179+
"""
180+
Window resize callback for glfw
181+
182+
:param window: The window
183+
:param width: New width
184+
:param height: New height
185+
"""
186+
print("Resize", width, height)
187+
self.resize(width, height)

demosys/opengl/fbo.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,31 @@
33
from demosys import context
44
from demosys.opengl import DepthTexture, Texture2D
55

6-
WINDOW_FBO = None
7-
86

97
class WindowFBO:
10-
"""Fake FBO representing default render target"""
11-
def __init__(self, window):
12-
self.window = window
13-
self.ctx = context.ctx()
8+
window = None
149

15-
def use(self):
10+
@classmethod
11+
def use(cls):
1612
"""Sets the viewport back to the buffer size of the screen/window"""
1713
# The expected height with the current viewport width
18-
expected_height = int(self.window.buffer_width / self.window.aspect_ratio)
14+
expected_height = int(cls.window.buffer_width / cls.window.aspect_ratio)
1915

2016
# How much positive or negative y padding
21-
blank_space = self.window.buffer_height - expected_height
17+
blank_space = cls.window.buffer_height - expected_height
2218

23-
self.ctx.screen.use()
24-
self.ctx.viewport = (0, blank_space // 2, self.window.buffer_width, expected_height)
19+
cls.window.ctx.screen.use()
20+
cls.window.ctx.viewport = (0, blank_space // 2, cls.window.buffer_width, expected_height)
2521

26-
def release(self):
22+
@classmethod
23+
def release(cls):
2724
"""Dummy release method"""
2825
pass
2926

30-
def clear(self, red=0.0, green=0.0, blue=0.0, depth=1.0, viewport=None):
27+
@classmethod
28+
def clear(cls, red=0.0, green=0.0, blue=0.0, depth=1.0, viewport=None):
3129
"""Dummy clear method"""
32-
self.ctx.screen.clear(red=red, green=green, blue=blue, depth=depth, viewport=viewport)
30+
cls.ctx.screen.clear(red=red, green=green, blue=blue, depth=depth, viewport=viewport)
3331

3432

3533
class FBO:
@@ -167,7 +165,7 @@ def release(self, stack=True):
167165
:param stack: (bool) If the bind should be popped form the FBO stack.
168166
"""
169167
if not stack:
170-
WINDOW_FBO.use()
168+
WindowFBO.use()
171169
return
172170

173171
# Are we trying to release an FBO that is not bound?
@@ -184,7 +182,7 @@ def release(self, stack=True):
184182
if FBO._stack:
185183
parent = FBO._stack[-1]
186184
else:
187-
parent = WINDOW_FBO
185+
parent = WindowFBO
188186

189187
# Bind the parent FBO
190188
if parent:

0 commit comments

Comments
 (0)