Skip to content

Update the arcade window so it resets gl context properly everyframe. #2752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions arcade/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ def _dispatch_frame(self, delta_time: float) -> None:

# In case the window close in on_update, on_fixed_update or input callbacks
if not self.closed:
# Reset the context, camera, and active framebuffer.
self._ctx.reset()
self.draw(self._accumulated_draw_time)
self._accumulated_draw_time %= self._draw_rate

Expand Down
13 changes: 7 additions & 6 deletions arcade/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
Providing a multitude of camera's for any need.
"""

from arcade.camera.data_types import (
from .data_types import (
Projection,
Projector,
CameraData,
OrthographicProjectionData,
PerspectiveProjectionData,
)

from arcade.camera.projection_functions import (
from .projection_functions import (
generate_view_matrix,
generate_orthographic_matrix,
generate_perspective_matrix,
Expand All @@ -21,17 +21,18 @@
unproject_perspective,
)

from arcade.camera.orthographic import OrthographicProjector
from arcade.camera.perspective import PerspectiveProjector

from arcade.camera.camera_2d import Camera2D
from .viewport import ViewportProjector
from .orthographic import OrthographicProjector
from .perspective import PerspectiveProjector
from .camera_2d import Camera2D

import arcade.camera.grips as grips


__all__ = [
"Projection",
"Projector",
"ViewportProjector",
"CameraData",
"generate_view_matrix",
"OrthographicProjectionData",
Expand Down
98 changes: 5 additions & 93 deletions arcade/camera/default.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,19 @@
from __future__ import annotations

from collections.abc import Generator
from contextlib import contextmanager
from typing import TYPE_CHECKING

from pyglet.math import Mat4, Vec2, Vec3
from typing_extensions import Self
from pyglet.math import Mat4

from arcade.types import LBWH, Point, Rect
from arcade.window_commands import get_window
from arcade.types import LBWH

from .viewport import ViewportProjector

if TYPE_CHECKING:
from arcade.context import ArcadeContext

__all__ = ["ViewportProjector", "DefaultProjector"]


class ViewportProjector:
"""
A simple Projector which does not rely on any camera PoDs.

Does not have a way of moving, rotating, or zooming the camera.
perfect for something like UI or for mapping to an offscreen framebuffer.

Args:
viewport: The viewport to project to.
context: The window context to bind the camera to. Defaults to the currently active window.
"""

def __init__(
self,
viewport: Rect | None = None,
*,
context: ArcadeContext | None = None,
):
self._ctx: ArcadeContext = context or get_window().ctx
self._viewport: Rect = viewport or LBWH(*self._ctx.viewport)
self._projection_matrix: Mat4 = Mat4.orthogonal_projection(
0.0, self._viewport.width, 0.0, self._viewport.height, -100, 100
)

@property
def viewport(self) -> Rect:
"""
The viewport use to derive projection and view matrix.
"""
return self._viewport

@viewport.setter
def viewport(self, viewport: Rect) -> None:
self._viewport = viewport
self._projection_matrix = Mat4.orthogonal_projection(
0, viewport.width, 0, viewport.height, -100, 100
)

def use(self) -> None:
"""
Set the window's projection and view matrix.
Also sets the projector as the windows current camera.
"""
self._ctx.current_camera = self

self._ctx.viewport = self.viewport.lbwh_int # get the integer 4-tuple LBWH

self._ctx.view_matrix = Mat4()
self._ctx.projection_matrix = self._projection_matrix

@contextmanager
def activate(self) -> Generator[Self, None, None]:
"""
The context manager version of the use method.

usable with the 'with' block. e.g. 'with ViewportProjector.activate() as cam: ...'
"""
previous = self._ctx.current_camera
try:
self.use()
yield self
finally:
previous.use()

def project(self, world_coordinate: Point) -> Vec2:
"""
Take a Vec2 or Vec3 of coordinates and return the related screen coordinate
"""
x, y, *z = world_coordinate
return Vec2(x, y)

def unproject(self, screen_coordinate: Point) -> Vec3:
"""
Map the screen pos to screen_coordinates.

Due to the nature of viewport projector this does not do anything.
"""
x, y, *_z = screen_coordinate
z = 0.0 if not _z else _z[0]

return Vec3(x, y, z)
__all__ = ("DefaultProjector",)


# As this class is only supposed to be used internally
# I wanted to place an _ in front, but the linting complains
# about it being a protected class.
class DefaultProjector(ViewportProjector):
"""
An extremely limited projector which lacks any kind of control. This is only
Expand Down
99 changes: 99 additions & 0 deletions arcade/camera/viewport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from __future__ import annotations

