Cleanup: refactor image loading to use abstract ImageLoader base class
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Sun, 8 Mar 2020 13:21:29 +0000 (14:21 +0100)
committerBrecht Van Lommel <brecht@blender.org>
Wed, 11 Mar 2020 19:45:39 +0000 (20:45 +0100)
Rather than passing around void pointers, various Blender image sources now
subclass this. OIIO is also just another type of image loader.

Also fixes T67718: Cycles viewport render crash editing point density settings

17 files changed:
intern/cycles/blender/CMakeLists.txt
intern/cycles/blender/blender_image.cpp
intern/cycles/blender/blender_image.h [new file with mode: 0644]
intern/cycles/blender/blender_session.cpp
intern/cycles/blender/blender_session.h
intern/cycles/blender/blender_shader.cpp
intern/cycles/blender/blender_volume.cpp
intern/cycles/render/CMakeLists.txt
intern/cycles/render/attribute.cpp
intern/cycles/render/attribute.h
intern/cycles/render/image.cpp
intern/cycles/render/image.h
intern/cycles/render/image_oiio.cpp [new file with mode: 0644]
intern/cycles/render/image_oiio.h [new file with mode: 0644]
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
source/blender/makesrna/intern/makesrna.c

index d9a2ebf85711e21804011acc27875c700a826e82..ae191b6c0f7e79381117bd01cc1cfc462468274b 100644 (file)
@@ -38,6 +38,7 @@ set(SRC
   CCL_api.h
   blender_device.h
   blender_id_map.h
+  blender_image.h
   blender_object_cull.h
   blender_sync.h
   blender_session.h
index 55ce6a295d119485154b719e8abcc3b26bfefff5..459dc1779fb281d33065c57ad6960eef867e7123 100644 (file)
  * limitations under the License.
  */
 
-#include "render/image.h"
+#include "MEM_guardedalloc.h"
 
-#include "blender/blender_sync.h"
+#include "blender/blender_image.h"
 #include "blender/blender_session.h"
 #include "blender/blender_util.h"
 
 CCL_NAMESPACE_BEGIN
 
-/* builtin image file name is actually an image datablock name with
- * absolute sequence frame number concatenated via '@' character
- *
- * this function splits frame from builtin name
- */
-int BlenderSession::builtin_image_frame(const string &builtin_name)
+/* Packed Images */
+
+BlenderImageLoader::BlenderImageLoader(BL::Image b_image, int frame)
+    : b_image(b_image), frame(frame), free_cache(!b_image.has_data())
 {
-  int last = builtin_name.find_last_of('@');
-  return atoi(builtin_name.substr(last + 1, builtin_name.size() - last - 1).c_str());
 }
 
-void BlenderSession::builtin_image_info(const string &builtin_name,
-                                        void *builtin_data,
-                                        ImageMetaData &metadata)
+bool BlenderImageLoader::load_metadata(ImageMetaData &metadata)
 {
-  /* empty image */
-  metadata.width = 1;
-  metadata.height = 1;
-
-  if (!builtin_data)
-    return;
-
-  /* recover ID pointer */
-  PointerRNA ptr;
-  RNA_id_pointer_create((ID *)builtin_data, &ptr);
-  BL::ID b_id(ptr);
-
-  if (b_id.is_a(&RNA_Image)) {
-    /* image data */
-    BL::Image b_image(b_id);
+  metadata.width = b_image.size()[0];
+  metadata.height = b_image.size()[1];
+  metadata.depth = 1;
+  metadata.channels = b_image.channels();
 
-    metadata.builtin_free_cache = !b_image.has_data();
-    metadata.is_float = b_image.is_float();
-    metadata.width = b_image.size()[0];
-    metadata.height = b_image.size()[1];
-    metadata.depth = 1;
-    metadata.channels = b_image.channels();
-
-    if (metadata.is_float) {
-      /* Float images are already converted on the Blender side,
-       * no need to do anything in Cycles. */
-      metadata.colorspace = u_colorspace_raw;
+  if (b_image.is_float()) {
+    if (metadata.channels == 1) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT;
     }
-  }
-  else if (b_id.is_a(&RNA_Object)) {
-    /* smoke volume data */
-    BL::Object b_ob(b_id);
-    BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
-
-    metadata.is_float = true;
-    metadata.depth = 1;
-    metadata.channels = 1;
-
-    if (!b_domain)
-      return;
-
-    if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_DENSITY) ||
-        builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_FLAME) ||
-        builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT) ||
-        builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE))
-      metadata.channels = 1;
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_COLOR))
-      metadata.channels = 4;
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY))
-      metadata.channels = 3;
-    else
-      return;
-
-    int3 resolution = get_int3(b_domain.domain_resolution());
-    int amplify = (b_domain.use_noise()) ? b_domain.noise_scale() : 1;
-
-    /* Velocity and heat data is always low-resolution. */
-    if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY) ||
-        builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT)) {
-      amplify = 1;
+    else if (metadata.channels == 4) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT4;
     }
-
-    metadata.width = resolution.x * amplify;
-    metadata.height = resolution.y * amplify;
-    metadata.depth = resolution.z * amplify;
-  }
-  else {
-    /* TODO(sergey): Check we're indeed in shader node tree. */
-    PointerRNA ptr;
-    RNA_pointer_create(NULL, &RNA_Node, builtin_data, &ptr);
-    BL::Node b_node(ptr);
-    if (b_node.is_a(&RNA_ShaderNodeTexPointDensity)) {
-      BL::ShaderNodeTexPointDensity b_point_density_node(b_node);
-      metadata.channels = 4;
-      metadata.width = b_point_density_node.resolution();
-      metadata.height = metadata.width;
-      metadata.depth = metadata.width;
-      metadata.is_float = true;
+    else {
+      return false;
     }
-  }
-}
 
-bool BlenderSession::builtin_image_pixels(const string &builtin_name,
-                                          void *builtin_data,
-                                          int tile,
-                                          unsigned char *pixels,
-                                          const size_t pixels_size,
-                                          const bool associate_alpha,
-                                          const bool free_cache)
-{
-  if (!builtin_data) {
-    return false;
-  }
-
-  const int frame = builtin_image_frame(builtin_name);
-
-  PointerRNA ptr;
-  RNA_id_pointer_create((ID *)builtin_data, &ptr);
-  BL::Image b_image(ptr);
-
-  const int width = b_image.size()[0];
-  const int height = b_image.size()[1];
-  const int channels = b_image.channels();
-
-  unsigned char *image_pixels = image_get_pixels_for_frame(b_image, frame, tile);
-  const size_t num_pixels = ((size_t)width) * height;
-
-  if (image_pixels && num_pixels * channels == pixels_size) {
-    memcpy(pixels, image_pixels, pixels_size * sizeof(unsigned char));
+    /* Float images are already converted on the Blender side,
+     * no need to do anything in Cycles. */
+    metadata.colorspace = u_colorspace_raw;
   }
   else {
-    if (channels == 1) {
-      memset(pixels, 0, pixels_size * sizeof(unsigned char));
+    if (metadata.channels == 1) {
+      metadata.type = IMAGE_DATA_TYPE_BYTE;
+    }
+    else if (metadata.channels == 4) {
+      metadata.type = IMAGE_DATA_TYPE_BYTE4;
     }
     else {
-      const size_t num_pixels_safe = pixels_size / channels;
-      unsigned char *cp = pixels;
-      for (size_t i = 0; i < num_pixels_safe; i++, cp += channels) {
-        cp[0] = 255;
-        cp[1] = 0;
-        cp[2] = 255;
-        if (channels == 4) {
-          cp[3] = 255;
-        }
-      }
+      return false;
     }
   }
 
-  if (image_pixels) {
-    MEM_freeN(image_pixels);
-  }
-
-  /* Free image buffers to save memory during render. */
-  if (free_cache) {
-    b_image.buffers_free();
-  }
-
-  if (associate_alpha) {
-    /* Premultiply, byte images are always straight for Blender. */
-    unsigned char *cp = pixels;
-    for (size_t i = 0; i < num_pixels; i++, cp += channels) {
-      cp[0] = (cp[0] * cp[3]) >> 8;
-      cp[1] = (cp[1] * cp[3]) >> 8;
-      cp[2] = (cp[2] * cp[3]) >> 8;
-    }
-  }
   return true;
 }
 
-bool BlenderSession::builtin_image_float_pixels(const string &builtin_name,
-                                                void *builtin_data,
-                                                int tile,
-                                                float *pixels,
-                                                const size_t pixels_size,
-                                                const bool,
-                                                const bool free_cache)
+bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata,
+                                     void *pixels,
+                                     const size_t pixels_size,
+                                     const bool associate_alpha)
 {
-  if (!builtin_data) {
-    return false;
-  }
+  const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
+  const int channels = metadata.channels;
+  const int tile = 0; /* TODO(lukas): Support tiles here? */
 
-  PointerRNA ptr;
-  RNA_id_pointer_create((ID *)builtin_data, &ptr);
-  BL::ID b_id(ptr);
-
-  if (b_id.is_a(&RNA_Image)) {
+  if (b_image.is_float()) {
     /* image data */
-    BL::Image b_image(b_id);
-    int frame = builtin_image_frame(builtin_name);
-
-    const int width = b_image.size()[0];
-    const int height = b_image.size()[1];
-    const int channels = b_image.channels();
-
     float *image_pixels;
     image_pixels = image_get_float_pixels_for_frame(b_image, frame, tile);
-    const size_t num_pixels = ((size_t)width) * height;
 
     if (image_pixels && num_pixels * channels == pixels_size) {
       memcpy(pixels, image_pixels, pixels_size * sizeof(float));
@@ -224,7 +89,7 @@ bool BlenderSession::builtin_image_float_pixels(const string &builtin_name,
       }
       else {
         const size_t num_pixels_safe = pixels_size / channels;
-        float *fp = pixels;
+        float *fp = (float *)pixels;
         for (int i = 0; i < num_pixels_safe; i++, fp += channels) {
           fp[0] = 1.0f;
           fp[1] = 0.0f;
@@ -239,107 +104,91 @@ bool BlenderSession::builtin_image_float_pixels(const string &builtin_name,
     if (image_pixels) {
       MEM_freeN(image_pixels);
     }
-
-    /* Free image buffers to save memory during render. */
-    if (free_cache) {
-      b_image.buffers_free();
-    }
-
-    return true;
   }
-  else if (b_id.is_a(&RNA_Object)) {
-    /* smoke volume data */
-    BL::Object b_ob(b_id);
-    BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
-
-    if (!b_domain) {
-      return false;
-    }
-#ifdef WITH_FLUID
-    int3 resolution = get_int3(b_domain.domain_resolution());
-    int length, amplify = (b_domain.use_noise()) ? b_domain.noise_scale() : 1;
-
-    /* Velocity and heat data is always low-resolution. */
-    if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY) ||
-        builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT)) {
-      amplify = 1;
-    }
-
-    const int width = resolution.x * amplify;
-    const int height = resolution.y * amplify;
-    const int depth = resolution.z * amplify;
-    const size_t num_pixels = ((size_t)width) * height * depth;
+  else {
+    unsigned char *image_pixels = image_get_pixels_for_frame(b_image, frame, tile);
 
-    if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_DENSITY)) {
-      FluidDomainSettings_density_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels) {
-        FluidDomainSettings_density_grid_get(&b_domain.ptr, pixels);
-        return true;
-      }
-    }
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_FLAME)) {
-      /* this is in range 0..1, and interpreted by the OpenGL smoke viewer
-       * as 1500..3000 K with the first part faded to zero density */
-      FluidDomainSettings_flame_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels) {
-        FluidDomainSettings_flame_grid_get(&b_domain.ptr, pixels);
-        return true;
-      }
+    if (image_pixels && num_pixels * channels == pixels_size) {
+      memcpy(pixels, image_pixels, pixels_size * sizeof(unsigned char));
     }
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_COLOR)) {
-      /* the RGB is "premultiplied" by density for better interpolation results */
-      FluidDomainSettings_color_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels * 4) {
-        FluidDomainSettings_color_grid_get(&b_domain.ptr, pixels);
-        return true;
+    else {
+      if (channels == 1) {
+        memset(pixels, 0, pixels_size * sizeof(unsigned char));
       }
-    }
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY)) {
-      FluidDomainSettings_velocity_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels * 3) {
-        FluidDomainSettings_velocity_grid_get(&b_domain.ptr, pixels);
-        return true;
+      else {
+        const size_t num_pixels_safe = pixels_size / channels;
+        unsigned char *cp = (unsigned char *)pixels;
+        for (size_t i = 0; i < num_pixels_safe; i++, cp += channels) {
+          cp[0] = 255;
+          cp[1] = 0;
+          cp[2] = 255;
+          if (channels == 4) {
+            cp[3] = 255;
+          }
+        }
       }
     }
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT)) {
-      FluidDomainSettings_heat_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels) {
-        FluidDomainSettings_heat_grid_get(&b_domain.ptr, pixels);
-        return true;
-      }
+
+    if (image_pixels) {
+      MEM_freeN(image_pixels);
     }
