[Bf-extensions-cvs] [dfadb306] master: glTF exporter: perf: use foreach_{get, set} for image encoding

Julien Duroure noreply at git.blender.org
Sat Apr 11 15:42:36 CEST 2020


Commit: dfadb306b0e93b894c0639379720fd2414b6b0ec
Author: Julien Duroure
Date:   Sat Apr 11 15:41:05 2020 +0200
Branches: master
https://developer.blender.org/rBAdfadb306b0e93b894c0639379720fd2414b6b0ec

glTF exporter: perf: use foreach_{get,set} for image encoding

We can now remove compositor code, no more needed

===================================================================

M	io_scene_gltf2/__init__.py
M	io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
M	io_scene_gltf2/blender/exp/gltf2_blender_image.py

===================================================================

diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 5317f0f8..f05cd174 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,8 +15,8 @@
 bl_info = {
     'name': 'glTF 2.0 format',
     'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
-    "version": (1, 2, 62),
-    'blender': (2, 82, 7),
+    "version": (1, 2, 63),
+    'blender': (2, 83, 9),
     'location': 'File > Import-Export',
     'description': 'Import-Export as glTF 2.0',
     'warning': '',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index f841f0e2..22f0bc6d 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -60,8 +60,6 @@ def gather_primitives(
             material = gltf2_blender_gather_materials.gather_material(blender_material,
                                                                   double_sided,
                                                                   export_settings)
-            # NOTE: gather_material may invalidate blender_mesh (see #932),
-            # so make sure not to access blender_mesh again after this point
         except IndexError:
             # no material at that index
             pass
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index e9db7e66..34bb8e87 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -14,7 +14,7 @@
 
 import bpy
 import os
-from typing import Optional
+from typing import Optional, Tuple
 import numpy as np
 import tempfile
 import enum
@@ -121,98 +121,8 @@ class ExportImage:
         return self.__encode_from_image(self.blender_image())
 
     def __encode_unhappy(self) -> bytes:
-        result = self.__encode_unhappy_with_compositor()
-        if result is not None:
-            return result
-        return self.__encode_unhappy_with_numpy()
-
-    def __encode_unhappy_with_compositor(self) -> bytes:
-        # Builds a Compositor graph that will build the correct image
-        # from the description in self.fills.
-        #
-        #     [ Image ]->[ Sep RGBA ]    [ Comb RGBA ]
-        #                [  src_chan]--->[dst_chan   ]--->[ Output ]
-        #
-        # This is hacky, but is about 4x faster than using
-        # __encode_unhappy_with_numpy. There are some caveats though:
-
-        # First, we can't handle pre-multiplied alpha.
-        if Channel.A in self.fills:
-            return None
-
-        # Second, in order to get the same results as using image.pixels
-        # (which ignores the colorspace), we need to use the 'Non-Color'
-        # colorspace for all images and set the output device to 'None'. But
-        # setting the colorspace on dirty images discards their changes.
-        # So we can't handle dirty images that aren't already 'Non-Color'.
-        for fill in self.fills:
-            if isinstance(fill, FillImage):
-                if fill.image.is_dirty:
-                    if fill.image.colorspace_settings.name != 'Non-Color':
-                        return None
-
-        tmp_scene = None
-        orig_colorspaces = {}  # remembers original colorspaces
-        try:
-            tmp_scene = bpy.data.scenes.new('##gltf-export:tmp-scene##')
-            tmp_scene.use_nodes = True
-            node_tree = tmp_scene.node_tree
-            for node in node_tree.nodes:
-                node_tree.nodes.remove(node)
-
-            out = node_tree.nodes.new('CompositorNodeComposite')
-            comb_rgba = node_tree.nodes.new('CompositorNodeCombRGBA')
-            for i in range(4):
-                comb_rgba.inputs[i].default_value = 1.0
-            node_tree.links.new(out.inputs['Image'], comb_rgba.outputs['Image'])
-
-            img_size = None
-            for dst_chan, fill in self.fills.items():
-                if not isinstance(fill, FillImage):
-                    continue
-
-                img = node_tree.nodes.new('CompositorNodeImage')
-                img.image = fill.image
-                sep_rgba = node_tree.nodes.new('CompositorNodeSepRGBA')
-                node_tree.links.new(sep_rgba.inputs['Image'], img.outputs['Image'])
-                node_tree.links.new(comb_rgba.inputs[dst_chan], sep_rgba.outputs[fill.src_chan])
-
-                if fill.image.colorspace_settings.name != 'Non-Color':
-                    if fill.image.name not in orig_colorspaces:
-                        orig_colorspaces[fill.image.name] = \
-                            fill.image.colorspace_settings.name
-                    fill.image.colorspace_settings.name = 'Non-Color'
-
-                if img_size is None:
-                    img_size = fill.image.size[:2]
-                else:
-                    # All images should be the same size (should be
-                    # guaranteed by gather_texture_info)
-                    assert img_size == fill.image.size[:2]
-
-            width, height = img_size or (1, 1)
-            return _render_temp_scene(
-                tmp_scene=tmp_scene,
-                width=width,
-                height=height,
-                file_format=self.file_format,
-                color_mode='RGB',
-                colorspace='None',
-            )
-
-        finally:
-            for img_name, colorspace in orig_colorspaces.items():
-                bpy.data.images[img_name].colorspace_settings.name = colorspace
-
-            if tmp_scene is not None:
-                bpy.data.scenes.remove(tmp_scene, do_unlink=True)
-
-
-    def __encode_unhappy_with_numpy(self):
-        # Read the pixels of each image with image.pixels, put them into a
-        # numpy, and assemble the desired image that way. This is the slowest
-        # method, and the conversion to Python data eats a lot of memory, so
-        # it's only used as a last resort.
+        # We need to assemble the image out of channels.
+        # Do it with numpy and image.pixels.
         result = None
 
         img_fills = {
@@ -227,42 +137,40 @@ class ExportImage:
             image = bpy.data.images[image_name]
 
             if result is None:
-                result = np.ones((image.size[0], image.size[1], 4), np.float32)
+                dim = (image.size[0], image.size[1])
+                result = np.ones(dim[0] * dim[1] * 4, np.float32)
+                tmp_buf = np.empty(dim[0] * dim[1] * 4, np.float32)
             # Images should all be the same size (should be guaranteed by
             # gather_texture_info).
-            assert (image.size[0], image.size[1]) == result.shape[:2]
+            assert (image.size[0], image.size[1]) == dim
 
-            # Slow and eats all your memory.
-            pixels = np.array(image.pixels[:])
-
-            pixels = pixels.reshape((image.size[0], image.size[1], image.channels))
+            image.pixels.foreach_get(tmp_buf)
 
             for dst_chan, img_fill in img_fills.items():
                 if img_fill.image == image:
-                    result[:, :, dst_chan] = pixels[:, :, img_fill.src_chan]
+                    result[int(dst_chan)::4] = tmp_buf[int(img_fill.src_chan)::4]
 
-            pixels = None  # GC this please
+        tmp_buf = None  # GC this
 
         if result is None:
             # No ImageFills; use a 1x1 white pixel
+            dim = (1, 1)
             result = np.array([1.0, 1.0, 1.0, 1.0])
-            result = result.reshape((1, 1, 4))
 
-        return self.__encode_from_numpy_array(result)
+        return self.__encode_from_numpy_array(result, dim)
 
-    def __encode_from_numpy_array(self, array: np.ndarray) -> bytes:
+    def __encode_from_numpy_array(self, pixels: np.ndarray, dim: Tuple[int, int]) -> bytes:
         tmp_image = None
         try:
             tmp_image = bpy.data.images.new(
                 "##gltf-export:tmp-image##",
-                width=array.shape[0],
-                height=array.shape[1],
+                width=dim[0],
+                height=dim[1],
                 alpha=Channel.A in self.fills,
             )
             assert tmp_image.channels == 4  # 4 regardless of the alpha argument above.
 
-            # Also slow and eats all your memory.
-            tmp_image.pixels = array.flatten().tolist()
+            tmp_image.pixels.foreach_set(pixels)
 
             return _encode_temp_image(tmp_image, self.file_format)
 
@@ -296,8 +204,13 @@ class ExportImage:
         try:
             tmp_image = image.copy()
             tmp_image.update()
+
             if image.is_dirty:
-                tmp_image.pixels = image.pixels[:]
+                # Copy the pixels to get the changes
+                tmp_buf = np.empty(image.size[0] * image.size[1] * 4, np.float32)
+                image.pixels.foreach_get(tmp_buf)
+                tmp_image.pixels.foreach_set(tmp_buf)
+                tmp_buf = None  # GC this
 
             return _encode_temp_image(tmp_image, self.file_format)
         finally:
@@ -316,36 +229,3 @@ def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes:
 
         with open(tmpfilename, "rb") as f:
             return f.read()
-
-
-def _render_temp_scene(
-    tmp_scene: bpy.types.Scene,
-    width: int,
-    height: int,
-    file_format: str,
-    color_mode: str,
-    colorspace: str,
-) -> bytes:
-    """Set render settings, render to a file, and read back."""
-    tmp_scene.render.resolution_x = width
-    tmp_scene.render.resolution_y = height
-    tmp_scene.render.resolution_percentage = 100
-    tmp_scene.display_settings.display_device = colorspace
-    tmp_scene.render.image_settings.color_mode = color_mode
-    tmp_scene.render.dither_intensity = 0.0
-
-    # Turn off all metadata (stuff like use_stamp_date, etc.)
-    for attr in dir(tmp_scene.render):
-        if attr.startswith('use_stamp_'):
-            setattr(tmp_scene.render, attr, False)
-
-    with tempfile.TemporaryDirectory() as tmpdirname:
-        tmpfilename = tmpdirname + "/

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list