from collections.abc import Generator
from contextlib import contextmanager
from typing import TYPE_CHECKING

from pyglet.math import Mat4, Vec2, Vec3
from typing_extensions import Self

from arcade.types import LBWH, Point, Rect
from arcade.window_commands import get_window

if TYPE_CHECKING:
from arcade.context import ArcadeContext

__all__ = ("ViewportProjector",)


class ViewportProjector:
"""
A simple Projector which does not rely on any camera PoDs.

Does not have a way of moving, rotating, or zooming the camera.
perfect for something like UI or for mapping to an offscreen framebuffer.

Args:
viewport: The viewport to project to.
context: The window context to bind the camera to. Defaults to the currently active window.
"""

def __init__(
self,
viewport: Rect | None = None,
*,
context: ArcadeContext | None = None,
):
self._ctx: ArcadeContext = context or get_window().ctx
self._viewport: Rect = viewport or LBWH(*self._ctx.viewport)
self._projection_matrix: Mat4 = Mat4.orthogonal_projection(
0.0, self._viewport.width, 0.0, self._viewport.height, -100, 100
)

@property
def viewport(self) -> Rect:
"""
The viewport use to derive projection and view matrix.
"""
return self._viewport

@viewport.setter
def viewport(self, viewport: Rect) -> None:
self._viewport = viewport
self._projection_matrix = Mat4.orthogonal_projection(
0, viewport.width, 0, viewport.height, -100, 100
)

def use(self) -> None:
"""
Set the window's projection and view matrix.
Also sets the projector as the windows current camera.
"""
self._ctx.current_camera = self

self._ctx.viewport = self.viewport.lbwh_int # get the integer 4-tuple LBWH

self._ctx.view_matrix = Mat4()
self._ctx.projection_matrix = self._projection_matrix

@contextmanager
def activate(self) -> Generator[Self, None, None]:
"""
The context manager version of the use method.

usable with the 'with' block. e.g. 'with ViewportProjector.activate() as cam: ...'
"""
previous = self._ctx.current_camera
try:
self.use()
yield self
finally:
previous.use()

def project(self, world_coordinate: Point) -> Vec2:
"""
Take a Vec2 or Vec3 of coordinates and return the related screen coordinate
"""
x, y, *z = world_coordinate
return Vec2(x, y)

def unproject(self, screen_coordinate: Point) -> Vec3:
"""
Map the screen pos to screen_coordinates.

Due to the nature of viewport projector this does not do anything.
"""
x, y, *_z = screen_coordinate
z = 0.0 if not _z else _z[0]

return Vec3(x, y, z)
7 changes: 2 additions & 5 deletions arcade/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,7 @@ def reset(self) -> None:
self.bind_window_block()
# self.active_program = None
self.viewport = 0, 0, self.window.width, self.window.height
self.view_matrix = Mat4()
self.projection_matrix = Mat4.orthogonal_projection(
0, self.window.width, 0, self.window.height, -100, 100
)
self._default_camera.use()
self.enable_only(self.BLEND)
self.blend_func = self.BLEND_DEFAULT
self.point_size = 1.0
Comment on lines 315 to 317
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reset method was more for unit tests. We can still probably trim it down. These lines should definitly not be called every frame but they should be called in conftest between tests except that blending needs to disable instead.

I also don't know if it's necessary to bind the windowblock every frame but that should be done in unit tests.

Expand Down Expand Up @@ -378,7 +375,7 @@ def viewport(self) -> tuple[int, int, int, int]:
@viewport.setter
def viewport(self, value: tuple[int, int, int, int]):
self.active_framebuffer.viewport = value
if self._default_camera == self.current_camera:
if self._default_camera is self.current_camera:
self._default_camera.use()

@property
Expand Down
3 changes: 3 additions & 0 deletions arcade/gl/framebuffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ def use(self, *, force: bool = False):
self._use(force=force)
self._ctx.active_framebuffer = self

# This is a hack to ensure the default camera has the correct viewport.
self._ctx.viewport = self.viewport

@abstractmethod
def _use(self, *, force: bool = False):
"""Internal use that do not change the global active framebuffer"""
Expand Down
Loading