1
+ from array import array
1
2
from contextlib import contextmanager
2
3
from typing import Generator
3
4
4
5
from PIL import Image
6
+ from pyglet .math import Vec2 , Vec4
5
7
from typing_extensions import Self
6
8
7
9
import arcade
8
10
from arcade import Texture
9
11
from arcade .camera import CameraData , OrthographicProjectionData , OrthographicProjector
10
12
from arcade .color import TRANSPARENT_BLACK
11
- from arcade .gl import Framebuffer
13
+ from arcade .gl import BufferDescription , Framebuffer
12
14
from arcade .gui .nine_patch import NinePatchTexture
13
15
from arcade .types import LBWH , RGBA255 , Point , Rect
14
16
@@ -37,6 +39,7 @@ def __init__(
37
39
self ._pos = position
38
40
self ._pixel_ratio = pixel_ratio
39
41
self ._pixelated = False
42
+ self ._area : Rect | None = None # Cached area for the last draw call
40
43
41
44
self .texture = self .ctx .texture (self .size_scaled , components = 4 )
42
45
self .fbo : Framebuffer = self .ctx .framebuffer (color_attachments = [self .texture ])
@@ -53,12 +56,17 @@ def __init__(
53
56
* self .ctx .BLEND_DEFAULT ,
54
57
)
55
58
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
+ )
57
65
self ._program = self .ctx .load_program (
58
66
vertex_shader = ":system:shaders/gui/surface_vs.glsl" ,
59
- geometry_shader = ":system:shaders/gui/surface_gs.glsl" ,
60
67
fragment_shader = ":system:shaders/gui/surface_fs.glsl" ,
61
68
)
69
+ self ._update_geometry ()
62
70
63
71
self ._cam = OrthographicProjector (
64
72
view = CameraData (),
@@ -228,6 +236,8 @@ def draw(
228
236
area: Limit the area in the surface we're drawing
229
237
(l, b, w, h)
230
238
"""
239
+ self ._update_geometry (area = area )
240
+
231
241
# Set blend function
232
242
blend_func = self .ctx .blend_func
233
243
self .ctx .blend_func = self .blend_func_render
@@ -239,10 +249,7 @@ def draw(
239
249
self .texture .filter = self .ctx .LINEAR , self .ctx .LINEAR
240
250
241
251
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 )
246
253
247
254
# Restore blend function
248
255
self .ctx .blend_func = blend_func
@@ -267,3 +274,52 @@ def resize(self, *, size: tuple[int, int], pixel_ratio: float) -> None:
267
274
def to_image (self ) -> Image .Image :
268
275
"""Convert the surface to an PIL image"""
269
276
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