Browse Source

gobj: Implement `copy.deepcopy()` for Texture class

Actually ensures that the underlying RAM images are really fully unique.
rdb 3 years ago
parent
commit
896346b99f

+ 1 - 0
doc/ReleaseNotes

@@ -72,6 +72,7 @@ Miscellaneous
 * Fix support for `#pragma include <file.glsl>` in GLSL shaders
 * Fix `ShaderBuffer.prepare()` not doing anything
 * Implement deepcopy for PointerToArray
+* Fix Texture deepcopy keeping a reference to the original RAM image
 * Fix bf-cbc encryption no longer working when building with OpenSSL 3.0
 * PandaNode bounds_type property was erroneously marked read-only
 * Fix warnings when copying OdeTriMeshGeom objects

+ 4 - 0
panda/src/gobj/texture.h

@@ -46,6 +46,7 @@
 #include "pnmImage.h"
 #include "pfmFile.h"
 #include "asyncFuture.h"
+#include "extension.h"
 
 class TextureContext;
 class FactoryParams;
@@ -472,6 +473,8 @@ PUBLISHED:
   MAKE_PROPERTY(keep_ram_image, get_keep_ram_image, set_keep_ram_image);
   MAKE_PROPERTY(cacheable, is_cacheable);
 
+  EXTENSION(PT(Texture) __deepcopy__(PyObject *memo) const);
+
   BLOCKING INLINE bool compress_ram_image(CompressionMode compression = CM_on,
                                           QualityLevel quality_level = QL_default,
                                           GraphicsStateGuardianBase *gsg = nullptr);
@@ -1110,6 +1113,7 @@ private:
 
   static TypeHandle _type_handle;
 
+  friend class Extension<Texture>;
   friend class TextureContext;
   friend class PreparedGraphicsObjects;
   friend class TexturePool;

+ 28 - 0
panda/src/gobj/texture_ext.cxx

@@ -165,4 +165,32 @@ set_ram_image_as(PyObject *image, const std::string &provided_format) {
   Dtool_Raise_ArgTypeError(image, 0, "Texture.set_ram_image_as", "CPTA_uchar or buffer");
 }
 
+/**
+ * A special Python method that is invoked by copy.deepcopy(tex).  This makes
+ * sure that the copy has a unique copy of the RAM image.
+ */
+PT(Texture) Extension<Texture>::
+__deepcopy__(PyObject *memo) const {
+  PT(Texture) copy = _this->make_copy();
+  {
+    Texture::CDWriter cdata(copy->_cycler, true);
+    for (Texture::RamImage &image : cdata->_ram_images) {
+      if (image._image.get_ref_count() > 1) {
+        PTA_uchar new_image;
+        new_image.v() = image._image.v();
+        image._image = std::move(new_image);
+      }
+    }
+    {
+      Texture::RamImage &image = cdata->_simple_ram_image;
+      if (image._image.get_ref_count() > 1) {
+        PTA_uchar new_image;
+        new_image.v() = image._image.v();
+        image._image = std::move(new_image);
+      }
+    }
+  }
+  return copy;
+}
+
 #endif  // HAVE_PYTHON

+ 2 - 0
panda/src/gobj/texture_ext.h

@@ -32,6 +32,8 @@ public:
   void set_ram_image(PyObject *image, Texture::CompressionMode compression = Texture::CM_off,
                      size_t page_size = 0);
   void set_ram_image_as(PyObject *image, const std::string &provided_format);
+
+  PT(Texture) __deepcopy__(PyObject *memo) const;
 };
 
 #endif  // HAVE_PYTHON

+ 23 - 0
tests/gobj/test_texture.py

@@ -134,3 +134,26 @@ def test_texture_clear_half():
     assert col.y == -inf
     assert col.z == -inf
     assert math.isnan(col.w)
+
+
+def test_texture_deepcopy():
+    from copy import deepcopy
+
+    empty_tex = Texture("empty-texture")
+    empty_tex.setup_2d_texture(16, 16, Texture.T_unsigned_byte, Texture.F_rgba)
+    assert not empty_tex.has_ram_image()
+    empty_tex2 = deepcopy(empty_tex)
+    assert empty_tex2.name == empty_tex.name
+    assert not empty_tex2.has_ram_image()
+
+    tex = Texture("texture")
+    tex.setup_2d_texture(16, 16, Texture.T_unsigned_byte, Texture.F_rgba)
+    img = tex.make_ram_image()
+    assert tex.has_ram_image()
+    assert img.get_ref_count() == 2
+
+    tex2 = deepcopy(tex)
+    assert tex2.name == tex.name
+    assert tex2.has_ram_image()
+    img2 = tex2.get_ram_image()
+    assert img2.get_ref_count() == 2