-    else if (builtin_name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE)) {
-      FluidDomainSettings_temperature_grid_get_length(&b_domain.ptr, &length);
-      if (length == num_pixels) {
-        FluidDomainSettings_temperature_grid_get(&b_domain.ptr, pixels);
-        return true;
+
+    if (associate_alpha) {
+      /* Premultiply, byte images are always straight for Blender. */
+      unsigned char *cp = (unsigned char *)pixels;
+      for (size_t i = 0; i < num_pixels; i++, cp += channels) {
+        cp[0] = (cp[0] * cp[3]) >> 8;
+        cp[1] = (cp[1] * cp[3]) >> 8;
+        cp[2] = (cp[2] * cp[3]) >> 8;
       }
     }
-    else {
-      fprintf(
-          stderr, "Cycles error: unknown volume attribute %s, skipping\n", builtin_name.c_str());
-      pixels[0] = 0.0f;
-      return false;
-    }
-#endif
-    fprintf(stderr, "Cycles error: unexpected smoke volume resolution, skipping\n");
   }
-  else {
-    /* We originally were passing view_layer here but in reality we need a
-     * a depsgraph to pass to the RE_point_density_minmax() function.
-     */
-    /* TODO(sergey): Check we're indeed in shader node tree. */
-    PointerRNA ptr;
-    RNA_pointer_create(NULL, &RNA_Node, builtin_data, &ptr);
-    BL::Node b_node(ptr);
-    if (b_node.is_a(&RNA_ShaderNodeTexPointDensity)) {
-      BL::ShaderNodeTexPointDensity b_point_density_node(b_node);
-      int length;
-      b_point_density_node.calc_point_density(b_depsgraph, &length, &pixels);
-    }
+
+  /* Free image buffers to save memory during render. */
+  if (free_cache) {
+    b_image.buffers_free();
   }
 
-  return false;
+  return true;
+}
+
+string BlenderImageLoader::name() const
+{
+  return BL::Image(b_image).name();
+}
+
+bool BlenderImageLoader::equals(const ImageLoader &other) const
+{
+  const BlenderImageLoader &other_loader = (const BlenderImageLoader &)other;
+  return b_image == other_loader.b_image && frame == other_loader.frame;
+}
+
+/* Point Density */
+
+BlenderPointDensityLoader::BlenderPointDensityLoader(BL::Depsgraph b_depsgraph,
+                                                     BL::ShaderNodeTexPointDensity b_node)
+    : b_depsgraph(b_depsgraph), b_node(b_node)
+{
+}
+
+bool BlenderPointDensityLoader::load_metadata(ImageMetaData &metadata)
+{
+  metadata.channels = 4;
+  metadata.width = b_node.resolution();
+  metadata.height = metadata.width;
+  metadata.depth = metadata.width;
+  metadata.type = IMAGE_DATA_TYPE_FLOAT4;
+  return true;
+}
+
+bool BlenderPointDensityLoader::load_pixels(const ImageMetaData &,
+                                            void *pixels,
+                                            const size_t,
+                                            const bool)
+{
+  int length;
+  b_node.calc_point_density(b_depsgraph, &length, (float **)&pixels);
+  return true;
 }
 
 void BlenderSession::builtin_images_load()
@@ -357,4 +206,15 @@ void BlenderSession::builtin_images_load()
   manager->device_load_builtin(device, session->scene, session->progress);
 }
 
+string BlenderPointDensityLoader::name() const
+{
+  return BL::ShaderNodeTexPointDensity(b_node).name();
+}
+
+bool BlenderPointDensityLoader::equals(const ImageLoader &other) const
+{
+  const BlenderPointDensityLoader &other_loader = (const BlenderPointDensityLoader &)other;
+  return b_node == other_loader.b_node && b_depsgraph == other_loader.b_depsgraph;
+}
+
 CCL_NAMESPACE_END
