Skip to content

Commit f8722c5

Browse files
committed
Don't use geo shader for nine patch
1 parent e76abba commit f8722c5

File tree

4 files changed

+243
-280
lines changed

4 files changed

+243
-280
lines changed

arcade/gui/nine_patch.py

+230-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from array import array
2+
13
import arcade
2-
import arcade.gl as gl
4+
from arcade.gl import Buffer, BufferDescription, Geometry, Program
5+
from arcade.math import Vec2
36
from arcade.texture_atlas.base import TextureAtlasBase
7+
from arcade.types import Rect
48

59

610
class NinePatchTexture:
@@ -74,6 +78,7 @@ def __init__(
7478
self._initialized = False
7579
self._texture = texture
7680
self._custom_atlas = atlas
81+
self._geometry_cache: tuple[int, int, int, int, Rect] | None = None
7782

7883
# pixel texture co-ordinate start and end of central box.
7984
self._left = left
@@ -84,37 +89,16 @@ def __init__(
8489
self._check_sizes()
8590

8691
# Created in _init_deferred
87-
self._program: gl.program.Program
88-
self._geometry: gl.Geometry
92+
self._buffer: Buffer
93+
self._program: Program
94+
self._geometry: Geometry
8995
self._ctx: arcade.ArcadeContext
9096
self._atlas: TextureAtlasBase
9197
try:
9298
self._init_deferred()
9399
except Exception:
94100
pass
95101

96-
def _init_deferred(self):
97-
"""Deferred initialization when lazy loaded"""
98-
self._ctx = arcade.get_window().ctx
99-
# TODO: Cache in context?
100-
self._program = self._ctx.load_program(
101-
vertex_shader=":system:shaders/gui/nine_patch_vs.glsl",
102-
geometry_shader=":system:shaders/gui/nine_patch_gs.glsl",
103-
fragment_shader=":system:shaders/gui/nine_patch_fs.glsl",
104-
)
105-
# Configure texture channels
106-
self._program.set_uniform_safe("uv_texture", 0)
107-
self._program["sprite_texture"] = 1
108-
109-
# TODO: Cache in context?
110-
self._geometry = self._ctx.geometry()
111-
112-
# References for the texture
113-
self._atlas = self._custom_atlas or self._ctx.default_atlas
114-
self._add_to_atlas(self.texture)
115-
116-
self._initialized = True
117-
118102
def initialize(self) -> None:
119103
"""
120104
Manually initialize the NinePatchTexture if it was lazy loaded.
@@ -141,7 +125,7 @@ def texture(self, texture: arcade.Texture):
141125
self._add_to_atlas(texture)
142126

143127
@property
144-
def program(self) -> gl.program.Program:
128+
def program(self) -> Program:
145129
"""Get or set the shader program.
146130
147131
Returns the default shader if no other shader is assigned.
@@ -152,7 +136,7 @@ def program(self) -> gl.program.Program:
152136
return self._program
153137

154138
@program.setter
155-
def program(self, program: gl.program.Program):
139+
def program(self, program: Program):
156140
if not self._initialized:
157141
raise RuntimeError("The NinePatchTexture has not been initialized")
158142

@@ -241,26 +225,22 @@ def draw_rect(
241225
if not self._initialized:
242226
self._init_deferred()
243227

228+
self._create_geometry(rect)
229+
244230
if blend:
245231
self._ctx.enable_only(self._ctx.BLEND)
246232
else:
247233
self._ctx.disable(self._ctx.BLEND)
248234

249-
self.program.set_uniform_safe("texture_id", self._atlas.get_texture_id(self._texture))
250235
if pixelated:
251236
self._atlas.texture.filter = self._ctx.NEAREST, self._ctx.NEAREST
252237
else:
253238
self._atlas.texture.filter = self._ctx.LINEAR, self._ctx.LINEAR
254239

255-
self.program["position"] = rect.bottom_left
256-
self.program["start"] = self._left, self._bottom
257-
self.program["end"] = self.width - self._right, self.height - self._top
258-
self.program["size"] = rect.size
259-
self.program["t_size"] = self._texture.size
260-
261240
self._atlas.use_uv_texture(0)
262241
self._atlas.texture.use(1)
263-
self._geometry.render(self._program, vertices=1)
242+
243+
self._geometry.render(self._program)
264244

265245
if blend:
266246
self._ctx.disable(self._ctx.BLEND)
@@ -282,3 +262,218 @@ def _check_sizes(self):
282262
raise ValueError("Left and right border must be smaller than texture width")
283263
if self._bottom + self._top > self._texture.height:
284264
raise ValueError("Bottom and top border must be smaller than texture height")
265+
266+
def _init_deferred(self):
267+
"""Deferred initialization when lazy loaded"""
268+
self._ctx = arcade.get_window().ctx
269+
# TODO: Cache in context?
270+
self._program = self._ctx.load_program(
271+
vertex_shader=":system:shaders/gui/nine_patch_vs.glsl",
272+
fragment_shader=":system:shaders/gui/nine_patch_fs.glsl",
273+
)
274+
# Configure texture channels
275+
self._program.set_uniform_safe("uv_texture", 0)
276+
self._program["sprite_texture"] = 1
277+
278+
# 4 byte floats * 4 floats * 4 vertices * 9 patches
279+
self._buffer = self._ctx.buffer(reserve=576)
280+
# fmt: off
281+
self._ibo = self._ctx.buffer(
282+
data=array("i",
283+
[
284+
# Triangulate the patches
285+
# First rot
286+
0, 1, 2,
287+
3, 1, 2,
288+
289+
4, 5, 6,
290+
7, 5, 6,
291+
292+
8, 9, 10,
293+
11, 9, 10,
294+
295+
# Middle row
296+
12, 13, 14,
297+
15, 13, 14,
298+
299+
16, 17, 18,
300+
19, 17, 18,
301+
302+
20, 21, 22,
303+
23, 21, 22,
304+
305+
# Bottom row
306+
24, 25, 26,
307+
27, 25, 26,
308+
309+
28, 29, 30,
310+
31, 29, 30,
311+
312+
32, 33, 34,
313+
35, 33, 34,
314+
]
315+
),
316+
)
317+
# fmt: on
318+
self._geometry = self._ctx.geometry(
319+
content=[BufferDescription(self._buffer, "2f 2f", ["in_position", "in_uv"])],
320+
index_buffer=self._ibo,
321+
mode=self._ctx.TRIANGLES,
322+
index_element_size=4,
323+
)
324+
325+
# References for the texture
326+
self._atlas = self._custom_atlas or self._ctx.default_atlas
327+
self._add_to_atlas(self.texture)
328+
329+
# NOTE: Important to create geometry after the texture is added to the atlas
330+
# self._create_geometry(LBWH(0, 0, self.width, self.height))
331+
self._initialized = True
332+
333+
def _create_geometry(self, rect: Rect):
334+
"""Create vertices for the 9-patch texture."""
335+
# NOTE: This was ported from glsl geometry shader to python
336+
# Simulate old uniforms
337+
cache_key = (self._left, self._right, self._bottom, self._top, rect)
338+
if cache_key == self._geometry_cache:
339+
return
340+
self._geometry_cache = cache_key
341+
342+
position = rect.bottom_left
343+
start = Vec2(self._left, self._bottom)
344+
end = Vec2(self.width - self._right, self.height - self._top)
345+
size = rect.size
346+
t_size = Vec2(*self._texture.size)
347+
atlas_size = Vec2(*self._atlas.size)
348+
349+
# Patch points starting from upper left row by row
350+
p1 = position + Vec2(0.0, size.y)
351+
p2 = position + Vec2(start.x, size.y)
352+
p3 = position + Vec2(size.x - (t_size.x - end.x), size.y)
353+
p4 = position + Vec2(size.x, size.y)
354+
355+
y = size.y - (t_size.y - end.y)
356+
p5 = position + Vec2(0.0, y)
357+
p6 = position + Vec2(start.x, y)
358+
p7 = position + Vec2(size.x - (t_size.x - end.x), y)
359+
p8 = position + Vec2(size.x, y)
360+
361+
p9 = position + Vec2(0.0, start.y)
362+
p10 = position + Vec2(start.x, start.y)
363+
p11 = position + Vec2(size.x - (t_size.x - end.x), start.y)
364+
p12 = position + Vec2(size.x, start.y)
365+
366+
p13 = position + Vec2(0.0, 0.0)
367+
p14 = position + Vec2(start.x, 0.0)
368+
p15 = position + Vec2(size.x - (t_size.x - end.x), 0.0)
369+
p16 = position + Vec2(size.x, 0.0)
370+
371+
# <AtlasRegion
372+
# x=1 y=1
373+
# width=100 height=100
374+
# uvs=(
375+
# 0.001953125, 0.001953125,
376+
# 0.197265625, 0.001953125,
377+
# 0.001953125, 0.197265625,
378+
# 0.197265625, 0.197265625,
379+
# )
380+
# Get texture coordinates
381+
# vec2 uv0, uv1, uv2, uv3
382+
region = self._atlas.get_texture_region_info(self._texture.atlas_name)
383+
tex_coords = region.texture_coordinates
384+
uv0 = Vec2(tex_coords[0], tex_coords[1])
385+
uv1 = Vec2(tex_coords[2], tex_coords[3])
386+
uv2 = Vec2(tex_coords[4], tex_coords[5])
387+
uv3 = Vec2(tex_coords[6], tex_coords[7])
388+
389+
# Local corner offsets in pixels
390+
left = start.x
391+
right = t_size.x - end.x
392+
top = t_size.y - end.y
393+
bottom = start.y
394+
395+
# UV offsets to the inner rectangle in the patch
396+
# This is the global texture coordinate offset in the entire atlas
397+
c1 = Vec2(left, top) / atlas_size # Upper left corner
398+
c2 = Vec2(right, top) / atlas_size # Upper right corner
399+
c3 = Vec2(left, bottom) / atlas_size # Lower left corner
400+
c4 = Vec2(right, bottom) / atlas_size # Lower right corner
401+
402+
# Texture coordinates for all the points in the patch
403+
t1 = uv0
404+
t2 = uv0 + Vec2(c1.x, 0.0)
405+
t3 = uv1 - Vec2(c2.x, 0.0)
406+
t4 = uv1
407+
408+
t5 = uv0 + Vec2(0.0, c1.y)
409+
t6 = uv0 + c1
410+
t7 = uv1 + Vec2(-c2.x, c2.y)
411+
t8 = uv1 + Vec2(0.0, c2.y)
412+
413+
t9 = uv2 - Vec2(0.0, c3.y)
414+
t10 = uv2 + Vec2(c3.x, -c3.y)
415+
t11 = uv3 - c4
416+
t12 = uv3 - Vec2(0.0, c4.y)
417+
418+
t13 = uv2
419+
t14 = uv2 + Vec2(c3.x, 0.0)
420+
t15 = uv3 - Vec2(c4.x, 0.0)
421+
t16 = uv3
422+
423+
# fmt: off
424+
primitives = [
425+
# First row - two fixed corners + stretchy middle
426+
# Upper left corner. Fixed size.
427+
p1, t1,
428+
p5, t5,
429+
p2, t2,
430+
p6, t6,
431+
# Upper middle part stretches on x axis
432+
p2, t2,
433+
p6, t6,
434+
p3, t3,
435+
p7, t7,
436+
# Upper right corner. Fixed size
437+
p3, t3,
438+
p7, t7,
439+
p4, t4,
440+
p8, t8,
441+
442+
# Middle row: Two stretchy sides + stretchy middle
443+
# left border sketching on y axis
444+
p5, t5,
445+
p9, t9,
446+
p6, t6,
447+
p10, t10,
448+
# Center stretchy area
449+
p6, t6,
450+
p10, t10,
451+
p7, t7,
452+
p11, t11,
453+
# Right border. Stenches on y axis
454+
p7, t7,
455+
p11, t11,
456+
p8, t8,
457+
p12, t12,
458+
459+
# Bottom row: two fixed corners + stretchy middle
460+
# Lower left corner. Fixed size.
461+
p9, t9,
462+
p13, t13,
463+
p10, t10,
464+
p14, t14,
465+
# Lower middle part stretches on x axis
466+
p10, t10,
467+
p14, t14,
468+
p11, t11,
469+
p15, t15,
470+
# Lower right corner. Fixed size
471+
p11, t11,
472+
p15, t15,
473+
p12, t12,
474+
p16, t16,
475+
]
476+
# fmt: on
477+
478+
data = array("f", [coord for point in primitives for coord in point])
479+
self._buffer.write(data.tobytes())
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#version 330
22

33
uniform sampler2D sprite_texture;
4-
out vec4 f_color;
4+
out vec4 fragColor;
55

66
in vec2 uv;
77

88
void main() {
9-
f_color = texture(sprite_texture, uv);
9+
fragColor = texture(sprite_texture, uv);
1010
}

0 commit comments

Comments
 (0)