From b48e20c9f08d9e3e7a37a44868bf2a8e3f75ca05 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Sun, 27 Apr 2025 19:51:54 +0200 Subject: [PATCH 1/3] GUI surface shader work --- arcade/gui/surface.py | 64 +++++++++++++++++-- .../system/shaders/gui/surface_gs.glsl | 60 ----------------- .../system/shaders/gui/surface_vs.glsl | 13 +++- 3 files changed, 69 insertions(+), 68 deletions(-) delete mode 100644 arcade/resources/system/shaders/gui/surface_gs.glsl diff --git a/arcade/gui/surface.py b/arcade/gui/surface.py index 2376ec7bcf..b597b9f650 100644 --- a/arcade/gui/surface.py +++ b/arcade/gui/surface.py @@ -1,14 +1,16 @@ +from array import array from contextlib import contextmanager from typing import Generator from PIL import Image +from pyglet.math import Vec2, Vec4 from typing_extensions import Self import arcade from arcade import Texture from arcade.camera import CameraData, OrthographicProjectionData, OrthographicProjector from arcade.color import TRANSPARENT_BLACK -from arcade.gl import Framebuffer +from arcade.gl import BufferDescription, Framebuffer from arcade.gui.nine_patch import NinePatchTexture from arcade.types import LBWH, RGBA255, Point, Rect @@ -53,12 +55,17 @@ def __init__( *self.ctx.BLEND_DEFAULT, ) - self._geometry = self.ctx.geometry() + # 5 floats per vertex (pos 3f, tex 2f) with 4 vertices + self._buffer = self.ctx.buffer(reserve=4 * 5 * 4) + self._geometry = self.ctx.geometry( + content=[BufferDescription(self._buffer, "3f 2f", ["in_pos", "in_uv"])], + mode=self.ctx.TRIANGLE_STRIP, + ) self._program = self.ctx.load_program( vertex_shader=":system:shaders/gui/surface_vs.glsl", - geometry_shader=":system:shaders/gui/surface_gs.glsl", fragment_shader=":system:shaders/gui/surface_fs.glsl", ) + self._update_geometry() self._cam = OrthographicProjector( view=CameraData(), @@ -228,6 +235,8 @@ def draw( area: Limit the area in the surface we're drawing (l, b, w, h) """ + self._update_geometry(area=area) + # Set blend function blend_func = self.ctx.blend_func self.ctx.blend_func = self.blend_func_render @@ -239,10 +248,7 @@ def draw( self.texture.filter = self.ctx.LINEAR, self.ctx.LINEAR self.texture.use(0) - self._program["pos"] = self._pos - self._program["size"] = self._size - self._program["area"] = (0, 0, *self._size) if not area else area.lbwh - self._geometry.render(self._program, vertices=1) + self._geometry.render(self._program) # Restore blend function self.ctx.blend_func = blend_func @@ -267,3 +273,47 @@ def resize(self, *, size: tuple[int, int], pixel_ratio: float) -> None: def to_image(self) -> Image.Image: """Convert the surface to an PIL image""" return self.ctx.get_framebuffer_image(self.fbo) + + def _update_geometry(self, area: Rect | None = None) -> None: + """ + Update the internal geometry of the surface mesh. + + The geometry is a triangle strip with 4 verties. + """ + if area is None: + area = LBWH(0, 0, *self.size) + + # Clamp the area inside the surface + # This is the local area inside the surface + _size = Vec2(*self.size) + _pos = Vec2(area.left, area.bottom) + _area_size = Vec2(area.width, area.height) + + b1 = _pos.clamp(Vec2(0.0), _size) + end_point = _pos + _area_size + b2 = end_point.clamp(Vec2(0.0), _size) + b = b2 - b1 + l_area = Vec4(b1.x, b1.y, b.x, b.y) + + # Create the 4 corners of the rectangle + # These are the final/global coordinates rendered + p_ll = _pos + l_area.xy # type: ignore + p_lr = _pos + l_area.xy + Vec2(l_area.z, 0.0) # type: ignore + p_ul = _pos + l_area.xy + Vec2(0.0, l_area.w) # type: ignore + p_ur = _pos + l_area.xy + l_area.zw # type: ignore + + # Calculate the UV coordinates + bottom = l_area.y / _size.y + left = l_area.x / _size.x + top = (l_area.y + l_area.w) / _size.y + right = (l_area.x + l_area.z) / _size.x + + # fmt: off + vertices = array("f", ( + p_ll.x, p_ll.y, 0.0, left, bottom, + p_lr.x, p_lr.y, 0.0, right, bottom, + p_ul.x, p_ul.y, 0.0, left, top, + p_ur.x, p_ur.y, 0.0, right, top, + )) + # fmt: on + self._buffer.write(vertices) diff --git a/arcade/resources/system/shaders/gui/surface_gs.glsl b/arcade/resources/system/shaders/gui/surface_gs.glsl deleted file mode 100644 index 926c3e825b..0000000000 --- a/arcade/resources/system/shaders/gui/surface_gs.glsl +++ /dev/null @@ -1,60 +0,0 @@ -#version 330 - -layout (points) in; -layout (triangle_strip, max_vertices = 4) out; - -uniform WindowBlock { - mat4 projection; - mat4 view; -} window; - -uniform vec2 pos; -uniform vec2 size; -uniform vec4 area; - -out vec2 uv; - -void main() { - mat4 mvp = window.projection * window.view; - - // Clamp the area inside the surface - // This is the local area inside the surface - // Format is (x, y, width, height) - vec2 b1 = clamp(area.xy, vec2(0.0), size); - vec2 b2 = clamp(area.xy + area.zw, vec2(0.0), size); - vec4 l_area = vec4( - clamp(area.xy, vec2(0.0), size), - b2 - b1 - ); - - // Create the 4 corners of the rectangle - // These are the final/global coordinates rendered - vec2 p_ll = pos + l_area.xy; - vec2 p_lr = pos + l_area.xy + vec2(l_area.z, 0.0); - vec2 p_ul = pos + l_area.xy + vec2(0, l_area.w);; - vec2 p_ur = pos + l_area.xy + l_area.zw; - - // Calculate the UV coordinates - float bottom = l_area.y / size.y; - float left = l_area.x / size.x; - float top = (l_area.y + l_area.w) / size.y; - float right = (l_area.x + l_area.z) / size.x; - - gl_Position = mvp * vec4(p_ll, 0.0, 1.0); - uv = vec2(left, bottom); - EmitVertex(); - - gl_Position = mvp * vec4(p_lr, 0.0, 1.0); - uv = vec2(right, bottom); - EmitVertex(); - - gl_Position = mvp * vec4(p_ul, 0.0, 1.0); - uv = vec2(left, top); - EmitVertex(); - - gl_Position = mvp * vec4(p_ur, 0.0, 1.0); - uv = vec2(right, top); - EmitVertex(); - - EndPrimitive(); -} diff --git a/arcade/resources/system/shaders/gui/surface_vs.glsl b/arcade/resources/system/shaders/gui/surface_vs.glsl index aa6ad9e8ac..ab8953538c 100644 --- a/arcade/resources/system/shaders/gui/surface_vs.glsl +++ b/arcade/resources/system/shaders/gui/surface_vs.glsl @@ -1,5 +1,16 @@ #version 330 +uniform WindowBlock { + mat4 projection; + mat4 view; +} window; + +in vec3 in_pos; +in vec2 in_uv; + +out vec2 uv; + void main() { - gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + gl_Position = window.projection * window.view * vec4(in_pos, 1.0); + uv = in_uv; } From 6c6c1158d18e407f6c20d4cdb273a54ff3d43bf1 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Sun, 4 May 2025 11:01:37 +0200 Subject: [PATCH 2/3] Lazy update vertices + left/bottom fix --- arcade/gui/surface.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/arcade/gui/surface.py b/arcade/gui/surface.py index b597b9f650..e8a516a687 100644 --- a/arcade/gui/surface.py +++ b/arcade/gui/surface.py @@ -39,6 +39,7 @@ def __init__( self._pos = position self._pixel_ratio = pixel_ratio self._pixelated = False + self._area = None # Cached area for the last draw call self.texture = self.ctx.texture(self.size_scaled, components=4) self.fbo: Framebuffer = self.ctx.framebuffer(color_attachments=[self.texture]) @@ -278,19 +279,24 @@ def _update_geometry(self, area: Rect | None = None) -> None: """ Update the internal geometry of the surface mesh. - The geometry is a triangle strip with 4 verties. + The geometry is a triangle strip with 4 vertices. """ if area is None: area = LBWH(0, 0, *self.size) + if self._area == area: + return + self._area = area + # Clamp the area inside the surface # This is the local area inside the surface _size = Vec2(*self.size) - _pos = Vec2(area.left, area.bottom) + _pos = Vec2(*self.position) + _area_pos = Vec2(area.left, area.bottom) _area_size = Vec2(area.width, area.height) - b1 = _pos.clamp(Vec2(0.0), _size) - end_point = _pos + _area_size + b1 = _area_pos.clamp(Vec2(0.0), _size) + end_point = _area_pos + _area_size b2 = end_point.clamp(Vec2(0.0), _size) b = b2 - b1 l_area = Vec4(b1.x, b1.y, b.x, b.y) From 83ab0da2b9acdc1e15e01c90b85e8ac0d7818048 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Sun, 4 May 2025 11:03:14 +0200 Subject: [PATCH 3/3] Type fix --- arcade/gui/surface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/gui/surface.py b/arcade/gui/surface.py index e8a516a687..200ebce209 100644 --- a/arcade/gui/surface.py +++ b/arcade/gui/surface.py @@ -39,7 +39,7 @@ def __init__( self._pos = position self._pixel_ratio = pixel_ratio self._pixelated = False - self._area = None # Cached area for the last draw call + self._area: Rect | None = None # Cached area for the last draw call self.texture = self.ctx.texture(self.size_scaled, components=4) self.fbo: Framebuffer = self.ctx.framebuffer(color_attachments=[self.texture])