1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
from pathlib import Path
from typing import Any, Optional
import moderngl
from moderngl_window.context.base import BaseWindow
from moderngl_window.context.headless.keys import Keys
class Window(BaseWindow):
"""Headless window.
Do not currently support any form window events or key input.
"""
#: Name of the window
name = "headless"
keys = Keys
def __init__(self, **kwargs: Any):
super().__init__(**kwargs)
self._fbo: Optional[moderngl.Framebuffer] = None
self._vsync = False # We don't care about vsync in headless mode
self._resizable = False # headless window is not resizable
self._cursor = False # Headless don't have a cursor
self._headless = True
self.init_mgl_context()
self.set_default_viewport()
@property
def fbo(self) -> moderngl.Framebuffer:
"""moderngl.Framebuffer: The default framebuffer"""
if self._fbo is None:
raise RuntimeError("No framebuffer created yet")
return self._fbo
def init_mgl_context(self) -> None:
"""Create an standalone context and framebuffer"""
if self._backend is not None:
self._ctx = moderngl.create_standalone_context(
require=self.gl_version_code,
backend=self._backend,
)
else:
self._ctx = moderngl.create_standalone_context(
require=self.gl_version_code,
)
self._create_fbo()
self.use()
def _create_fbo(self) -> None:
if self._fbo:
for attachment in self._fbo.color_attachments:
attachment.release()
if self._fbo.depth_attachment:
self._fbo.depth_attachment.release()
self._fbo.release()
self._fbo = self.ctx.framebuffer(
color_attachments=self.ctx.texture(self.size, 4, samples=self._samples),
depth_attachment=self.ctx.depth_texture(self.size, samples=self._samples),
)
@property
def size(self) -> tuple[int, int]:
"""tuple[int, int]: current window size.
This property also support assignment::
# Resize the window to 1000 x 1000
window.size = 1000, 1000
"""
return self._width, self._height
@size.setter
def size(self, value: tuple[int, int]) -> None:
if value == (self._width, self._height):
return
self._width, self._height = value
self._create_fbo()
def use(self) -> None:
"""Bind the window's framebuffer"""
if self._fbo is None:
raise RuntimeError("No framebuffer created yet")
self._fbo.use()
def clear(
self,
red: float = 0.0,
green: float = 0.0,
blue: float = 0.0,
alpha: float = 0.0,
depth: float = 1.0,
viewport: Optional[tuple[int, int, int, int]] = None,
) -> None:
"""
Binds and clears the default framebuffer
Args:
red (float): color component
green (float): color component
blue (float): color component
alpha (float): alpha component
depth (float): depth value
viewport (tuple): The viewport
"""
self.use()
self._ctx.clear(
red=red, green=green, blue=blue, alpha=alpha, depth=depth, viewport=viewport
)
def swap_buffers(self) -> None:
"""
Placeholder. We currently don't do double buffering in headless mode.
This may change in the future.
"""
# NOTE: No double buffering currently
self._frames += 1
self._ctx.finish()
def _set_icon(self, icon_path: Path) -> None:
"""Do nothing when icon is set"""
pass
def _set_fullscreen(self, value: bool) -> None:
"""Do nothing when fullscreen is toggled"""
pass
def _set_vsync(self, value: bool) -> None:
pass
def destroy(self) -> None:
"""Destroy the context"""
self._ctx.release()
|