1+ from array import array
12from contextlib import contextmanager
23from typing import Generator
34
45from PIL import Image
6+ from pyglet .math import Vec2 , Vec4
57from typing_extensions import Self
68
79import arcade
810from arcade import Texture
911from arcade .camera import CameraData , OrthographicProjectionData , OrthographicProjector
1012from arcade .color import TRANSPARENT_BLACK
11- from arcade .gl import Framebuffer
13+ from arcade .gl import BufferDescription , Framebuffer
1214from arcade .gui .nine_patch import NinePatchTexture
1315from arcade .types import LBWH , RGBA255 , Point , Rect
1416
@@ -37,6 +39,7 @@ def __init__(
3739 self ._pos = position
3840 self ._pixel_ratio = pixel_ratio
3941 self ._pixelated = False
42+ self ._area : Rect | None = None # Cached area for the last draw call
4043
4144 self .texture = self .ctx .texture (self .size_scaled , components = 4 )
4245 self .fbo : Framebuffer = self .ctx .framebuffer (color_attachments = [self .texture ])
@@ -53,12 +56,17 @@ def __init__(
5356 * self .ctx .BLEND_DEFAULT ,
5457 )
5558
56- self ._geometry = self .ctx .geometry ()
59+ # 5 floats per vertex (pos 3f, tex 2f) with 4 vertices
60+ self ._buffer = self .ctx .buffer (reserve = 4 * 5 * 4 )
61+ self ._geometry = self .ctx .geometry (
62+ content = [BufferDescription (self ._buffer , "3f 2f" , ["in_pos" , "in_uv" ])],
63+ mode = self .ctx .TRIANGLE_STRIP ,
64+ )
5765 self ._program = self .ctx .load_program (
5866 vertex_shader = ":system:shaders/gui/surface_vs.glsl" ,
59- geometry_shader = ":system:shaders/gui/surface_gs.glsl" ,
6067 fragment_shader = ":system:shaders/gui/surface_fs.glsl" ,
6168 )
69+ self ._update_geometry ()
6270
6371 self ._cam = OrthographicProjector (
6472 view = CameraData (),
@@ -228,6 +236,8 @@ def draw(
228236 area: Limit the area in the surface we're drawing
229237 (l, b, w, h)
230238 """
239+ self ._update_geometry (area = area )
240+
231241 # Set blend function
232242 blend_func = self .ctx .blend_func
233243 self .ctx .blend_func = self .blend_func_render
@@ -239,10 +249,7 @@ def draw(
239249 self .texture .filter = self .ctx .LINEAR , self .ctx .LINEAR
240250
241251 self .texture .use (0 )
242- self ._program ["pos" ] = self ._pos
243- self ._program ["size" ] = self ._size
244- self ._program ["area" ] = (0 , 0 , * self ._size ) if not area else area .lbwh
245- self ._geometry .render (self ._program , vertices = 1 )
252+ self ._geometry .render (self ._program )
246253
247254 # Restore blend function
248255 self .ctx .blend_func = blend_func
@@ -267,3 +274,52 @@ def resize(self, *, size: tuple[int, int], pixel_ratio: float) -> None:
267274 def to_image (self ) -> Image .Image :
268275 """Convert the surface to an PIL image"""
269276 return self .ctx .get_framebuffer_image (self .fbo )
277+
278+ def _update_geometry (self , area : Rect | None = None ) -> None :
279+ """
280+ Update the internal geometry of the surface mesh.
281+
282+ The geometry is a triangle strip with 4 vertices.
283+ """
284+ if area is None :
285+ area = LBWH (0 , 0 , * self .size )
286+
287+ if self ._area == area :
288+ return
289+ self ._area = area
290+
291+ # Clamp the area inside the surface
292+ # This is the local area inside the surface
293+ _size = Vec2 (* self .size )
294+ _pos = Vec2 (* self .position )
295+ _area_pos = Vec2 (area .left , area .bottom )
296+ _area_size = Vec2 (area .width , area .height )
297+
298+ b1 = _area_pos .clamp (Vec2 (0.0 ), _size )
299+ end_point = _area_pos + _area_size
300+ b2 = end_point .clamp (Vec2 (0.0 ), _size )
301+ b = b2 - b1
302+ l_area = Vec4 (b1 .x , b1 .y , b .x , b .y )
303+
304+ # Create the 4 corners of the rectangle
305+ # These are the final/global coordinates rendered
306+ p_ll = _pos + l_area .xy # type: ignore
307+ p_lr = _pos + l_area .xy + Vec2 (l_area .z , 0.0 ) # type: ignore
308+ p_ul = _pos + l_area .xy + Vec2 (0.0 , l_area .w ) # type: ignore
309+ p_ur = _pos + l_area .xy + l_area .zw # type: ignore
310+
311+ # Calculate the UV coordinates
312+ bottom = l_area .y / _size .y
313+ left = l_area .x / _size .x
314+ top = (l_area .y + l_area .w ) / _size .y
315+ right = (l_area .x + l_area .z ) / _size .x
316+
317+ # fmt: off
318+ vertices = array ("f" , (
319+ p_ll .x , p_ll .y , 0.0 , left , bottom ,
320+ p_lr .x , p_lr .y , 0.0 , right , bottom ,
321+ p_ul .x , p_ul .y , 0.0 , left , top ,
322+ p_ur .x , p_ur .y , 0.0 , right , top ,
323+ ))
324+ # fmt: on
325+ self ._buffer .write (vertices )
0 commit comments