From 8964fc37ef1a9b77c90421cb04c204d712ade07e Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Sat, 24 May 2025 14:35:07 +0200 Subject: [PATCH] Atlas resize shader without geometry shader --- arcade/context.py | 10 ++- .../system/shaders/atlas/resize_fs.glsl | 3 +- .../system/shaders/atlas/resize_gs.glsl | 6 +- .../shaders/atlas/resize_simple_fs.glsl | 13 ++++ .../shaders/atlas/resize_simple_vs.glsl | 76 +++++++++++++++++++ arcade/texture_atlas/atlas_default.py | 12 +-- tests/unit/atlas/test_basics.py | 2 +- 7 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 arcade/resources/system/shaders/atlas/resize_simple_fs.glsl create mode 100644 arcade/resources/system/shaders/atlas/resize_simple_vs.glsl diff --git a/arcade/context.py b/arcade/context.py index b9ae6b372f..58d5f94b5f 100644 --- a/arcade/context.py +++ b/arcade/context.py @@ -131,9 +131,13 @@ def __init__( ) # Atlas shaders self.atlas_resize_program: Program = self.load_program( - vertex_shader=":system:shaders/atlas/resize_vs.glsl", - geometry_shader=":system:shaders/atlas/resize_gs.glsl", - fragment_shader=":system:shaders/atlas/resize_fs.glsl", + # NOTE: This is the geo shader version of the atlas resize program. + # vertex_shader=":system:shaders/atlas/resize_vs.glsl", + # geometry_shader=":system:shaders/atlas/resize_gs.glsl", + # fragment_shader=":system:shaders/atlas/resize_fs.glsl", + # Vertex and fragment shader version + vertex_shader=":system:shaders/atlas/resize_simple_vs.glsl", + fragment_shader=":system:shaders/atlas/resize_simple_fs.glsl", ) self.atlas_resize_program["atlas_old"] = 0 # Configure texture channels self.atlas_resize_program["atlas_new"] = 1 diff --git a/arcade/resources/system/shaders/atlas/resize_fs.glsl b/arcade/resources/system/shaders/atlas/resize_fs.glsl index deff9722b6..081eef2b24 100644 --- a/arcade/resources/system/shaders/atlas/resize_fs.glsl +++ b/arcade/resources/system/shaders/atlas/resize_fs.glsl @@ -1,6 +1,7 @@ #version 330 -// The old atlas texture. +// The old atlas texture. We copy sections to the new atlas texture +// by render into an fbo with the target texture as the color attachment. uniform sampler2D atlas_old; out vec4 fragColor; diff --git a/arcade/resources/system/shaders/atlas/resize_gs.glsl b/arcade/resources/system/shaders/atlas/resize_gs.glsl index a1bf6d77f1..25a496a8d2 100644 --- a/arcade/resources/system/shaders/atlas/resize_gs.glsl +++ b/arcade/resources/system/shaders/atlas/resize_gs.glsl @@ -4,11 +4,13 @@ #include :system:shaders/lib/sprite.glsl -// Old and new texture coordiantes +// Old and new texture coordinates uniform sampler2D atlas_old; uniform sampler2D atlas_new; + uniform sampler2D texcoords_old; uniform sampler2D texcoords_new; + uniform mat4 projection; uniform float border; @@ -33,7 +35,7 @@ void main() { // absolute value of the diagonal * size + border * 2 vec2 size = abs(new_uv3 - new_uv0) * vec2(size_new) + vec2(border * 2.0); - // We need to offset the old coordiantes by border size + // We need to offset the old coordinates by border size vec2 pix_offset = vec2(border) / vec2(size_old); // ( // 0.015625, 0.015625, # minus, minus diff --git a/arcade/resources/system/shaders/atlas/resize_simple_fs.glsl b/arcade/resources/system/shaders/atlas/resize_simple_fs.glsl new file mode 100644 index 0000000000..fb394386ab --- /dev/null +++ b/arcade/resources/system/shaders/atlas/resize_simple_fs.glsl @@ -0,0 +1,13 @@ +#version 330 +// Atlas resize without geometry shader + +// The old atlas texture. We copy sections to the new atlas texture +// by render into an fbo with the target texture as the color attachment. +uniform sampler2D atlas_old; + +out vec4 fragColor; +in vec2 uv; + +void main() { + fragColor = texture(atlas_old, uv); +} diff --git a/arcade/resources/system/shaders/atlas/resize_simple_vs.glsl b/arcade/resources/system/shaders/atlas/resize_simple_vs.glsl new file mode 100644 index 0000000000..4daecf6e66 --- /dev/null +++ b/arcade/resources/system/shaders/atlas/resize_simple_vs.glsl @@ -0,0 +1,76 @@ +#version 330 +// Atlas resize without geometry shader + +// The render target for this program is the new +// texture atlas texture + +#include :system:shaders/lib/sprite.glsl + +// Old and new texture coordinates +uniform sampler2D atlas_old; +uniform sampler2D atlas_new; + +uniform sampler2D texcoords_old; +uniform sampler2D texcoords_new; + +uniform mat4 projection; +uniform float border; + +out vec2 uv; + +void main() { + // Get the texture sizes + ivec2 size_old = textureSize(atlas_old, 0).xy; + ivec2 size_new = textureSize(atlas_new, 0).xy; + + // Read texture coordinates from UV texture here + int texture_id = gl_VertexID / 6; + vec2 old_uv0, old_uv1, old_uv2, old_uv3; + getSpriteUVs(texcoords_old, texture_id, old_uv0, old_uv1, old_uv2, old_uv3); + vec2 new_uv0, new_uv1, new_uv2, new_uv3; + getSpriteUVs(texcoords_new, texture_id, new_uv0, new_uv1, new_uv2, new_uv3); + + // Lower left corner flipped * size - border + vec2 pos = vec2(new_uv2.x, 1.0 - new_uv2.y) * vec2(size_new) - vec2(border); + // absolute value of the diagonal * size + border * 2 + vec2 size = abs(new_uv3 - new_uv0) * vec2(size_new) + vec2(border * 2.0); + + // We need to offset the old coordinates by border size + vec2 pix_offset = vec2(border) / vec2(size_old); + + // Emit two triangles over 6 vertices + switch (gl_VertexID % 6) { + // First triangle + case 0: + // upper left + uv = old_uv0 - pix_offset; + gl_Position = projection * vec4(pos + vec2(0.0, size.y), 0.0, 1.0); + break; + case 1: + // lower left + uv = old_uv2 + vec2(-pix_offset.x, pix_offset.y); + gl_Position = projection * vec4(pos, 0.0, 1.0); + break; + case 2: + // upper right + uv = old_uv1 + vec2(pix_offset.x, -pix_offset.y); + gl_Position = projection * vec4(pos + vec2(size.x, size.y), 0.0, 1.0); + break; + // Second triangle + case 3: + // lower left + uv = old_uv2 + vec2(-pix_offset.x, pix_offset.y); + gl_Position = projection * vec4(pos, 0.0, 1.0); + break; + case 4: + // upper right + uv = old_uv1 + vec2(pix_offset.x, -pix_offset.y); + gl_Position = projection * vec4(pos + vec2(size.x, size.y), 0.0, 1.0); + break; + case 5: + // lower right + uv = old_uv3 + pix_offset; + gl_Position = projection * vec4(pos + vec2(size.x, 0.0), 0.0, 1.0); + break; + } +} diff --git a/arcade/texture_atlas/atlas_default.py b/arcade/texture_atlas/atlas_default.py index f920b7675d..77a60174f0 100644 --- a/arcade/texture_atlas/atlas_default.py +++ b/arcade/texture_atlas/atlas_default.py @@ -103,7 +103,7 @@ def __init__( self, size: tuple[int, int], *, - border: int = 1, + border: int = 2, textures: Sequence[Texture] | None = None, auto_resize: bool = True, ctx: ArcadeContext | None = None, @@ -667,8 +667,7 @@ def resize(self, size: tuple[int, int], force=False) -> None: force: Force a resize even if the size is the same """ - # LOG.info("[%s] Resizing atlas from %s to %s", id(self), self._size, size) - # print("Resizing atlas from", self._size, "to", size) + print("Resizing atlas from", self._size, "to", size) # Only resize if the size actually changed if size == self._size and not force: @@ -732,8 +731,9 @@ def resize(self, size: tuple[int, int], force=False) -> None: with self._ctx.enabled_only(): self._ctx.geometry_empty.render( self._ctx.atlas_resize_program, - mode=self._ctx.POINTS, - vertices=self.max_width, + mode=self._ctx.TRIANGLES, + # Two triangles per texture + vertices=UV_TEXTURE_WIDTH * self._capacity * 6, ) # duration = time.perf_counter() - resize_start @@ -746,7 +746,7 @@ def rebuild(self) -> None: This method also tries to organize the textures more efficiently ordering them by size. The texture ids will persist so the sprite list doesn't need to be rebuilt. """ - # LOG.info("Rebuilding atlas") + print("Rebuilding atlas") # Hold a reference to the old textures textures = self.textures diff --git a/tests/unit/atlas/test_basics.py b/tests/unit/atlas/test_basics.py index 1d682a2827..018ab9e09b 100644 --- a/tests/unit/atlas/test_basics.py +++ b/tests/unit/atlas/test_basics.py @@ -11,7 +11,7 @@ def test_create(ctx, common): assert atlas.width == 100 assert atlas.height == 200 assert atlas.size == (100, 200) - assert atlas.border == 1 + assert atlas.border == 2 assert atlas.auto_resize is True assert isinstance(atlas.max_size, tuple) assert atlas.max_size > (0, 0)