From 2d32fc5f3cc263105239827eb7d305040c02d22e Mon Sep 17 00:00:00 2001 From: Andrew Coffey Date: Wed, 25 Jun 2025 19:57:32 -0500 Subject: [PATCH 1/4] Sprites/Groups can now support Renderers/Textures in a limited capacity --- buildconfig/stubs/pygame/sprite.pyi | 17 ++++++++++------ docs/reST/ref/sprite.rst | 31 +++++++++++++++++++++++++---- src_c/cython/pygame/_sdl2/video.pxd | 10 +++++++++- src_c/cython/pygame/_sdl2/video.pyx | 2 +- src_c/doc/sprite_doc.h | 2 +- test/sprite_test.py | 21 +++++++++++++++++++ 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/buildconfig/stubs/pygame/sprite.pyi b/buildconfig/stubs/pygame/sprite.pyi index 25f5986f07..0563715106 100644 --- a/buildconfig/stubs/pygame/sprite.pyi +++ b/buildconfig/stubs/pygame/sprite.pyi @@ -9,6 +9,7 @@ from typing import ( SupportsFloat, TypeVar, Union, + overload, ) # use typing_extensions for compatibility with older Python versions @@ -22,6 +23,7 @@ if sys.version_info >= (3, 11): else: from typing_extensions import Self +from pygame._sdl2.video import Renderer, Texture from pygame.mask import Mask from pygame.rect import FRect, Rect from pygame.surface import Surface @@ -37,7 +39,7 @@ class _HasRect(Protocol): # image in addition to rect class _HasImageAndRect(_HasRect, Protocol): @property - def image(self) -> Optional[Surface]: ... + def image(self) -> Optional[Union[Surface, Texture]]: ... # mask in addition to rect class _HasMaskAndRect(_HasRect, Protocol): @@ -54,9 +56,9 @@ _Group = AbstractGroup[Any] # and allows the use of any class with the required attributes and methods class _SupportsSprite(_HasImageAndRect, Protocol): @property - def image(self) -> Optional[Surface]: ... + def image(self) -> Optional[Union[Surface, Texture]]: ... @image.setter - def image(self, value: Optional[Surface]) -> None: ... + def image(self, value: Optional[Union[Surface, Texture]]) -> None: ... @property def rect(self) -> Optional[Union[FRect, Rect]]: ... @rect.setter @@ -87,9 +89,9 @@ class _SupportsDirtySprite(_SupportsSprite, Protocol): # concrete sprite implementation class class Sprite(_SupportsSprite): @property - def image(self) -> Optional[Surface]: ... + def image(self) -> Optional[Union[Surface, Texture]]: ... @image.setter - def image(self, value: Optional[Surface]) -> None: ... + def image(self, value: Optional[Union[Surface, Texture]]) -> None: ... @property def rect(self) -> Optional[Union[FRect, Rect]]: ... @rect.setter @@ -146,7 +148,10 @@ class AbstractGroup(Generic[_TSprite]): def has(self, *sprites: _SpriteOrIterable[_TSprite]) -> bool: ... def update(self, *args: Any, **kwargs: Any) -> None: ... def draw( - self, surface: Surface, bgd: Optional[Surface] = None, special_flags: int = 0 + self, + surface: Union[Surface, Renderer], + bgd: Optional[Surface] = None, + special_flags: int = 0, ) -> list[Union[FRect, Rect]]: ... def clear( self, diff --git a/docs/reST/ref/sprite.rst b/docs/reST/ref/sprite.rst index 16583d9690..3dcfba96f1 100644 --- a/docs/reST/ref/sprite.rst +++ b/docs/reST/ref/sprite.rst @@ -50,6 +50,17 @@ extend those when you add a Sprite or Group class. Sprites are not thread safe. So lock them yourself if using threads. +.. note:: As of version 2.5.6, you can use ``Sprite`` and ``Group`` with ``Renderer`` + and ``Texture`` from ``_sdl2.video``, at least partially. Instead of ``Sprite.image`` + needing to be a ``Surface``, it needs to be a ``Texture`` to do this. This behavior + is still very experimental, so please report any weirdness to the developers as an + issue on the ``pygame-ce`` github repository. Also note that ``LayeredDirty.draw`` + does not currently work in this manner. Other group types might work, but have not + been thorougly tested. + +.. versionchanged:: 2.5.6 ``Sprite`` and ``Group`` + have some compatibility with ``_sdl2.video`` + .. class:: Sprite | :sl:`Simple base class for visible game objects.` @@ -291,17 +302,29 @@ Sprites are not thread safe. So lock them yourself if using threads. .. method:: draw | :sl:`blit the Sprite images` - | :sg:`draw(Surface, bgd=None, special_flags=0) -> list[Rect]` + | :sg:`draw(Surface, bgd=None, special_flags=0) -> list[Union[Rect, FRect]]` + | :sg:`draw(Renderer, bgd=None, special_flags=0) -> list[Union[Rect, FRect]]` + + Draws the contained Sprites to the Surface or _sdl2.video.Renderer argument. + + Surface + ------- + This uses the ``Sprite.image`` attribute for the source surface, and ``Sprite.rect`` + for the position. + + Renderer + -------- + This uses the ``Sprite.image`` attribute for the source Texture, and ``Sprite.rect`` + for the position. - Draws the contained Sprites to the Surface argument. This uses the - ``Sprite.image`` attribute for the source surface, and ``Sprite.rect`` - for the position. ``special_flags`` is passed to ``Surface.blit()``. + ``special_flags`` is passed to ``Surface.blit()``. ``bgd`` is unused in this method but ``LayeredDirty.draw()`` uses it. The Group keeps sprites in the order they were added, they will be drawn in this order. .. versionchanged:: 2.5.4 Added the ``bgd`` and ``special_flags`` arguments + .. versionchanged:: 2.5.6 Now accepts a renderer for use with ``_sdl2.video`` and ``Sprite.rect`` supports ``FRect``s .. ## Group.draw ## diff --git a/src_c/cython/pygame/_sdl2/video.pxd b/src_c/cython/pygame/_sdl2/video.pxd index 1d87ba43b2..0f285bace4 100644 --- a/src_c/cython/pygame/_sdl2/video.pxd +++ b/src_c/cython/pygame/_sdl2/video.pxd @@ -427,6 +427,14 @@ cdef extern from "pygame.h" nogil: cdef SDL_Rect r cdef object weakreflist + ctypedef class pygame.rect.FRect [object pgFRectObject]: + cdef SDL_FRect r + cdef object weakreflist + + ctypedef fused RectLike: + Rect + FRect + ctypedef class pygame.window.Window [object pgWindowObject]: cdef SDL_Window *_win cdef SDL_bool _is_borrowed @@ -465,7 +473,7 @@ cdef class Renderer: cdef int _is_borrowed cpdef object get_viewport(self) - cpdef object blit(self, object source, Rect dest=*, Rect area=*, int special_flags=*) + cpdef object blit(self, object source, RectLike dest=*, RectLike area=*, int special_flags=*) cdef class Texture: cdef SDL_Texture* _tex diff --git a/src_c/cython/pygame/_sdl2/video.pyx b/src_c/cython/pygame/_sdl2/video.pyx index af8ea28e1e..329fda73ee 100644 --- a/src_c/cython/pygame/_sdl2/video.pyx +++ b/src_c/cython/pygame/_sdl2/video.pyx @@ -998,7 +998,7 @@ cdef class Renderer: else: raise TypeError('target must be a Texture or None') - cpdef object blit(self, object source, Rect dest=None, Rect area=None, int special_flags=0): + cpdef object blit(self, object source, RectLike dest=None, RectLike area=None, int special_flags=0): """Draw textures using a Surface-like API For compatibility purposes. Draws :class:`Texture` objects onto the diff --git a/src_c/doc/sprite_doc.h b/src_c/doc/sprite_doc.h index 457ac3c56a..f2c6b84515 100644 --- a/src_c/doc/sprite_doc.h +++ b/src_c/doc/sprite_doc.h @@ -15,7 +15,7 @@ #define DOC_SPRITE_GROUP_REMOVE "remove(*sprites) -> None\nremove Sprites from the Group" #define DOC_SPRITE_GROUP_HAS "has(*sprites) -> bool\ntest if a Group contains Sprites" #define DOC_SPRITE_GROUP_UPDATE "update(*args, **kwargs) -> None\ncall the update method on contained Sprites" -#define DOC_SPRITE_GROUP_DRAW "draw(Surface, bgd=None, special_flags=0) -> list[Rect]\nblit the Sprite images" +#define DOC_SPRITE_GROUP_DRAW "draw(Surface, bgd=None, special_flags=0) -> list[Union[Rect, FRect]]\ndraw(Renderer, bgd=None, special_flags=0) -> list[Union[Rect, FRect]]\nblit the Sprite images" #define DOC_SPRITE_GROUP_CLEAR "clear(Surface_dest, background) -> None\ndraw a background over the Sprites" #define DOC_SPRITE_GROUP_EMPTY "empty() -> None\nremove all Sprites" #define DOC_SPRITE_RENDERUPDATES "RenderUpdates(*sprites) -> RenderUpdates\nGroup sub-class that tracks dirty updates." diff --git a/test/sprite_test.py b/test/sprite_test.py index fd06cd47d6..b8b78bbdb1 100644 --- a/test/sprite_test.py +++ b/test/sprite_test.py @@ -7,6 +7,7 @@ import pygame from pygame import sprite +from pygame._sdl2.video import Renderer, Texture ################################# MODULE LEVEL ################################# @@ -585,6 +586,26 @@ def test_draw(self): self.assertEqual(self.ag.spritedict[self.s1], pygame.Rect(0, 0, 10, 10)) self.assertEqual(self.ag.spritedict[self.s2], pygame.Rect(10, 0, 10, 10)) + def test_draw_sdl2(self): + reinit_display = pygame.display.get_init() + + pygame.display.quit() + win = pygame.Window() + group = sprite.Group() + renderer = Renderer(win) + gpusprite = sprite.Sprite(group) + + tempsurf = pygame.Surface((50, 50)) + gpusprite.image = Texture.from_surface(renderer, tempsurf) + gpusprite.rect = gpusprite.image.get_rect() + + group.draw(renderer) + self.assertEqual(group.spritedict[gpusprite], tempsurf.get_rect()) + + win.destroy() + if reinit_display: + pygame.display.init() + def test_empty(self): self.ag.empty() self.assertFalse(self.s1 in self.ag) From 9e74c57f7584386ad9ebab0156ef632f91ad6488 Mon Sep 17 00:00:00 2001 From: Andrew Coffey Date: Wed, 25 Jun 2025 21:22:10 -0500 Subject: [PATCH 2/4] Removed pygame.display stuff from gpu sprite test --- test/sprite_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/sprite_test.py b/test/sprite_test.py index b8b78bbdb1..448ead7067 100644 --- a/test/sprite_test.py +++ b/test/sprite_test.py @@ -587,9 +587,6 @@ def test_draw(self): self.assertEqual(self.ag.spritedict[self.s2], pygame.Rect(10, 0, 10, 10)) def test_draw_sdl2(self): - reinit_display = pygame.display.get_init() - - pygame.display.quit() win = pygame.Window() group = sprite.Group() renderer = Renderer(win) @@ -603,8 +600,6 @@ def test_draw_sdl2(self): self.assertEqual(group.spritedict[gpusprite], tempsurf.get_rect()) win.destroy() - if reinit_display: - pygame.display.init() def test_empty(self): self.ag.empty() From 94a59944f202e9bd6fdf8b362ac4322d5a58023d Mon Sep 17 00:00:00 2001 From: Andrew Coffey Date: Wed, 25 Jun 2025 21:27:33 -0500 Subject: [PATCH 3/4] Fixed sprite stubs --- buildconfig/stubs/pygame/sprite.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/sprite.pyi b/buildconfig/stubs/pygame/sprite.pyi index 0563715106..17ebb8f40f 100644 --- a/buildconfig/stubs/pygame/sprite.pyi +++ b/buildconfig/stubs/pygame/sprite.pyi @@ -197,7 +197,7 @@ class LayeredUpdates(AbstractGroup[_TSprite]): class LayeredDirty(LayeredUpdates[_TDirtySprite]): def draw( self, - surface: Surface, + surface: Union[Surface, Renderer], bgd: Optional[Surface] = None, special_flags: Optional[int] = None, ) -> list[Union[FRect, Rect]]: ... From 7e52a7ec8146626d3ebe5856771ae0199315f0e2 Mon Sep 17 00:00:00 2001 From: Andrew Coffey Date: Wed, 25 Jun 2025 21:32:49 -0500 Subject: [PATCH 4/4] Fixed test maybe --- test/sprite_test.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/sprite_test.py b/test/sprite_test.py index 448ead7067..007e65d2f3 100644 --- a/test/sprite_test.py +++ b/test/sprite_test.py @@ -590,16 +590,19 @@ def test_draw_sdl2(self): win = pygame.Window() group = sprite.Group() renderer = Renderer(win) + renderer.draw_color = "blue" gpusprite = sprite.Sprite(group) - tempsurf = pygame.Surface((50, 50)) - gpusprite.image = Texture.from_surface(renderer, tempsurf) + gpusprite.image = Texture(renderer, (50, 50), target=True) + renderer.target = gpusprite.image + renderer.clear() gpusprite.rect = gpusprite.image.get_rect() + renderer.target = None group.draw(renderer) - self.assertEqual(group.spritedict[gpusprite], tempsurf.get_rect()) + self.assertEqual(group.spritedict[gpusprite], pygame.Rect(0, 0, 50, 50)) - win.destroy() + renderer.present() def test_empty(self): self.ag.empty()