diff --git a/intern/cycles/blender/blender_image.h b/intern/cycles/blender/blender_image.h
new file mode 100644 (file)
index 0000000..b58a159
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011-2020 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __BLENDER_IMAGE_H__
+#define __BLENDER_IMAGE_H__
+
+#include "RNA_blender_cpp.h"
+
+#include "render/image.h"
+
+CCL_NAMESPACE_BEGIN
+
+class BlenderImageLoader : public ImageLoader {
+ public:
+  BlenderImageLoader(BL::Image b_image, int frame);
+
+  bool load_metadata(ImageMetaData &metadata) override;
+  bool load_pixels(const ImageMetaData &metadata,
+                   void *pixels,
+                   const size_t pixels_size,
+                   const bool associate_alpha) override;
+  string name() const override;
+  bool equals(const ImageLoader &other) const override;
+
+  BL::Image b_image;
+  int frame;
+  bool free_cache;
+};
+
+class BlenderPointDensityLoader : public ImageLoader {
+ public:
+  BlenderPointDensityLoader(BL::Depsgraph depsgraph, BL::ShaderNodeTexPointDensity b_node);
+
+  bool load_metadata(ImageMetaData &metadata) override;
+  bool load_pixels(const ImageMetaData &metadata,
+                   void *pixels,
+                   const size_t pixels_size,
+                   const bool associate_alpha) override;
+  string name() const override;
+  bool equals(const ImageLoader &other) const override;
+
+  BL::Depsgraph b_depsgraph;
+  BL::ShaderNodeTexPointDensity b_node;
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __BLENDER_IMAGE_H__ */
index ac307743e4838c8c82a02d3c41361dc63cf93e62..8e01ad9a96da6ac07948c858f0060503fdb85bb2 100644 (file)
@@ -138,14 +138,6 @@ void BlenderSession::create_session()
   scene = new Scene(scene_params, session->device);
   scene->name = b_scene.name();
 
-  /* setup callbacks for builtin image support */
-  scene->image_manager->builtin_image_info_cb = function_bind(
-      &BlenderSession::builtin_image_info, this, _1, _2, _3);
-  scene->image_manager->builtin_image_pixels_cb = function_bind(
-      &BlenderSession::builtin_image_pixels, this, _1, _2, _3, _4, _5, _6, _7);
-  scene->image_manager->builtin_image_float_pixels_cb = function_bind(
-      &BlenderSession::builtin_image_float_pixels, this, _1, _2, _3, _4, _5, _6, _7);
-
   session->scene = scene;
 
   /* There is no single depsgraph to use for the entire render.
index a107d526dd6418344227f6ec32a344dc567e43bf..3e6498bb6559eee3aa711c375ffe659eb3b5b2c8 100644 (file)
@@ -157,22 +157,6 @@ class BlenderSession {
                                      bool do_update_only);
   void do_write_update_render_tile(RenderTile &rtile, bool do_update_only, bool highlight);
 
-  int builtin_image_frame(const string &builtin_name);
-  void builtin_image_info(const string &builtin_name, void *builtin_data, ImageMetaData &metadata);
-  bool builtin_image_pixels(const string &builtin_name,
-                            void *builtin_data,
-                            int tile,
-                            unsigned char *pixels,
-                            const size_t pixels_size,
-                            const bool associate_alpha,
-                            const bool free_cache);
-  bool builtin_image_float_pixels(const string &builtin_name,
-                                  void *builtin_data,
-                                  int tile,
-                                  float *pixels,
-                                  const size_t pixels_size,
-                                  const bool associate_alpha,
-                                  const bool free_cache);
   void builtin_images_load();
 
   /* Update tile manager to reflect resumable render settings. */
index 64367d5fcd0e675104751cbb3632970ad8d7144d..dc226805664aca9ff5c8f68dcf4d8eb43ed1bc76 100644 (file)
@@ -23,6 +23,7 @@
 #include "render/scene.h"
 #include "render/shader.h"
 
+#include "blender/blender_image.h"
 #include "blender/blender_texture.h"
 #include "blender/blender_sync.h"
 #include "blender/blender_util.h"
@@ -650,6 +651,18 @@ static ShaderNode *add_node(Scene *scene,
     get_tex_mapping(&image->tex_mapping, b_texture_mapping);
 
     if (b_image) {
+      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
+      image->colorspace = get_enum_identifier(colorspace_ptr, "name");
+
+      image->animated = b_image_node.image_user().use_auto_refresh();
+      image->alpha_type = get_image_alpha_type(b_image);
+
+      image->tiles.clear();
+      BL::Image::tiles_iterator b_iter;
+      for (b_image.tiles.begin(b_iter); b_iter != b_image.tiles.end(); ++b_iter) {
+        image->tiles.push_back(b_iter->number());
+      }
+
       /* builtin images will use callback-based reading because
        * they could only be loaded correct from blender side
        */
@@ -666,25 +679,12 @@ static ShaderNode *add_node(Scene *scene,
          */
         int scene_frame = b_scene.frame_current();
         int image_frame = image_user_frame_number(b_image_user, scene_frame);
-        image->filename = b_image.name() + "@" + string_printf("%d", image_frame);
-        image->builtin_data = b_image.ptr.data;
+        image->handle = scene->image_manager->add_image(
+            new BlenderImageLoader(b_image, image_frame), image->image_params());
       }
       else {
         image->filename = image_user_file_path(
             b_image_user, b_image, b_scene.frame_current(), true);
-        image->builtin_data = NULL;
-      }
-
-      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
-      image->colorspace = get_enum_identifier(colorspace_ptr, "name");
-
-      image->animated = b_image_node.image_user().use_auto_refresh();
-      image->alpha_type = get_image_alpha_type(b_image);
-
-      image->tiles.clear();
-      BL::Image::tiles_iterator b_iter;
-      for (b_image.tiles.begin(b_iter); b_iter != b_image.tiles.end(); ++b_iter) {
-        image->tiles.push_back(b_iter->number());
       }
     }
     node = image;
@@ -701,6 +701,12 @@ static ShaderNode *add_node(Scene *scene,
     get_tex_mapping(&env->tex_mapping, b_texture_mapping);
 
     if (b_image) {
+      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
+      env->colorspace = get_enum_identifier(colorspace_ptr, "name");
+
+      env->animated = b_env_node.image_user().use_auto_refresh();
+      env->alpha_type = get_image_alpha_type(b_image);
+
       bool is_builtin = b_image.packed_file() || b_image.source() == BL::Image::source_GENERATED ||
                         b_image.source() == BL::Image::source_MOVIE ||
                         (b_engine.is_preview() && b_image.source() != BL::Image::source_SEQUENCE);
@@ -708,20 +714,13 @@ static ShaderNode *add_node(Scene *scene,
       if (is_builtin) {
         int scene_frame = b_scene.frame_current();
         int image_frame = image_user_frame_number(b_image_user, scene_frame);
-        env->filename = b_image.name() + "@" + string_printf("%d", image_frame);
-        env->builtin_data = b_image.ptr.data;
+        env->handle = scene->image_manager->add_image(new BlenderImageLoader(b_image, image_frame),
+                                                      env->image_params());
       }
       else {
         env->filename = image_user_file_path(
             b_image_user, b_image, b_scene.frame_current(), false);
-        env->builtin_data = NULL;
       }
-
-      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
-      env->colorspace = get_enum_identifier(colorspace_ptr, "name");
-
-      env->animated = b_env_node.image_user().use_auto_refresh();
-      env->alpha_type = get_image_alpha_type(b_image);
     }
     node = env;
   }
@@ -865,12 +864,12 @@ static ShaderNode *add_node(Scene *scene,
   else if (b_node.is_a(&RNA_ShaderNodeTexPointDensity)) {
     BL::ShaderNodeTexPointDensity b_point_density_node(b_node);
     PointDensityTextureNode *point_density = new PointDensityTextureNode();
-    point_density->filename = b_point_density_node.name();
     point_density->space = (NodeTexVoxelSpace)b_point_density_node.space();
     point_density->interpolation = get_image_interpolation(b_point_density_node);
-    point_density->builtin_data = b_point_density_node.ptr.data;
+    point_density->handle = scene->image_manager->add_image(
+        new BlenderPointDensityLoader(b_depsgraph, b_point_density_node),
+        point_density->image_params());
 
-    point_density->add_image(scene->image_manager);
     b_point_density_node.cache_point_density(b_depsgraph);
     node = point_density;
 
index e11cc4ab18f3136b04aafbc52a2e540a33a74736..60ef4c5ed877ea84fea13735c5a44a8e5b58b401 100644 (file)
 
 CCL_NAMESPACE_BEGIN
 
+/* TODO: verify this is not loading unnecessary attributes. */
+class BlenderSmokeLoader : public ImageLoader {
+ public:
+  BlenderSmokeLoader(const BL::Object &b_ob, AttributeStandard attribute)
+      : b_ob(b_ob), attribute(attribute)
+  {
+  }
+
+  bool load_metadata(ImageMetaData &metadata) override
+  {
+    BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
+
+    if (!b_domain) {
+      return false;
+    }
+
+    if (attribute == ATTR_STD_VOLUME_DENSITY || attribute == ATTR_STD_VOLUME_FLAME ||
+        attribute == ATTR_STD_VOLUME_HEAT || attribute == ATTR_STD_VOLUME_TEMPERATURE) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT;
+      metadata.channels = 1;
+    }
+    else if (attribute == ATTR_STD_VOLUME_COLOR) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT4;
+      metadata.channels = 4;
+    }
+    else if (attribute == ATTR_STD_VOLUME_VELOCITY) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT4;
+      metadata.channels = 3;
+    }
+    else {
+      return false;
+    }
+
+    int3 resolution = get_int3(b_domain.domain_resolution());
+    int amplify = (b_domain.use_noise()) ? b_domain.noise_scale() : 1;
+
+    /* Velocity and heat data is always low-resolution. */
+    if (attribute == ATTR_STD_VOLUME_VELOCITY || attribute == ATTR_STD_VOLUME_HEAT) {
+      amplify = 1;
+    }
+
+    metadata.width = resolution.x * amplify;
+    metadata.height = resolution.y * amplify;
+    metadata.depth = resolution.z * amplify;
+
+    return true;
+  }
+
+  bool load_pixels(const ImageMetaData &, void *pixels, const size_t, const bool) override
+  {
+    /* smoke volume data */
+    BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
+
+    if (!b_domain) {
+      return false;
+    }
+#ifdef WITH_FLUID
+    int3 resolution = get_int3(b_domain.domain_resolution());
+    int length, amplify = (b_domain.use_noise()) ? b_domain.noise_scale() : 1;
+
+    /* Velocity and heat data is always low-resolution. */
+    if (attribute == ATTR_STD_VOLUME_VELOCITY || attribute == ATTR_STD_VOLUME_HEAT) {
+      amplify = 1;
+    }
+
+    const int width = resolution.x * amplify;
+    const int height = resolution.y * amplify;
+    const int depth = resolution.z * amplify;
+    const size_t num_pixels = ((size_t)width) * height * depth;
+
+    float *fpixels = (float *)pixels;
+
+    if (attribute == ATTR_STD_VOLUME_DENSITY) {
+      FluidDomainSettings_density_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels) {
+        FluidDomainSettings_density_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else if (attribute == ATTR_STD_VOLUME_FLAME) {
+      /* this is in range 0..1, and interpreted by the OpenGL smoke viewer
+       * as 1500..3000 K with the first part faded to zero density */
+      FluidDomainSettings_flame_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels) {
+        FluidDomainSettings_flame_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else if (attribute == ATTR_STD_VOLUME_COLOR) {
+      /* the RGB is "premultiplied" by density for better interpolation results */
+      FluidDomainSettings_color_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels * 4) {
+        FluidDomainSettings_color_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else if (attribute == ATTR_STD_VOLUME_VELOCITY) {
+      FluidDomainSettings_velocity_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels * 3) {
+        FluidDomainSettings_velocity_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else if (attribute == ATTR_STD_VOLUME_HEAT) {
+      FluidDomainSettings_heat_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels) {
+        FluidDomainSettings_heat_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else if (attribute == ATTR_STD_VOLUME_TEMPERATURE) {
+      FluidDomainSettings_temperature_grid_get_length(&b_domain.ptr, &length);
+      if (length == num_pixels) {
+        FluidDomainSettings_temperature_grid_get(&b_domain.ptr, fpixels);
+        return true;
+      }
+    }
+    else {
+      fprintf(stderr,
+              "Cycles error: unknown volume attribute %s, skipping\n",
+              Attribute::standard_name(attribute));
+      fpixels[0] = 0.0f;
+      return false;
+    }
+#else
+    (void)pixels;
+#endif
+    fprintf(stderr, "Cycles error: unexpected smoke volume resolution, skipping\n");
+    return false;
+  }
+
+  string name() const override
+  {
+    return Attribute::standard_name(attribute);
+  }
+
+  bool equals(const ImageLoader &other) const override
+  {
+    const BlenderSmokeLoader &other_loader = (const BlenderSmokeLoader &)other;
+    return b_ob == other_loader.b_ob && attribute == other_loader.attribute;
+  }
+
+  BL::Object b_ob;
+  AttributeStandard attribute;
+};
+
 static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float frame)
 {
   BL::FluidDomainSettings b_domain = object_fluid_gas_domain_find(b_ob);
@@ -30,7 +176,6 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float
     return;
   }
 
-  ImageManager *image_manager = scene->image_manager;
   AttributeStandard attributes[] = {ATTR_STD_VOLUME_DENSITY,
                                     ATTR_STD_VOLUME_COLOR,
                                     ATTR_STD_VOLUME_FLAME,
@@ -49,11 +194,11 @@ static void sync_smoke_volume(Scene *scene, BL::Object &b_ob, Mesh *mesh, float
 
     Attribute *attr = mesh->attributes.add(std);
 
-    ImageKey key;
-    key.filename = Attribute::standard_name(std);
-    key.builtin_data = b_ob.ptr.data;
+    ImageLoader *loader = new BlenderSmokeLoader(b_ob, std);
+    ImageParams params;
+    params.frame = frame;
 
-    attr->add(image_manager->add_image(key, frame));
+    attr->data_voxel() = scene->image_manager->add_image(loader, params);
   }
 
   /* Create a matrix to transform from object space to mesh texture space.
index 6cde52bb89dcfbf89aaa08babacfd23544938c5e..9922547a8d2eb0c0fa591aea3c4ed213f6ca65a2 100644 (file)
@@ -23,6 +23,7 @@ set(SRC
   graph.cpp
   hair.cpp
   image.cpp
+  image_oiio.cpp
   integrator.cpp
   jitter.cpp
   light.cpp
@@ -61,6 +62,7 @@ set(SRC_HEADERS
   graph.h
   hair.h
   image.h
+  image_oiio.h
   integrator.h
   light.h
   jitter.h
index fffd26f906a8171dc9925094836c2accddfa3cb4..d0d9888ac88081ca9d024d97b55b221f194cca30 100644 (file)
@@ -26,6 +26,25 @@ CCL_NAMESPACE_BEGIN
 
 /* Attribute */
 
+Attribute::Attribute(
+    ustring name, TypeDesc type, AttributeElement element, Geometry *geom, AttributePrimitive prim)
+    : name(name), std(ATTR_STD_NONE), type(type), element(element), flags(0)
+{
+  /* string and matrix not supported! */
+  assert(type == TypeDesc::TypeFloat || type == TypeDesc::TypeColor ||
+         type == TypeDesc::TypePoint || type == TypeDesc::TypeVector ||
+         type == TypeDesc::TypeNormal || type == TypeDesc::TypeMatrix || type == TypeFloat2 ||
+         type == TypeRGBA);
+
+  if (element == ATTR_ELEMENT_VOXEL) {
+    buffer.resize(sizeof(ImageHandle));
+    new (buffer.data()) ImageHandle();
+  }
+  else {
+    resize(geom, prim, false);
+  }
+}
+
 Attribute::~Attribute()
 {
   /* For voxel data, we need to free the image handle. */
@@ -35,34 +54,23 @@ Attribute::~Attribute()
   }
 }
 
-void Attribute::set(ustring name_, TypeDesc type_, AttributeElement element_)
-{
-  name = name_;
-  type = type_;
-  element = element_;
-  std = ATTR_STD_NONE;
-  flags = 0;
-
-  /* string and matrix not supported! */
-  assert(type == TypeDesc::TypeFloat || type == TypeDesc::TypeColor ||
-         type == TypeDesc::TypePoint || type == TypeDesc::TypeVector ||
-         type == TypeDesc::TypeNormal || type == TypeDesc::TypeMatrix || type == TypeFloat2 ||
-         type == TypeRGBA);
-}
-
 void Attribute::resize(Geometry *geom, AttributePrimitive prim, bool reserve_only)
 {
-  if (reserve_only) {
-    buffer.reserve(buffer_size(geom, prim));
-  }
-  else {
-    buffer.resize(buffer_size(geom, prim), 0);
+  if (element != ATTR_ELEMENT_VOXEL) {
+    if (reserve_only) {
+      buffer.reserve(buffer_size(geom, prim));
+    }
+    else {
+      buffer.resize(buffer_size(geom, prim), 0);
+    }
   }
 }
 
 void Attribute::resize(size_t num_elements)
 {
-  buffer.resize(num_elements * data_sizeof(), 0);
+  if (element != ATTR_ELEMENT_VOXEL) {
+    buffer.resize(num_elements * data_sizeof(), 0);
+  }
 }
 
 void Attribute::add(const float &f)
@@ -120,15 +128,6 @@ void Attribute::add(const Transform &f)
     buffer.push_back(data[i]);
 }
 
-void Attribute::add(const ImageHandle &handle)
-{
-  assert(data_sizeof() == sizeof(ImageHandle));
-  assert(buffer.size() == 0);
-
-  buffer.resize(sizeof(ImageHandle));
-  new (buffer.data()) ImageHandle(handle);
-}
-
 void Attribute::add(const char *data)
 {
   size_t size = data_sizeof();
@@ -409,23 +408,9 @@ Attribute *AttributeSet::add(ustring name, TypeDesc type, AttributeElement eleme
     remove(name);
   }
 
-#if __cplusplus >= 201103L
-  attributes.emplace_back();
-  attr = &attributes.back();
-  attr->set(name, type, element);
-#else
-  {
-    Attribute attr_temp;
-    attr_temp.set(name, type, element);
-    attributes.push_back(attr_temp);
-    attr = &attributes.back();
-  }
-#endif
-
-  /* this is weak .. */
-  attr->resize(geometry, prim, false);
-
-  return attr;
+  Attribute new_attr(name, type, element, geometry, prim);
+  attributes.emplace_back(std::move(new_attr));
+  return &attributes.back();
 }
 
 Attribute *AttributeSet::find(ustring name) const
index 23626410ac122f82bea7c365cf5b6c6d221c036a..351357a7f7a194dd246b2001b8a626e325224f8c 100644 (file)
@@ -54,9 +54,11 @@ class Attribute {
   AttributeElement element;
   uint flags; /* enum AttributeFlag */
 
-  Attribute()
-  {
-  }
+  Attribute(ustring name,
+            TypeDesc type,
+            AttributeElement element,
+            Geometry *geom,
+            AttributePrimitive prim);
   ~Attribute();
   void set(ustring name, TypeDesc type, AttributeElement element);
   void resize(Geometry *geom, AttributePrimitive prim, bool reserve_only);
@@ -151,7 +153,6 @@ class Attribute {
   void add(const float3 &f);
   void add(const uchar4 &f);
   void add(const Transform &tfm);
-  void add(const ImageHandle &handle);
   void add(const char *data);
 
   static bool same_storage(TypeDesc a, TypeDesc b);
index 123bb1294669f703547149fb9014b8b2b9f5e84e..b4539b5ce3c2bfb7490000acaca9ba845363056e 100644 (file)
  */
 
 #include "render/image.h"
+#include "render/image_oiio.h"
 #include "device/device.h"
 #include "render/colorspace.h"
 #include "render/scene.h"
 #include "render/stats.h"
 
 #include "util/util_foreach.h"
+#include "util/util_image.h"
 #include "util/util_image_impl.h"
 #include "util/util_logging.h"
 #include "util/util_path.h"
@@ -85,10 +87,11 @@ ImageHandle::ImageHandle() : manager(NULL)
 {
 }
 
-ImageHandle::ImageHandle(const ImageHandle &other) : slots(other.slots), manager(other.manager)
+ImageHandle::ImageHandle(const ImageHandle &other)
+    : tile_slots(other.tile_slots), manager(other.manager)
 {
   /* Increase image user count. */
-  foreach (const int slot, slots) {
+  foreach (const int slot, tile_slots) {
     manager->add_image_user(slot);
   }
 }
@@ -97,9 +100,9 @@ ImageHandle &ImageHandle::operator=(const ImageHandle &other)
 {
   clear();
   manager = other.manager;
-  slots = other.slots;
+  tile_slots = other.tile_slots;
 
-  foreach (const int slot, slots) {
+  foreach (const int slot, tile_slots) {
     manager->add_image_user(slot);
   }
 
@@ -113,275 +116,265 @@ ImageHandle::~ImageHandle()
 
 void ImageHandle::clear()
 {
-  foreach (const int slot, slots) {
+  foreach (const int slot, tile_slots) {
     manager->remove_image_user(slot);
   }
 }
 
 bool ImageHandle::empty()
 {
-  return slots.empty();
+  return tile_slots.empty();
 }
 
 int ImageHandle::num_tiles()
 {
-  return slots.size();
+  return tile_slots.size();
 }
 
 ImageMetaData ImageHandle::metadata()
 {
-  if (slots.empty()) {
+  if (tile_slots.empty()) {
     return ImageMetaData();
   }
 
-  return manager->images[slots.front()]->metadata;
+  ImageManager::Image *img = manager->images[tile_slots.front()];
+  manager->load_image_metadata(img);
+  return img->metadata;
 }
 
-int ImageHandle::svm_slot(const int tile_index)
+int ImageHandle::svm_slot(const int tile_index) const
 {
-  if (tile_index >= slots.size()) {
+  if (tile_index >= tile_slots.size()) {
     return -1;
   }
 
   if (manager->osl_texture_system) {
-    ImageManager::Image *img = manager->images[slots[tile_index]];
-    if (!img->key.builtin_data) {
+    ImageManager::Image *img = manager->images[tile_slots[tile_index]];
+    if (img->loader->osl_filepath()) {
       return -1;
     }
   }
 
-  return slots[tile_index];
+  return tile_slots[tile_index];
 }
 
-device_memory *ImageHandle::image_memory(const int tile_index)
+device_memory *ImageHandle::image_memory(const int tile_index) const
 {
-  if (tile_index >= slots.size()) {
+  if (tile_index >= tile_slots.size()) {
     return NULL;
   }
 
-  ImageManager::Image *img = manager->images[slots[tile_index]];
+  ImageManager::Image *img = manager->images[tile_slots[tile_index]];
   return img ? img->mem : NULL;
 }
 
-/* Image Manager */
-
-ImageManager::ImageManager(const DeviceInfo &info)
+bool ImageHandle::operator==(const ImageHandle &other) const
 {
-  need_update = true;
-  osl_texture_system = NULL;
-  animation_frame = 0;
-
-  /* Set image limits */
-  has_half_images = info.has_half_images;
+  return manager == other.manager && tile_slots == other.tile_slots;
 }
 
-ImageManager::~ImageManager()
+/* Image MetaData */
+
+ImageMetaData::ImageMetaData()
+    : channels(0),
+      width(0),
+      height(0),
+      depth(0),
+      type(IMAGE_DATA_NUM_TYPES),
+      colorspace(u_colorspace_raw),
+      colorspace_file_format(""),
+      compress_as_srgb(false)
 {
-  for (size_t slot = 0; slot < images.size(); slot++)
-    assert(!images[slot]);
 }
 
-void ImageManager::set_osl_texture_system(void *texture_system)
+bool ImageMetaData::operator==(const ImageMetaData &other) const
 {
-  osl_texture_system = texture_system;
+  return channels == other.channels && width == other.width && height == other.height &&
+         depth == other.depth && type == other.type && colorspace == other.colorspace &&
+         compress_as_srgb == other.compress_as_srgb;
 }
 
-bool ImageManager::set_animation_frame_update(int frame)
+bool ImageMetaData::is_float() const
 {
-  if (frame != animation_frame) {
-    animation_frame = frame;
-
-    for (size_t slot = 0; slot < images.size(); slot++) {
-      if (images[slot] && images[slot]->key.animated)
-        return true;
-    }
-  }
-
-  return false;
+  return (type == IMAGE_DATA_TYPE_FLOAT || type == IMAGE_DATA_TYPE_FLOAT4 ||
+          type == IMAGE_DATA_TYPE_HALF || type == IMAGE_DATA_TYPE_HALF4);
 }
 
-void ImageManager::metadata_detect_colorspace(ImageMetaData &metadata, const char *file_format)
+void ImageMetaData::detect_colorspace()
 {
   /* Convert used specified color spaces to one we know how to handle. */
-  metadata.colorspace = ColorSpaceManager::detect_known_colorspace(
-      metadata.colorspace, file_format, metadata.is_float || metadata.is_half);
+  colorspace = ColorSpaceManager::detect_known_colorspace(
+      colorspace, colorspace_file_format, is_float());
 
-  if (metadata.colorspace == u_colorspace_raw) {
+  if (colorspace == u_colorspace_raw) {
     /* Nothing to do. */
   }
-  else if (metadata.colorspace == u_colorspace_srgb) {
+  else if (colorspace == u_colorspace_srgb) {
     /* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time
      * for the common case of 8bit sRGB images like PNG. */
-    metadata.compress_as_srgb = true;
+    compress_as_srgb = true;
   }
   else {
     /* Always compress non-raw 8bit images as scene linear + sRGB, as a
      * heuristic to keep memory usage the same without too much data loss
      * due to quantization in common cases. */
-    metadata.compress_as_srgb = (metadata.type == IMAGE_DATA_TYPE_BYTE ||
-                                 metadata.type == IMAGE_DATA_TYPE_BYTE4);
+    compress_as_srgb = (type == IMAGE_DATA_TYPE_BYTE || type == IMAGE_DATA_TYPE_BYTE4);
 
     /* If colorspace conversion needed, use half instead of short so we can
      * represent HDR values that might result from conversion. */
-    if (metadata.type == IMAGE_DATA_TYPE_USHORT) {
-      metadata.type = IMAGE_DATA_TYPE_HALF;
+    if (type == IMAGE_DATA_TYPE_USHORT) {
+      type = IMAGE_DATA_TYPE_HALF;
     }
-    else if (metadata.type == IMAGE_DATA_TYPE_USHORT4) {
-      metadata.type = IMAGE_DATA_TYPE_HALF4;
+    else if (type == IMAGE_DATA_TYPE_USHORT4) {
+      type = IMAGE_DATA_TYPE_HALF4;
     }
   }
 }
 
-bool ImageManager::load_image_metadata(const ImageKey &key, ImageMetaData &metadata)
-{
-  metadata = ImageMetaData();
-  metadata.colorspace = key.colorspace;
-
-  if (key.builtin_data) {
-    if (builtin_image_info_cb) {
-      builtin_image_info_cb(key.filename, key.builtin_data, metadata);
-    }
-    else {
-      return false;
-    }
+/* Image Loader */
 
-    if (metadata.is_float) {
-      metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT;
-    }
-    else {
-      metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE;
-    }
+ImageLoader::ImageLoader()
+{
+}
 
-    metadata_detect_colorspace(metadata, "");
+ustring ImageLoader::osl_filepath() const
+{
+  return ustring();
+}
 
+bool ImageLoader::equals(const ImageLoader *a, const ImageLoader *b)
+{
+  if (a == NULL && b == NULL) {
     return true;
   }
-
-  /* Perform preliminary checks, with meaningful logging. */
-  if (!path_exists(key.filename)) {
-    VLOG(1) << "File '" << key.filename << "' does not exist.";
-    return false;
-  }
-  if (path_is_directory(key.filename)) {
-    VLOG(1) << "File '" << key.filename << "' is a directory, can't use as image.";
-    return false;
+  else {
+    return (a && b && typeid(*a) == typeid(*b) && a->equals(*b));
   }
+}
 
-  unique_ptr<ImageInput> in(ImageInput::create(key.filename));
+/* Image Manager */
 
-  if (!in) {
-    return false;
-  }
+ImageManager::ImageManager(const DeviceInfo &info)
+{
+  need_update = true;
+  osl_texture_system = NULL;
+  animation_frame = 0;
 
-  ImageSpec spec;
-  if (!in->open(key.filename, spec)) {
-    return false;
-  }
+  /* Set image limits */
+  has_half_images = info.has_half_images;
+}
 
-  metadata.width = spec.width;
-  metadata.height = spec.height;
-  metadata.depth = spec.depth;
-  metadata.compress_as_srgb = false;
+ImageManager::~ImageManager()
+{
+  for (size_t slot = 0; slot < images.size(); slot++)
+    assert(!images[slot]);
+}
 
-  /* Check the main format, and channel formats. */
-  size_t channel_size = spec.format.basesize();
+void ImageManager::set_osl_texture_system(void *texture_system)
+{
+  osl_texture_system = texture_system;
+}
 
-  if (spec.format.is_floating_point()) {
-    metadata.is_float = true;
-  }
+bool ImageManager::set_animation_frame_update(int frame)
+{
+  if (frame != animation_frame) {
+    animation_frame = frame;
 
-  for (size_t channel = 0; channel < spec.channelformats.size(); channel++) {
-    channel_size = max(channel_size, spec.channelformats[channel].basesize());
-    if (spec.channelformats[channel].is_floating_point()) {
-      metadata.is_float = true;
+    for (size_t slot = 0; slot < images.size(); slot++) {
+      if (images[slot] && images[slot]->params.animated)
+        return true;
     }
   }
 
-  /* check if it's half float */
-  if (spec.format == TypeDesc::HALF) {
-    metadata.is_half = true;
-  }
-
-  /* set type and channels */
-  metadata.channels = spec.nchannels;
+  return false;
+}
 
-  if (metadata.is_half) {
-    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_HALF4 : IMAGE_DATA_TYPE_HALF;
-  }
-  else if (metadata.is_float) {
-    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT;
-  }
-  else if (spec.format == TypeDesc::USHORT) {
-    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_USHORT4 : IMAGE_DATA_TYPE_USHORT;
+void ImageManager::load_image_metadata(Image *img)
+{
+  if (!img->need_metadata) {
+    return;
   }
-  else {
-    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE;
+
+  thread_scoped_lock image_lock(img->mutex);
+  if (!img->need_metadata) {
+    return;
   }
 
-  metadata_detect_colorspace(metadata, in->format_name());
+  ImageMetaData &metadata = img->metadata;
+  metadata = ImageMetaData();
+  metadata.colorspace = img->params.colorspace;
 
-  in->close();
+  img->loader->load_metadata(metadata);
 
-  return true;
+  metadata.detect_colorspace();
+
+  /* No half textures on OpenCL, use full float instead. */
+  if (!has_half_images) {
+    if (metadata.type == IMAGE_DATA_TYPE_HALF4) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT4;
+    }
+    else if (metadata.type == IMAGE_DATA_TYPE_HALF) {
+      metadata.type = IMAGE_DATA_TYPE_FLOAT;
+    }
+  }
+
+  img->need_metadata = false;
 }
 
-ImageHandle ImageManager::add_image(const ImageKey &key, float frame)
+ImageHandle ImageManager::add_image(const string &filename, const ImageParams &params)
 {
+  const int slot = add_image_slot(new OIIOImageLoader(filename), params, false);
+
   ImageHandle handle;
-  handle.slots.push_back(add_image_slot(key, frame));
+  handle.tile_slots.push_back(slot);
   handle.manager = this;
   return handle;
 }
 
-ImageHandle ImageManager::add_image(const ImageKey &key, float frame, const vector<int> &tiles)
+ImageHandle ImageManager::add_image(const string &filename,
+                                    const ImageParams &params,
+                                    const vector<int> &tiles)
 {
   ImageHandle handle;
   handle.manager = this;
 
   foreach (int tile, tiles) {
-    ImageKey tile_key = key;
+    string tile_filename = filename;
     if (tile != 0) {
-      string_replace(tile_key.filename, "<UDIM>", string_printf("%04d", tile));
+      string_replace(tile_filename, "<UDIM>", string_printf("%04d", tile));
     }
-    handle.slots.push_back(add_image_slot(tile_key, frame));
+    const int slot = add_image_slot(new OIIOImageLoader(tile_filename), params, false);
+    handle.tile_slots.push_back(slot);
   }
 
   return handle;
 }
 
-int ImageManager::add_image_slot(const ImageKey &key, float frame)
+ImageHandle ImageManager::add_image(ImageLoader *loader, const ImageParams &params)
+{
+  const int slot = add_image_slot(loader, params, true);
+
+  ImageHandle handle;
+  handle.tile_slots.push_back(slot);
+  handle.manager = this;
+  return handle;
+}
+
+int ImageManager::add_image_slot(ImageLoader *loader,
+                                 const ImageParams &params,
+                                 const bool builtin)
 {
   Image *img;
   size_t slot;
 
-  ImageMetaData metadata;
-  load_image_metadata(key, metadata);
-
   thread_scoped_lock device_lock(device_mutex);
 
-  /* No half textures on OpenCL, use full float instead. */
-  if (!has_half_images) {
-    if (metadata.type == IMAGE_DATA_TYPE_HALF4) {
-      metadata.type = IMAGE_DATA_TYPE_FLOAT4;
-    }
-    else if (metadata.type == IMAGE_DATA_TYPE_HALF) {
-      metadata.type = IMAGE_DATA_TYPE_FLOAT;
-    }
-  }
-
   /* Fnd existing image. */
   for (slot = 0; slot < images.size(); slot++) {
     img = images[slot];
-    if (img && img->key == key) {
-      if (img->frame != frame) {
-        img->frame = frame;
-        img->need_load = true;
-      }
-      if (!(img->metadata == metadata)) {
-        img->metadata = metadata;
-        img->need_load = true;
-      }
+    if (img && ImageLoader::equals(img->loader, loader) && img->params == params) {
       img->users++;
+      delete loader;
       return slot;
     }
   }
@@ -398,10 +391,11 @@ int ImageManager::add_image_slot(const ImageKey &key, float frame)
 
   /* Add new image. */
   img = new Image();
-  img->key = key;
-  img->frame = frame;
-  img->metadata = metadata;
-  img->need_load = true;
+  img->params = params;
+  img->loader = loader;
+  img->need_metadata = true;
+  img->need_load = !(osl_texture_system && img->loader->osl_filepath());
+  img->builtin = builtin;
   img->users = 1;
   img->mem = NULL;
 
@@ -439,54 +433,9 @@ static bool image_associate_alpha(ImageManager::Image *img)
 {
   /* For typical RGBA images we let OIIO convert to associated alpha,
    * but some types we want to leave the RGB channels untouched. */
-  return !(ColorSpaceManager::colorspace_is_data(img->key.colorspace) ||
-           img->key.alpha_type == IMAGE_ALPHA_IGNORE ||
-           img->key.alpha_type == IMAGE_ALPHA_CHANNEL_PACKED);
-}
-
-bool ImageManager::file_load_image_generic(Image *img, unique_ptr<ImageInput> *in)
-{
-  if (img->key.filename == "")
-    return false;
-
-  if (!img->key.builtin_data) {
-    /* NOTE: Error logging is done in meta data acquisition. */
-    if (!path_exists(img->key.filename) || path_is_directory(img->key.filename)) {
-      return false;
-    }
-
-    /* load image from file through OIIO */
-    *in = unique_ptr<ImageInput>(ImageInput::create(img->key.filename));
-
-    if (!*in)
-      return false;
-
-    ImageSpec spec = ImageSpec();
-    ImageSpec config = ImageSpec();
-
-    if (!image_associate_alpha(img)) {
-      config.attribute("oiio:UnassociatedAlpha", 1);
-    }
-
-    if (!(*in)->open(img->key.filename, spec, config)) {
-      return false;
-    }
-  }
-  else {
-    /* load image using builtin images callbacks */
-    if (!builtin_image_info_cb || !builtin_image_pixels_cb)
-      return false;
-  }
-
-  /* we only handle certain number of components */
-  if (!(img->metadata.channels >= 1 && img->metadata.channels <= 4)) {
-    if (*in) {
-      (*in)->close();
-    }
-    return false;
-  }
-
-  return true;
+  return !(ColorSpaceManager::colorspace_is_data(img->params.colorspace) ||
+           img->params.alpha_type == IMAGE_ALPHA_IGNORE ||
+           img->params.alpha_type == IMAGE_ALPHA_CHANNEL_PACKED);
 }
 
 template<TypeDesc::BASETYPE FileFormat, typename StorageType, typename DeviceType>
@@ -494,8 +443,8 @@ bool ImageManager::file_load_image(Image *img,
                                    int texture_limit,
                                    device_vector<DeviceType> &tex_img)
 {
-  unique_ptr<ImageInput> in = NULL;
-  if (!file_load_image_generic(img, &in)) {
+  /* we only handle certain number of components */
+  if (!(img->metadata.channels >= 1 && img->metadata.channels <= 4)) {
     return false;
   }
 
@@ -529,67 +478,9 @@ bool ImageManager::file_load_image(Image *img,
     return false;
   }
 
-  bool cmyk = false;
   const size_t num_pixels = ((size_t)width) * height * depth;
-  if (in) {
-    /* Read pixels through OpenImageIO. */
-    StorageType *readpixels = pixels;
-    vector<StorageType> tmppixels;
-    if (components > 4) {
-      tmppixels.resize(((size_t)width) * height * components);
-      readpixels = &tmppixels[0];
-    }
-
-    if (depth <= 1) {
-      size_t scanlinesize = ((size_t)width) * components * sizeof(StorageType);
-      in->read_image(FileFormat,
-                     (uchar *)readpixels + (height - 1) * scanlinesize,
-                     AutoStride,
-                     -scanlinesize,
-                     AutoStride);
-    }
-    else {
-      in->read_image(FileFormat, (uchar *)readpixels);
-    }
-
-    if (components > 4) {
-      size_t dimensions = ((size_t)width) * height;
-      for (size_t i = dimensions - 1, pixel = 0; pixel < dimensions; pixel++, i--) {
-        pixels[i * 4 + 3] = tmppixels[i * components + 3];
-        pixels[i * 4 + 2] = tmppixels[i * components + 2];
-        pixels[i * 4 + 1] = tmppixels[i * components + 1];
-        pixels[i * 4 + 0] = tmppixels[i * components + 0];
-      }
-      tmppixels.clear();
-    }
-
-    cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4;
-    in->close();
-  }
-  else {
-    /* Read pixels through callback. */
-    if (FileFormat == TypeDesc::FLOAT) {
-      builtin_image_float_pixels_cb(img->key.filename,
-                                    img->key.builtin_data,
-                                    0, /* TODO(lukas): Support tiles here? */
-                                    (float *)&pixels[0],
-                                    num_pixels * components,
-                                    image_associate_alpha(img),
-                                    img->metadata.builtin_free_cache);
-    }
-    else if (FileFormat == TypeDesc::UINT8) {
-      builtin_image_pixels_cb(img->key.filename,
-                              img->key.builtin_data,
-                              0, /* TODO(lukas): Support tiles here? */
-                              (uchar *)&pixels[0],
-                              num_pixels * components,
-                              image_associate_alpha(img),
-                              img->metadata.builtin_free_cache);
-    }
-    else {
-      /* TODO(dingto): Support half for ImBuf. */
-    }
-  }
+  img->loader->load_pixels(
+      img->metadata, pixels, num_pixels * components, image_associate_alpha(img));
 
   /* The kernel can handle 1 and 4 channel images. Anything that is not a single
    * channel image is converted to RGBA format. */
@@ -601,20 +492,7 @@ bool ImageManager::file_load_image(Image *img,
   if (is_rgba) {
     const StorageType one = util_image_cast_from_float<StorageType>(1.0f);
 
-    if (cmyk) {
-      /* CMYK to RGBA. */
-      for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) {
-        float c = util_image_cast_to_float(pixels[i * 4 + 0]);
-        float m = util_image_cast_to_float(pixels[i * 4 + 1]);
-        float y = util_image_cast_to_float(pixels[i * 4 + 2]);
-        float k = util_image_cast_to_float(pixels[i * 4 + 3]);
-        pixels[i * 4 + 0] = util_image_cast_from_float<StorageType>((1.0f - c) * (1.0f - k));
-        pixels[i * 4 + 1] = util_image_cast_from_float<StorageType>((1.0f - m) * (1.0f - k));
-        pixels[i * 4 + 2] = util_image_cast_from_float<StorageType>((1.0f - y) * (1.0f - k));
-        pixels[i * 4 + 3] = one;
-      }
-    }
-    else if (components == 2) {
+    if (components == 2) {
       /* Grayscale + alpha to RGBA. */
       for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) {
         pixels[i * 4 + 3] = pixels[i * 2 + 1];
@@ -643,7 +521,7 @@ bool ImageManager::file_load_image(Image *img,
     }
 
     /* Disable alpha if requested by the user. */
-    if (img->key.alpha_type == IMAGE_ALPHA_IGNORE) {
+    if (img->params.alpha_type == IMAGE_ALPHA_IGNORE) {
       for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) {
         pixels[i * 4 + 3] = one;
       }
@@ -690,7 +568,8 @@ bool ImageManager::file_load_image(Image *img,
     while (max_size * scale_factor > texture_limit) {
       scale_factor *= 0.5f;
     }
-    VLOG(1) << "Scaling image " << img->key.filename << " by a factor of " << scale_factor << ".";
+    VLOG(1) << "Scaling image " << img->loader->name() << " by a factor of " << scale_factor
+            << ".";
     vector<StorageType> scaled_pixels;
     size_t scaled_width, scaled_height, scaled_depth;
     util_image_resize_pixels(pixels_storage,
@@ -721,25 +600,23 @@ static void image_set_device_memory(ImageManager::Image *img, device_memory *mem
 {
   img->mem = mem;
   mem->image_data_type = img->metadata.type;
-  mem->interpolation = img->key.interpolation;
-  mem->extension = img->key.extension;
+  mem->interpolation = img->params.interpolation;
+  mem->extension = img->params.extension;
 }
 
 void ImageManager::device_load_image(Device *device, Scene *scene, int slot, Progress *progress)
 {
-  if (progress->get_cancel())
+  if (progress->get_cancel()) {
     return;
+  }
 
   Image *img = images[slot];
 
-  if (osl_texture_system && !img->key.builtin_data)
-    return;
-
-  string filename = path_filename(images[slot]->key.filename);
-  progress->set_status("Updating Images", "Loading " + filename);
+  progress->set_status("Updating Images", "Loading " + img->loader->name());
 
   const int texture_limit = scene->params.texture_limit;
 
+  load_image_metadata(img);
   ImageDataType type = img->metadata.type;
 
   /* Slot assignment */
@@ -901,29 +778,36 @@ void ImageManager::device_load_image(Device *device, Scene *scene, int slot, Pro
     thread_scoped_lock device_lock(device_mutex);
     tex_img->copy_to_device();
   }
+
+  /* Cleanup memory in image loader. */
+  img->loader->cleanup();
   img->need_load = false;
 }
 
 void ImageManager::device_free_image(Device *, int slot)
 {
   Image *img = images[slot];
+  if (img == NULL) {
+    return;
+  }
 
-  if (img) {
-    if (osl_texture_system && !img->key.builtin_data) {
+  if (osl_texture_system) {
 #ifdef WITH_OSL
-      ustring filename(images[slot]->key.filename);
-      ((OSL::TextureSystem *)osl_texture_system)->invalidate(filename);
-#endif
-    }
-
-    if (img->mem) {
-      thread_scoped_lock device_lock(device_mutex);
-      delete img->mem;
+    ustring filepath = img->loader->osl_filepath();
+    if (filepath) {
+      ((OSL::TextureSystem *)osl_texture_system)->invalidate(filepath);
     }
+#endif
+  }
 
-    delete img;
-    images[slot] = NULL;
+  if (img->mem) {
+    thread_scoped_lock device_lock(device_mutex);
+    delete img->mem;
   }
+
+  delete img->loader;
+  delete img;
+  images[slot] = NULL;
 }
 
 void ImageManager::device_update(Device *device, Scene *scene, Progress &progress)
@@ -934,20 +818,13 @@ void ImageManager::device_update(Device *device, Scene *scene, Progress &progres
 
   TaskPool pool;
   for (size_t slot = 0; slot < images.size(); slot++) {
-    if (!images[slot])
-      continue;
-
-    if (images[slot]->users == 0) {
+    Image *img = images[slot];
+    if (img && img->users == 0) {
       device_free_image(device, slot);
     }
-    else if (images[slot]->need_load) {
-      if (osl_texture_system && !images[slot]->key.builtin_data) {
-        images[slot]->need_load = false;
-      }
-      else {
-        pool.push(
-            function_bind(&ImageManager::device_load_image, this, device, scene, slot, &progress));
-      }
+    else if (img && img->need_load) {
+      pool.push(
+          function_bind(&ImageManager::device_load_image, this, device, scene, slot, &progress));
     }
   }
 
@@ -958,19 +835,14 @@ void ImageManager::device_update(Device *device, Scene *scene, Progress &progres
 
 void ImageManager::device_update_slot(Device *device, Scene *scene, int slot, Progress *progress)
 {
-  Image *image = images[slot];
-  assert(image != NULL);
+  Image *img = images[slot];
+  assert(img != NULL);
 
-  if (image->users == 0) {
+  if (img->users == 0) {
     device_free_image(device, slot);
   }
-  else if (image->need_load) {
-    if (osl_texture_system && !image->key.builtin_data) {
-      images[slot]->need_load = false;
-    }
-    else {
-      device_load_image(device, scene, slot, progress);
-    }
+  else if (img->need_load) {
+    device_load_image(device, scene, slot, progress);
   }
 }
 
@@ -984,14 +856,10 @@ void ImageManager::device_load_builtin(Device *device, Scene *scene, Progress &p
 
   TaskPool pool;
   for (size_t slot = 0; slot < images.size(); slot++) {
-    if (!images[slot])
-      continue;
-
-    if (images[slot]->need_load) {
-      if (images[slot]->key.builtin_data) {
-        pool.push(
-            function_bind(&ImageManager::device_load_image, this, device, scene, slot, &progress));
-      }
+    Image *img = images[slot];
+    if (img && img->need_load && img->builtin) {
+      pool.push(
+          function_bind(&ImageManager::device_load_image, this, device, scene, slot, &progress));
     }
   }
 
@@ -1001,8 +869,10 @@ void ImageManager::device_load_builtin(Device *device, Scene *scene, Progress &p
 void ImageManager::device_free_builtin(Device *device)
 {
   for (size_t slot = 0; slot < images.size(); slot++) {
-    if (images[slot] && images[slot]->key.builtin_data)
+    Image *img = images[slot];
+    if (img && img->builtin) {
       device_free_image(device, slot);
+    }
   }
 }
 
@@ -1018,7 +888,7 @@ void ImageManager::collect_statistics(RenderStats *stats)
 {
   foreach (const Image *image, images) {
     stats->image.textures.add_entry(
-        NamedSizeEntry(path_filename(image->key.filename), image->mem->memory_size()));
+        NamedSizeEntry(image->loader->name(), image->mem->memory_size()));
   }
 }
 
index b23bb7bed6304374f3059ca3c92ab9ec15708e30..35e6d4dcb083126594e0d15190e3f48a706ceff5 100644 (file)
@@ -22,7 +22,6 @@
 
 #include "render/colorspace.h"
 
-#include "util/util_image.h"
 #include "util/util_string.h"
 #include "util/util_thread.h"
 #include "util/util_unique_ptr.h"
@@ -40,78 +39,89 @@ class RenderStats;
 class Scene;
 class ColorSpaceProcessor;
 
-/* Image MetaData
- *
- * Information about the image that is available before the image pxeisl are loaded. */
-class ImageMetaData {
+/* Image Parameters */
+class ImageParams {
  public:
-  /* Must be set by image file or builtin callback. */
-  bool is_float, is_half;
-  int channels;
-  size_t width, height, depth;
-  bool builtin_free_cache;
-
-  /* Automatically set. */
-  ImageDataType type;
-  ustring colorspace;
-  bool compress_as_srgb;
-
-  ImageMetaData()
-      : is_float(false),
-        is_half(false),
-        channels(0),
-        width(0),
-        height(0),
-        depth(0),
-        builtin_free_cache(false),
-        type(IMAGE_DATA_NUM_TYPES),
-        colorspace(u_colorspace_raw),
-        compress_as_srgb(false)
-  {
-  }
-
-  bool operator==(const ImageMetaData &other) const
-  {
-    return is_float == other.is_float && is_half == other.is_half && channels == other.channels &&
-           width == other.width && height == other.height && depth == other.depth &&
-           type == other.type && colorspace == other.colorspace &&
-           compress_as_srgb == other.compress_as_srgb;
-  }
-};
-
-/* Image Key
- *
- * Image description that uniquely identifies and images. When adding images
- * with the same key, they will be internally deduplicated. */
-class ImageKey {
- public:
-  string filename;
-  void *builtin_data;
   bool animated;
   InterpolationType interpolation;
   ExtensionType extension;
   ImageAlphaType alpha_type;
   ustring colorspace;
+  float frame;
 
-  ImageKey()
-      : builtin_data(NULL),
-        animated(false),
+  ImageParams()
+      : animated(false),
         interpolation(INTERPOLATION_LINEAR),
         extension(EXTENSION_CLIP),
         alpha_type(IMAGE_ALPHA_AUTO),
-        colorspace(u_colorspace_raw)
+        colorspace(u_colorspace_raw),
+        frame(0.0f)
   {
   }
 
-  bool operator==(const ImageKey &other) const
+  bool operator==(const ImageParams &other) const
   {
-    return (filename == other.filename && builtin_data == other.builtin_data &&
-            animated == other.animated && interpolation == other.interpolation &&
+    return (animated == other.animated && interpolation == other.interpolation &&
             extension == other.extension && alpha_type == other.alpha_type &&
-            colorspace == other.colorspace);
+            colorspace == other.colorspace && frame == other.frame);
   }
 };
 
+/* Image MetaData
+ *
+ * Information about the image that is available before the image pxeisl are loaded. */
+class ImageMetaData {
+ public:
+  /* Set by ImageLoader.load_metadata(). */
+  int channels;
+  size_t width, height, depth;
+  ImageDataType type;
+
+  /* Optional color space, defaults to raw. */
+  ustring colorspace;
+  const char *colorspace_file_format;
+
+  /* Automatically set. */
+  bool compress_as_srgb;
+
+  ImageMetaData();
+  bool operator==(const ImageMetaData &other) const;
+  bool is_float() const;
+  void detect_colorspace();
+};
+
+/* Image loader base class, that can be subclassed to load image data
+ * from custom sources (file, memory, procedurally generated, etc). */
+class ImageLoader {
+ public:
+  ImageLoader();
+  virtual ~ImageLoader(){};
+
+  /* Load metadata without actual image yet, should be fast. */
+  virtual bool load_metadata(ImageMetaData &metadata) = 0;
+
+  /* Load actual image contents. */
+  virtual bool load_pixels(const ImageMetaData &metadata,
+                           void *pixels,
+                           const size_t pixels_size,
+                           const bool associate_alpha) = 0;
+
+  /* Name for logs and stats. */
+  virtual string name() const = 0;
+
+  /* Optional for OSL texture cache. */
+  virtual ustring osl_filepath() const;
+
+  /* Free any memory used for loading metadata and pixels. */
+  virtual void cleanup(){};
+
+  /* Compare avoid loading the same image multiple times. */
+  virtual bool equals(const ImageLoader &other) const = 0;
+  static bool equals(const ImageLoader *a, const ImageLoader *b);
+
+  /* Work around for no RTTI. */
+};
+
 /* Image Handle
  *
  * Access handle for image in the image manager. Multiple shader nodes may
@@ -123,17 +133,19 @@ class ImageHandle {
   ImageHandle &operator=(const ImageHandle &other);
   ~ImageHandle();
 
+  bool operator==(const ImageHandle &other) const;
+
   void clear();
 
   bool empty();
   int num_tiles();
 
   ImageMetaData metadata();
-  int svm_slot(const int tile_index = 0);
-  device_memory *image_memory(const int tile_index = 0);
+  int svm_slot(const int tile_index = 0) const;
+  device_memory *image_memory(const int tile_index = 0) const;
 
  protected:
-  vector<int> slots;
+  vector<int> tile_slots;
   ImageManager *manager;
 
   friend class ImageManager;
@@ -148,8 +160,11 @@ class ImageManager {
   explicit ImageManager(const DeviceInfo &info);
   ~ImageManager();
 
-  ImageHandle add_image(const ImageKey &key, float frame);
-  ImageHandle add_image(const ImageKey &key, float frame, const vector<int> &tiles);
+  ImageHandle add_image(const string &filename, const ImageParams &params);
+  ImageHandle add_image(const string &filename,
+                        const ImageParams &params,
+                        const vector<int> &tiles);
+  ImageHandle add_image(ImageLoader *loader, const ImageParams &params);
 
   void device_update(Device *device, Scene *scene, Progress &progress);
   void device_update_slot(Device *device, Scene *scene, int slot, Progress *progress);
@@ -165,40 +180,21 @@ class ImageManager {
 
   bool need_update;
 
-  /* NOTE: Here pixels_size is a size of storage, which equals to
-   *       width * height * depth.
-   *       Use this to avoid some nasty memory corruptions.
-   */
-  function<void(const string &filename, void *data, ImageMetaData &metadata)>
-      builtin_image_info_cb;
-  function<bool(const string &filename,
-                void *data,
-                int tile,
-                unsigned char *pixels,
-                const size_t pixels_size,
-                const bool associate_alpha,
-                const bool free_cache)>
-      builtin_image_pixels_cb;
-  function<bool(const string &filename,
-                void *data,
-                int tile,
-                float *pixels,
-                const size_t pixels_size,
-                const bool associate_alpha,
-                const bool free_cache)>
-      builtin_image_float_pixels_cb;
-
   struct Image {
-    ImageKey key;
+    ImageParams params;
     ImageMetaData metadata;
+    ImageLoader *loader;
 
     float frame;
+    bool need_metadata;
     bool need_load;
+    bool builtin;
 
     string mem_name;
     device_memory *mem;
 
     int users;
+    thread_mutex mutex;
   };
 
  private:
@@ -210,19 +206,15 @@ class ImageManager {
   vector<Image *> images;
   void *osl_texture_system;
 
-  int add_image_slot(const ImageKey &key, float frame);
+  int add_image_slot(ImageLoader *loader, const ImageParams &params, const bool builtin);
   void add_image_user(int slot);
   void remove_image_user(int slot);
 
-  bool load_image_metadata(const ImageKey &key, ImageMetaData &metadata);
-
-  bool file_load_image_generic(Image *img, unique_ptr<ImageInput> *in);
+  void load_image_metadata(Image *img);
 
   template<TypeDesc::BASETYPE FileFormat, typename StorageType, typename DeviceType>
   bool file_load_image(Image *img, int texture_limit, device_vector<DeviceType> &tex_img);
 
-  void metadata_detect_colorspace(ImageMetaData &metadata, const char *file_format);
-
   void device_load_image(Device *device, Scene *scene, int slot, Progress *progress);
   void device_free_image(Device *device, int slot);
 
diff --git a/intern/cycles/render/image_oiio.cpp b/intern/cycles/render/image_oiio.cpp
new file mode 100644 (file)
index 0000000..c4f95c6
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2011-2020 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "render/image_oiio.h"
+
+#include "util/util_image.h"
+#include "util/util_logging.h"
+#include "util/util_path.h"
+
+CCL_NAMESPACE_BEGIN
+
+OIIOImageLoader::OIIOImageLoader(const string &filepath) : filepath(filepath)
+{
+}
+
+OIIOImageLoader::~OIIOImageLoader()
+{
+}
+
+bool OIIOImageLoader::load_metadata(ImageMetaData &metadata)
+{
+  /* Perform preliminary checks, with meaningful logging. */
+  if (!path_exists(filepath.string())) {
+    VLOG(1) << "File '" << filepath.string() << "' does not exist.";
+    return false;
+  }
+  if (path_is_directory(filepath.string())) {
+    VLOG(1) << "File '" << filepath.string() << "' is a directory, can't use as image.";
+    return false;
+  }
+
+  unique_ptr<ImageInput> in(ImageInput::create(filepath.string()));
+
+  if (!in) {
+    return false;
+  }
+
+  ImageSpec spec;
+  if (!in->open(filepath.string(), spec)) {
+    return false;
+  }
+
+  metadata.width = spec.width;
+  metadata.height = spec.height;
+  metadata.depth = spec.depth;
+  metadata.compress_as_srgb = false;
+
+  /* Check the main format, and channel formats. */
+  size_t channel_size = spec.format.basesize();
+
+  bool is_float = false;
+  bool is_half = false;
+
+  if (spec.format.is_floating_point()) {
+    is_float = true;
+  }
+
+  for (size_t channel = 0; channel < spec.channelformats.size(); channel++) {
+    channel_size = max(channel_size, spec.channelformats[channel].basesize());
+    if (spec.channelformats[channel].is_floating_point()) {
+      is_float = true;
+    }
+  }
+
+  /* check if it's half float */
+  if (spec.format == TypeDesc::HALF) {
+    is_half = true;
+  }
+
+  /* set type and channels */
+  metadata.channels = spec.nchannels;
+
+  if (is_half) {
+    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_HALF4 : IMAGE_DATA_TYPE_HALF;
+  }
+  else if (is_float) {
+    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_FLOAT4 : IMAGE_DATA_TYPE_FLOAT;
+  }
+  else if (spec.format == TypeDesc::USHORT) {
+    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_USHORT4 : IMAGE_DATA_TYPE_USHORT;
+  }
+  else {
+    metadata.type = (metadata.channels > 1) ? IMAGE_DATA_TYPE_BYTE4 : IMAGE_DATA_TYPE_BYTE;
+  }
+
+  metadata.colorspace_file_format = in->format_name();
+
+  in->close();
+
+  return true;
+}
+
+template<TypeDesc::BASETYPE FileFormat, typename StorageType>
+static void oiio_load_pixels(const ImageMetaData &metadata,
+                             const unique_ptr<ImageInput> &in,
+                             StorageType *pixels)
+{
+  const int width = metadata.width;
+  const int height = metadata.height;
+  const int depth = metadata.depth;
+  const int components = metadata.channels;
+
+  /* Read pixels through OpenImageIO. */
+  StorageType *readpixels = pixels;
+  vector<StorageType> tmppixels;
+  if (components > 4) {
+    tmppixels.resize(((size_t)width) * height * components);
+    readpixels = &tmppixels[0];
+  }
+
+  if (depth <= 1) {
+    size_t scanlinesize = ((size_t)width) * components * sizeof(StorageType);
+    in->read_image(FileFormat,
+                   (uchar *)readpixels + (height - 1) * scanlinesize,
+                   AutoStride,
+                   -scanlinesize,
+                   AutoStride);
+  }
+  else {
+    in->read_image(FileFormat, (uchar *)readpixels);
+  }
+
+  if (components > 4) {
+    size_t dimensions = ((size_t)width) * height;
+    for (size_t i = dimensions - 1, pixel = 0; pixel < dimensions; pixel++, i--) {
+      pixels[i * 4 + 3] = tmppixels[i * components + 3];
+      pixels[i * 4 + 2] = tmppixels[i * components + 2];
+      pixels[i * 4 + 1] = tmppixels[i * components + 1];
+      pixels[i * 4 + 0] = tmppixels[i * components + 0];
+    }
+    tmppixels.clear();
+  }
+
+  /* CMYK to RGBA. */
+  const bool cmyk = strcmp(in->format_name(), "jpeg") == 0 && components == 4;
+  if (cmyk) {
+    const StorageType one = util_image_cast_from_float<StorageType>(1.0f);
+
+    const size_t num_pixels = ((size_t)width) * height * depth;
+    for (size_t i = num_pixels - 1, pixel = 0; pixel < num_pixels; pixel++, i--) {
+      float c = util_image_cast_to_float(pixels[i * 4 + 0]);
+      float m = util_image_cast_to_float(pixels[i * 4 + 1]);
+      float y = util_image_cast_to_float(pixels[i * 4 + 2]);
+      float k = util_image_cast_to_float(pixels[i * 4 + 3]);
+      pixels[i * 4 + 0] = util_image_cast_from_float<StorageType>((1.0f - c) * (1.0f - k));
+      pixels[i * 4 + 1] = util_image_cast_from_float<StorageType>((1.0f - m) * (1.0f - k));
+      pixels[i * 4 + 2] = util_image_cast_from_float<StorageType>((1.0f - y) * (1.0f - k));
+      pixels[i * 4 + 3] = one;
+    }
+  }
+}
+
+bool OIIOImageLoader::load_pixels(const ImageMetaData &metadata,
+                                  void *pixels,
+                                  const size_t,
+                                  const bool associate_alpha)
+{
+  unique_ptr<ImageInput> in = NULL;
+
+  /* NOTE: Error logging is done in meta data acquisition. */
+  if (!path_exists(filepath.string()) || path_is_directory(filepath.string())) {
+    return false;
+  }
+
+  /* load image from file through OIIO */
+  in = unique_ptr<ImageInput>(ImageInput::create(filepath.string()));
+  if (!in) {
+    return false;
+  }
+
+  ImageSpec spec = ImageSpec();
+  ImageSpec config = ImageSpec();
+
+  if (!associate_alpha) {
+    config.attribute("oiio:UnassociatedAlpha", 1);
+  }
+
+  if (!in->open(filepath.string(), spec, config)) {
+    return false;
+  }
+
+  switch (metadata.type) {
+    case IMAGE_DATA_TYPE_BYTE:
+    case IMAGE_DATA_TYPE_BYTE4:
+      oiio_load_pixels<TypeDesc::UINT8, uchar>(metadata, in, (uchar *)pixels);
+      break;
+    case IMAGE_DATA_TYPE_USHORT:
+    case IMAGE_DATA_TYPE_USHORT4:
+      oiio_load_pixels<TypeDesc::USHORT, uint16_t>(metadata, in, (uint16_t *)pixels);
+      break;
+    case IMAGE_DATA_TYPE_HALF:
+    case IMAGE_DATA_TYPE_HALF4:
+      oiio_load_pixels<TypeDesc::HALF, half>(metadata, in, (half *)pixels);
+      break;
+    case IMAGE_DATA_TYPE_FLOAT:
+    case IMAGE_DATA_TYPE_FLOAT4:
+      oiio_load_pixels<TypeDesc::FLOAT, float>(metadata, in, (float *)pixels);
+      break;
+    case IMAGE_DATA_NUM_TYPES:
+      break;
+  }
+
+  in->close();
+  return true;
+}
+
+string OIIOImageLoader::name() const
+{
+  return path_filename(filepath.string());
+}
+
+ustring OIIOImageLoader::osl_filepath() const
+{
+  return filepath;
+}
+
+bool OIIOImageLoader::equals(const ImageLoader &other) const
+{
+  const OIIOImageLoader &other_loader = (const OIIOImageLoader &)other;
+  return filepath == other_loader.filepath;
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/render/image_oiio.h b/intern/cycles/render/image_oiio.h
new file mode 100644 (file)
index 0000000..a234b96
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011-2020 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __IMAGE_OIIO__
+#define __IMAGE_OIIO__
+
+#include "render/image.h"
+
+CCL_NAMESPACE_BEGIN
+
+class OIIOImageLoader : public ImageLoader {
+ public:
+  OIIOImageLoader(const string &filepath);
+  ~OIIOImageLoader();
+
+  bool load_metadata(ImageMetaData &metadata) override;
+
+  bool load_pixels(const ImageMetaData &metadata,
+                   void *pixels,
+                   const size_t pixels_size,
+                   const bool associate_alpha) override;
+
+  string name() const override;
+
+  ustring osl_filepath() const override;
+
+  bool equals(const ImageLoader &other) const override;
+
+ protected:
+  ustring filepath;
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __IMAGE_OIIO__ */
index bbdb2572392c0cdab3972fbc6f744b42fd39538f..26b816b65e94bf309fd3f8c41fe85b1d2d5764e5 100644 (file)
@@ -256,7 +256,6 @@ NODE_DEFINE(ImageTextureNode)
 ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(node_type)
 {
   colorspace = u_colorspace_raw;
-  builtin_data = NULL;
   animated = false;
   tiles.push_back(1001);
 }
@@ -268,17 +267,15 @@ ShaderNode *ImageTextureNode::clone() const
   return node;
 }
 
-ImageKey ImageTextureNode::image_key() const
+ImageParams ImageTextureNode::image_params() const
 {
-  ImageKey key;
-  key.filename = filename.string();
-  key.builtin_data = builtin_data;
-  key.animated = animated;
-  key.interpolation = interpolation;
-  key.extension = extension;
-  key.alpha_type = alpha_type;
-  key.colorspace = colorspace;
-  return key;
+  ImageParams params;
+  params.animated = animated;
+  params.interpolation = interpolation;
+  params.extension = extension;
+  params.alpha_type = alpha_type;
+  params.colorspace = colorspace;
+  return params;
 }
 
 void ImageTextureNode::cull_tiles(Scene *scene, ShaderGraph *graph)
@@ -365,9 +362,8 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
 
   if (handle.empty()) {
     cull_tiles(compiler.scene, compiler.current_graph);
-
     ImageManager *image_manager = compiler.scene->image_manager;
-    handle = image_manager->add_image(image_key(), 0, tiles);
+    handle = image_manager->add_image(filename.string(), image_params(), tiles);
   }
 
   /* All tiles have the same metadata. */
@@ -448,11 +444,11 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
 
   if (handle.empty()) {
     ImageManager *image_manager = compiler.scene->image_manager;
-    handle = image_manager->add_image(image_key(), 0);
+    handle = image_manager->add_image(filename.string(), image_params());
   }
 
   const ImageMetaData metadata = handle.metadata();
-  const bool is_float = metadata.is_float;
+  const bool is_float = metadata.is_float();
   const bool compress_as_srgb = metadata.compress_as_srgb;
   const ustring known_colorspace = metadata.colorspace;
 
@@ -524,7 +520,6 @@ NODE_DEFINE(EnvironmentTextureNode)
 EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(node_type)
 {
   colorspace = u_colorspace_raw;
-  builtin_data = NULL;
   animated = false;
 }
 
@@ -535,17 +530,15 @@ ShaderNode *EnvironmentTextureNode::clone() const
   return node;
 }
 
-ImageKey EnvironmentTextureNode::image_key() const
+ImageParams EnvironmentTextureNode::image_params() const
 {
-  ImageKey key;
-  key.filename = filename.string();
-  key.builtin_data = builtin_data;
-  key.animated = animated;
-  key.interpolation = interpolation;
-  key.extension = EXTENSION_REPEAT;
-  key.alpha_type = alpha_type;
-  key.colorspace = colorspace;
-  return key;
+  ImageParams params;
+  params.animated = animated;
+  params.interpolation = interpolation;
+  params.extension = EXTENSION_REPEAT;
+  params.alpha_type = alpha_type;
+  params.colorspace = colorspace;
+  return params;
 }
 
 void EnvironmentTextureNode::attributes(Shader *shader, AttributeRequestSet *attributes)
@@ -569,7 +562,7 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
 
   if (handle.empty()) {
     ImageManager *image_manager = compiler.scene->image_manager;
-    handle = image_manager->add_image(image_key(), 0);
+    handle = image_manager->add_image(filename.string(), image_params());
   }
 
   const ImageMetaData metadata = handle.metadata();
@@ -596,18 +589,15 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
 
 void EnvironmentTextureNode::compile(OSLCompiler &compiler)
 {
-  tex_mapping.compile(compiler);
-
-  /* See comments in ImageTextureNode::compile about support
-   * of builtin images.
-   */
   if (handle.empty()) {
     ImageManager *image_manager = compiler.scene->image_manager;
-    handle = image_manager->add_image(image_key(), 0);
+    handle = image_manager->add_image(filename.string(), image_params());
   }
 
+  tex_mapping.compile(compiler);
+
   const ImageMetaData metadata = handle.metadata();
-  const bool is_float = metadata.is_float;
+  const bool is_float = metadata.is_float();
   const bool compress_as_srgb = metadata.compress_as_srgb;
   const ustring known_colorspace = metadata.colorspace;
 
@@ -1655,7 +1645,6 @@ NODE_DEFINE(PointDensityTextureNode)
 
 PointDensityTextureNode::PointDensityTextureNode() : ShaderNode(node_type)
 {
-  builtin_data = NULL;
 }
 
 PointDensityTextureNode::~PointDensityTextureNode()
@@ -1668,7 +1657,7 @@ ShaderNode *PointDensityTextureNode::clone() const
    * add_image again, to work around access of freed data on the Blender
    * side. A better solution should be found to avoid this. */
   PointDensityTextureNode *node = new PointDensityTextureNode(*this);
-  node->handle = handle;
+  node->handle = handle; /* TODO: not needed? */
   return node;
 }
 
@@ -1680,18 +1669,11 @@ void PointDensityTextureNode::attributes(Shader *shader, AttributeRequestSet *at
   ShaderNode::attributes(shader, attributes);
 }
 
-void PointDensityTextureNode::add_image(ImageManager *image_manager)
+ImageParams PointDensityTextureNode::image_params() const
 {
-  if (!handle.empty()) {
-    return;
-  }
-
-  ImageKey key;
-  key.filename = filename.string();
-  key.builtin_data = builtin_data;
-  key.interpolation = interpolation;
-
-  handle = image_manager->add_image(key, 0);
+  ImageParams params;
+  params.interpolation = interpolation;
+  return params;
 }
 
 void PointDensityTextureNode::compile(SVMCompiler &compiler)
@@ -1704,7 +1686,10 @@ void PointDensityTextureNode::compile(SVMCompiler &compiler)
   const bool use_color = !color_out->links.empty();
 
   if (use_density || use_color) {
-    add_image(compiler.scene->image_manager);
+    if (handle.empty()) {
+      ImageManager *image_manager = compiler.scene->image_manager;
+      handle = image_manager->add_image(filename.string(), image_params());
+    }
 
     const int slot = handle.svm_slot();
     if (slot != -1) {
@@ -1744,7 +1729,10 @@ void PointDensityTextureNode::compile(OSLCompiler &compiler)
   const bool use_color = !color_out->links.empty();
 
   if (use_density || use_color) {
-    add_image(compiler.scene->image_manager);
+    if (handle.empty()) {
+      ImageManager *image_manager = compiler.scene->image_manager;
+      handle = image_manager->add_image(filename.string(), image_params());
+    }
 
     compiler.parameter_texture("filename", handle.svm_slot());
     if (space == NODE_TEX_VOXEL_SPACE_WORLD) {
index 99e676fc8cb43b6e9cb20df141902b620b3f38d0..1f52a2a49ab66d964fc2e3b5f8e155b0f2efd529 100644 (file)
@@ -80,6 +80,12 @@ class ImageSlotTextureNode : public TextureNode {
     special_type = SHADER_SPECIAL_TYPE_IMAGE_SLOT;
   }
 
+  virtual bool equals(const ShaderNode &other)
+  {
+    const ImageSlotTextureNode &other_node = (const ImageSlotTextureNode &)other;
+    return TextureNode::equals(other) && handle == other_node.handle;
+  }
+
   ImageHandle handle;
 };
 
@@ -95,16 +101,14 @@ class ImageTextureNode : public ImageSlotTextureNode {
 
   virtual bool equals(const ShaderNode &other)
   {
-    const ImageTextureNode &image_node = (const ImageTextureNode &)other;
-    return ImageSlotTextureNode::equals(other) && builtin_data == image_node.builtin_data &&
-           animated == image_node.animated;
+    const ImageTextureNode &other_node = (const ImageTextureNode &)other;
+    return ImageSlotTextureNode::equals(other) && animated == other_node.animated;
   }
 
-  ImageKey image_key() const;
+  ImageParams image_params() const;
 
   /* Parameters. */
   ustring filename;
-  void *builtin_data;
   ustring colorspace;
   ImageAlphaType alpha_type;
   NodeImageProjection projection;
@@ -135,16 +139,14 @@ class EnvironmentTextureNode : public ImageSlotTextureNode {
 
   virtual bool equals(const ShaderNode &other)
   {
-    const EnvironmentTextureNode &env_node = (const EnvironmentTextureNode &)other;
-    return ImageSlotTextureNode::equals(other) && builtin_data == env_node.builtin_data &&
-           animated == env_node.animated;
+    const EnvironmentTextureNode &other_node = (const EnvironmentTextureNode &)other;
+    return ImageSlotTextureNode::equals(other) && animated == other_node.animated;
   }
 
-  ImageKey image_key() const;
+  ImageParams image_params() const;
 
   /* Parameters. */
   ustring filename;
-  void *builtin_data;
   ustring colorspace;
   ImageAlphaType alpha_type;
   NodeEnvironmentProjection projection;
@@ -357,23 +359,22 @@ class PointDensityTextureNode : public ShaderNode {
     return true;
   }
 
-  void add_image(ImageManager *image_manager);
-
   /* Parameters. */
   ustring filename;
   NodeTexVoxelSpace space;
   InterpolationType interpolation;
   Transform tfm;
   float3 vector;
-  void *builtin_data;
 
   /* Runtime. */
   ImageHandle handle;
 
+  ImageParams image_params() const;
+
   virtual bool equals(const ShaderNode &other)
   {
-    const PointDensityTextureNode &point_dendity_node = (const PointDensityTextureNode &)other;
-    return ShaderNode::equals(other) && builtin_data == point_dendity_node.builtin_data;
+    const PointDensityTextureNode &other_node = (const PointDensityTextureNode &)other;
+    return ShaderNode::equals(other) && handle == other_node.handle;
   }
 };
 
index b1143acfbee97d792f55d10de482351072aa1e4f..af3069cf95eed970dbe76076b0dfe621676b8cf1 100644 (file)
@@ -4653,8 +4653,8 @@ static const char *cpp_classes =
     "    operator void*() { return ptr.data; }\n"
     "    operator bool() { return ptr.data != NULL; }\n"
     "\n"
-    "    bool operator==(const Pointer &other) { return ptr.data == other.ptr.data; }\n"
-    "    bool operator!=(const Pointer &other) { return ptr.data != other.ptr.data; }\n"
+    "    bool operator==(const Pointer &other) const { return ptr.data == other.ptr.data; }\n"
+    "    bool operator!=(const Pointer &other) const { return ptr.data != other.ptr.data; }\n"
     "\n"
     "    PointerRNA ptr;\n"
     "};\n"