From 9ec913d6445b0265dffbaf9105646ea8892a68d5 Mon Sep 17 00:00:00 2001 From: Einar Forselv Date: Sun, 30 Jun 2024 02:35:21 +0200 Subject: [PATCH] Spritelist tweaks (#2160) * Default texture filter class variable + blend property * Be more specific .. * Remove blend parameter in initializer * Tweaks * tests * Speed up unit tests by keeping textures * Compare gc runtime without explicit gc.collect --- arcade/sprite_list/sprite_list.py | 51 +++++++++++++++++------- tests/conftest.py | 11 ++--- tests/unit/spritelist/test_spritelist.py | 23 +++++++++++ 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/arcade/sprite_list/sprite_list.py b/arcade/sprite_list/sprite_list.py index 9b32281c1..d0e1d66b7 100644 --- a/arcade/sprite_list/sprite_list.py +++ b/arcade/sprite_list/sprite_list.py @@ -23,6 +23,7 @@ Callable, cast, Sized, + ClassVar, ) from arcade import ( @@ -94,9 +95,20 @@ class SpriteList(Generic[SpriteType]): :ref:`pg_spritelist_advanced_lazy_spritelists` to learn more. :param visible: Setting this to False will cause the SpriteList to not be drawn. When draw is called, the method will just return without drawing. - :param blend: Enable or disable blending for the sprite list """ + #: The default texture filter used when no other filter is specified. + #: This can be used to change the global default for all spritelists + #: + #: Example:: + #: + #: from arcade import gl + #: # Set global default to nearest filtering (pixelated) + #: arcade.SpriteList.DEFAULT_TEXTURE_FILTER = gl.NEAREST, gl.NEAREST + #: # Set global default to linear filtering (smooth). This is the default. + #: arcade.SpriteList.DEFAULT_TEXTURE_FILTER = gl.NEAREST, gl.NEAREST + DEFAULT_TEXTURE_FILTER: ClassVar[tuple[int, int]] = gl.LINEAR, gl.LINEAR + def __init__( self, use_spatial_hash: bool = False, @@ -105,14 +117,13 @@ def __init__( capacity: int = 100, lazy: bool = False, visible: bool = True, - blend: bool = True, ) -> None: self.program: Optional[Program] = None self._atlas: Optional[TextureAtlasBase] = atlas self._initialized = False self._lazy = lazy self._visible = visible - self._blend = blend + self._blend = True self._color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0) # The initial capacity of the spritelist buffers (internal) @@ -305,6 +316,17 @@ def visible(self) -> bool: def visible(self, value: bool) -> None: self._visible = value + @property + def blend(self) -> bool: + """ + Flag for enabling or disabling alpha blending for the spritelist. + """ + return self._blend + + @blend.setter + def blend(self, value: bool) -> None: + self._blend = value + @property def color(self) -> Color: """ @@ -1006,13 +1028,17 @@ def draw( :param filter: Optional parameter to set OpenGL filter, such as `gl.GL_NEAREST` to avoid smoothing. :param pixelated: ``True`` for pixelated and ``False`` for smooth interpolation. - Shortcut for setting filter=GL_NEAREST. + Shortcut for setting filter to GL_NEAREST for a pixelated look. + The filter parameter have precedence over this. :param blend_function: Optional parameter to set the OpenGL blend function used for drawing the sprite list, such as 'arcade.Window.ctx.BLEND_ADDITIVE' or 'arcade.Window.ctx.BLEND_DEFAULT' """ if len(self.sprite_list) == 0 or not self._visible or self.alpha_normalized == 0.0: return + if not self.program: + raise ValueError("Attempting to render without shader program.") + self._init_deferred() self._write_sprite_buffers_to_gpu() @@ -1040,16 +1066,11 @@ def draw( else: # assume it's an int atlas_texture.filter = cast(OpenGlFilter, (filter, filter)) else: - atlas_texture.filter = self.ctx.LINEAR, self.ctx.LINEAR - - # Handle the pixelated shortcut - if pixelated: - atlas_texture.filter = self.ctx.NEAREST, self.ctx.NEAREST - else: - atlas_texture.filter = self.ctx.LINEAR, self.ctx.LINEAR - - if not self.program: - raise ValueError("Attempting to render without 'program' field being set.") + # Handle the pixelated shortcut if filter is not set + if pixelated: + atlas_texture.filter = self.ctx.NEAREST, self.ctx.NEAREST + else: + atlas_texture.filter = self.DEFAULT_TEXTURE_FILTER self.program["spritelist_color"] = self._color @@ -1063,8 +1084,10 @@ def draw( vertices=self._sprite_index_slots, ) + # Leave global states to default if self._blend: self.ctx.disable(self.ctx.BLEND) + self.ctx.blend_func = self.ctx.BLEND_DEFAULT def draw_hit_boxes(self, color: RGBA255 = (0, 0, 0, 255), line_thickness: float = 1.0) -> None: """Draw all the hit boxes in this list""" diff --git a/tests/conftest.py b/tests/conftest.py index d6931b0fb..18ff33206 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,6 @@ import gc import os -import sys from contextlib import contextmanager from pathlib import Path @@ -13,7 +12,8 @@ import pytest import arcade -from arcade.texture import default_texture_cache +from arcade import gl +# from arcade.texture import default_texture_cache PROJECT_ROOT = (Path(__file__).parent.parent).resolve() FIXTURE_ROOT = PROJECT_ROOT / "tests" / "fixtures" @@ -43,8 +43,9 @@ def prepare_window(window: arcade.Window): window.set_size(800, 600) ctx = window.ctx - ctx._atlas = None # Clear the global atlas - default_texture_cache.flush() # Clear the global/default texture cache + # ctx._atlas = None # Clear the global atlas + # default_texture_cache.flush() # Clear the global/default texture cache + arcade.SpriteList.DEFAULT_TEXTURE_FILTER = gl.LINEAR, gl.LINEAR window.hide_view() # Disable views if any is active window.dispatch_pending_events() try: @@ -60,7 +61,7 @@ def prepare_window(window: arcade.Window): window.default_camera.use() ctx.gc_mode = "context_gc" ctx.gc() - gc.collect() + # gc.collect(1) # Ensure no old functions are lingering window.on_draw = lambda: None diff --git a/tests/unit/spritelist/test_spritelist.py b/tests/unit/spritelist/test_spritelist.py index d271773a1..0225699ac 100644 --- a/tests/unit/spritelist/test_spritelist.py +++ b/tests/unit/spritelist/test_spritelist.py @@ -2,6 +2,7 @@ import struct import pytest import arcade +from arcade import gl def make_named_sprites(amount): @@ -18,6 +19,28 @@ def make_named_sprites(amount): return spritelist +def test_filter(window): + atlas = arcade.DefaultTextureAtlas((256, 256)) + spritelist = arcade.SpriteList(atlas=atlas) + spritelist.append(arcade.SpriteSolidColor(10, 10, color=arcade.color.WHITE)) + + assert atlas.texture.filter == (gl.LINEAR, gl.LINEAR) + spritelist.draw(filter=(gl.NEAREST, gl.NEAREST)) + assert atlas.texture.filter == (gl.NEAREST, gl.NEAREST) + spritelist.draw() + assert atlas.texture.filter == (gl.LINEAR, gl.LINEAR) + spritelist.draw(pixelated=True) + assert atlas.texture.filter == (gl.NEAREST, gl.NEAREST) + + +def test_default_texture_filter(window): + arcade.SpriteList.DEFAULT_TEXTURE_FILTER = gl.NEAREST, gl.NEAREST + spritelist = arcade.SpriteList() + spritelist.append(arcade.SpriteSolidColor(10, 10, color=arcade.color.WHITE)) + spritelist.draw() + assert spritelist.atlas.texture.filter == (gl.NEAREST, gl.NEAREST) + + # Temp fix for https://github.com/pythonarcade/arcade/issues/2074 def test_copy_dunder_stubs_raise_notimplementederror(): spritelist = arcade.SpriteList()