Add support for tiled images and the UDIM naming scheme
authorLukas Stockner <lukas.stockner@freenet.de>
Thu, 12 Dec 2019 15:06:08 +0000 (16:06 +0100)
committerLukas Stockner <lukas.stockner@freenet.de>
Thu, 12 Dec 2019 17:40:37 +0000 (18:40 +0100)
This patch contains the work that I did during my week at the Code Quest - adding support for tiled images to Blender.

With this patch, images now contain a list of tiles. By default, this just contains one tile, but if the source type is set to Tiled, the user can add additional tiles. When acquiring an ImBuf, the tile to be loaded is specified in the ImageUser.
Therefore, code that is not yet aware of tiles will just access the default tile as usual.

The filenames of the additional tiles are derived from the original filename according to the UDIM naming scheme - the filename contains an index that is calculated as (1001 + 10*<y coordinate of the tile> + <x coordinate of the tile>), where the x coordinate never goes above 9.
Internally, the various tiles are stored in a cache just like sequences. When acquired for the first time, the code will try to load the corresponding file from disk. Alternatively, a new operator can be used to initialize the tile similar to the New Image operator.

The following features are supported so far:
- Automatic detection and loading of all tiles when opening the first tile (1001)
- Saving all tiles
- Adding and removing tiles
- Filling tiles with generated images
- Drawing all tiles in the Image Editor
- Viewing a tiled grid even if no image is selected
- Rendering tiled images in Eevee
- Rendering tiled images in Cycles (in SVM mode)
- Automatically skipping loading of unused tiles in Cycles
- 2D texture painting (also across tiles)
- 3D texture painting (also across tiles, only limitation: individual faces can not cross tile borders)
- Assigning custom labels to individual tiles (drawn in the Image Editor instead of the ID)
- Different resolutions between tiles

There still are some missing features that will be added later (see T72390):
- Workbench engine support
- Packing/Unpacking support
- Baking support
- Cycles OSL support
- many other Blender features that rely on images

Thanks to Brecht for the review and to all who tested the intermediate versions!

Differential Revision: https://developer.blender.org/D3509

68 files changed:
intern/cycles/blender/blender_session.cpp
intern/cycles/blender/blender_session.h
intern/cycles/blender/blender_shader.cpp
intern/cycles/blender/blender_sync.cpp
intern/cycles/blender/blender_util.h
intern/cycles/kernel/svm/svm.h
intern/cycles/kernel/svm/svm_image.h
intern/cycles/render/image.cpp
intern/cycles/render/image.h
intern/cycles/render/light.cpp
intern/cycles/render/mesh.cpp
intern/cycles/render/mesh.h
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
intern/cycles/render/osl.cpp
intern/cycles/render/osl.h
intern/cycles/render/scene.h
intern/cycles/render/svm.cpp
intern/cycles/render/svm.h
intern/cycles/test/render_graph_finalize_test.cpp
release/scripts/startup/bl_ui/space_image.py
source/blender/blenfont/BLF_api.h
source/blender/blenfont/intern/blf.c
source/blender/blenkernel/BKE_image.h
source/blender/blenkernel/intern/bpath.c
source/blender/blenkernel/intern/image.c
source/blender/blenkernel/intern/image_save.c
source/blender/blenkernel/intern/packedFile.c
source/blender/blenlib/BLI_math_vector.h
source/blender/blenlib/intern/math_vector_inline.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/versioning_280.c
source/blender/blenloader/intern/writefile.c
source/blender/compositor/operations/COM_ViewerOperation.cpp
source/blender/draw/intern/draw_manager_data.c
source/blender/editors/include/ED_image.h
source/blender/editors/include/ED_paint.h
source/blender/editors/include/ED_screen.h
source/blender/editors/interface/interface_ops.c
source/blender/editors/object/object_bake_api.c
source/blender/editors/render/render_preview.c
source/blender/editors/screen/area.c
source/blender/editors/sculpt_paint/paint_image.c
source/blender/editors/sculpt_paint/paint_image_2d.c
source/blender/editors/sculpt_paint/paint_image_proj.c
source/blender/editors/sculpt_paint/paint_intern.h
source/blender/editors/space_clip/clip_draw.c
source/blender/editors/space_image/image_draw.c
source/blender/editors/space_image/image_edit.c
source/blender/editors/space_image/image_intern.h
source/blender/editors/space_image/image_ops.c
source/blender/editors/space_image/image_undo.c
source/blender/editors/space_image/space_image.c
source/blender/gpu/GPU_material.h
source/blender/gpu/intern/gpu_codegen.c
source/blender/gpu/intern/gpu_codegen.h
source/blender/gpu/intern/gpu_draw.c
source/blender/gpu/shaders/material/gpu_shader_material_tex_image.glsl
source/blender/imbuf/IMB_moviecache.h
source/blender/imbuf/intern/moviecache.c
source/blender/makesdna/DNA_image_types.h
source/blender/makesdna/DNA_space_types.h
source/blender/makesrna/intern/rna_image.c
source/blender/makesrna/intern/rna_image_api.c
source/blender/makesrna/intern/rna_main_api.c
source/blender/makesrna/intern/rna_space.c
source/blender/nodes/shader/nodes/node_shader_tex_environment.c
source/blender/nodes/shader/nodes/node_shader_tex_image.c

index 53f2fdb91b94001b3ba83017f1b4b6958504bb6a..78fb49db6c8d974358b2799cddd57c0a0f523ac0 100644 (file)
@@ -142,9 +142,9 @@ void BlenderSession::create_session()
   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);
+      &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);
+      &BlenderSession::builtin_image_float_pixels, this, _1, _2, _3, _4, _5, _6, _7);
 
   session->scene = scene;
 
@@ -1210,6 +1210,7 @@ void BlenderSession::builtin_image_info(const string &builtin_name,
 
 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,
@@ -1229,7 +1230,7 @@ bool BlenderSession::builtin_image_pixels(const string &builtin_name,
   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);
+  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) {
@@ -1276,6 +1277,7 @@ bool BlenderSession::builtin_image_pixels(const string &builtin_name,
 
 bool BlenderSession::builtin_image_float_pixels(const string &builtin_name,
                                                 void *builtin_data,
+                                                int tile,
                                                 float *pixels,
                                                 const size_t pixels_size,
                                                 const bool,
@@ -1299,7 +1301,7 @@ bool BlenderSession::builtin_image_float_pixels(const string &builtin_name,
     const int channels = b_image.channels();
 
     float *image_pixels;
-    image_pixels = image_get_float_pixels_for_frame(b_image, frame);
+    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) {
index 7445fb53458fdac5d956e3d0c425db73b39ab4a8..2f25ec740f9fa0827d81036f878643cdd7d349ca 100644 (file)
@@ -157,12 +157,14 @@ class BlenderSession {
   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,
index c3564eac940eef440f0623f88839cee7c94aea88..6bbc73f72ec821c5ab7ddb39549c8e76b2672de8 100644 (file)
@@ -665,6 +665,12 @@ static ShaderNode *add_node(Scene *scene,
       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());
+      }
+
       /* TODO: restore */
       /* TODO(sergey): Does not work properly when we change builtin type. */
 #if 0
index bb52c740bfb4cf02387bb9d167e710856dcaf25f..332ee3575c0b1608c174b6a12ffea3969979a53e 100644 (file)
@@ -729,6 +729,9 @@ SceneParams BlenderSync::get_scene_params(BL::Scene &b_scene, bool background)
   params.bvh_layout = RNA_boolean_get(&cscene, "use_bvh_embree") ? BVH_LAYOUT_EMBREE :
                                                                    params.bvh_layout;
 #endif
+
+  params.background = background;
+
   return params;
 }
 
index cbe61e367fabc652d40f6b6999b22c35391f633e..efed96ec9f56c4d8c7f82f22f3c868a6c4d94c78 100644 (file)
@@ -34,8 +34,8 @@
 extern "C" {
 void BKE_image_user_frame_calc(void *ima, void *iuser, int cfra);
 void BKE_image_user_file_path(void *iuser, void *ima, char *path);
-unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame);
-float *BKE_image_get_float_pixels_for_frame(void *image, int frame);
+unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame, int tile);
+float *BKE_image_get_float_pixels_for_frame(void *image, int frame, int tile);
 }
 
 CCL_NAMESPACE_BEGIN
@@ -234,8 +234,15 @@ static inline int render_resolution_y(BL::RenderSettings &b_render)
 static inline string image_user_file_path(BL::ImageUser &iuser, BL::Image &ima, int cfra)
 {
   char filepath[1024];
+  iuser.tile(0);
   BKE_image_user_frame_calc(NULL, iuser.ptr.data, cfra);
   BKE_image_user_file_path(iuser.ptr.data, ima.ptr.data, filepath);
+  if (ima.source() == BL::Image::source_TILED) {
+    char *udim_id = strstr(filepath, "1001");
+    if (udim_id != NULL) {
+      memcpy(udim_id, "%04d", 4);
+    }
+  }
   return string(filepath);
 }
 
@@ -245,14 +252,14 @@ static inline int image_user_frame_number(BL::ImageUser &iuser, int cfra)
   return iuser.frame_current();
 }
 
-static inline unsigned char *image_get_pixels_for_frame(BL::Image &image, int frame)
+static inline unsigned char *image_get_pixels_for_frame(BL::Image &image, int frame, int tile)
 {
-  return BKE_image_get_pixels_for_frame(image.ptr.data, frame);
+  return BKE_image_get_pixels_for_frame(image.ptr.data, frame, tile);
 }
 
-static inline float *image_get_float_pixels_for_frame(BL::Image &image, int frame)
+static inline float *image_get_float_pixels_for_frame(BL::Image &image, int frame, int tile)
 {
-  return BKE_image_get_float_pixels_for_frame(image.ptr.data, frame);
+  return BKE_image_get_float_pixels_for_frame(image.ptr.data, frame, tile);
 }
 
 static inline void render_add_metadata(BL::RenderResult &b_rr, string name, string value)
index c4d7164a4d83413fc19580a30042df5530a88927..fd2833ee687ebaee12b516b7653abf649e712053 100644 (file)
@@ -311,7 +311,7 @@ ccl_device_noinline void svm_eval_nodes(KernelGlobals *kg,
 #  endif /* NODES_FEATURE(NODE_FEATURE_BUMP) */
 #  ifdef __TEXTURES__
       case NODE_TEX_IMAGE:
-        svm_node_tex_image(kg, sd, stack, node);
+        svm_node_tex_image(kg, sd, stack, node, &offset);
         break;
       case NODE_TEX_IMAGE_BOX:
         svm_node_tex_image_box(kg, sd, stack, node);
index 64abdd2d8b33cd45acc0dccba17f45eb1bc7e8df..90f1a7845c76e3ec039214ae46c0ac1d4dacb728 100644 (file)
@@ -20,6 +20,11 @@ CCL_NAMESPACE_BEGIN
 
 ccl_device float4 svm_image_texture(KernelGlobals *kg, int id, float x, float y, uint flags)
 {
+  if (id == -1) {
+    return make_float4(
+        TEX_IMAGE_MISSING_R, TEX_IMAGE_MISSING_G, TEX_IMAGE_MISSING_B, TEX_IMAGE_MISSING_A);
+  }
+
   float4 r = kernel_tex_image_interp(kg, id, x, y);
   const float alpha = r.w;
 
@@ -45,9 +50,9 @@ ccl_device_inline float3 texco_remap_square(float3 co)
   return (co - make_float3(0.5f, 0.5f, 0.5f)) * 2.0f;
 }
 
-ccl_device void svm_node_tex_image(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node)
+ccl_device void svm_node_tex_image(
+    KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int *offset)
 {
-  uint id = node.y;
   uint co_offset, out_offset, alpha_offset, flags;
 
   svm_unpack_node_uchar4(node.z, &co_offset, &out_offset, &alpha_offset, &flags);
@@ -65,6 +70,50 @@ ccl_device void svm_node_tex_image(KernelGlobals *kg, ShaderData *sd, float *sta
   else {
     tex_co = make_float2(co.x, co.y);
   }
+
+  /* TODO(lukas): Consider moving tile information out of the SVM node.
+   * TextureInfo seems a reasonable candidate. */
+  int id = -1;
+  int num_nodes = (int)node.y;
+  if (num_nodes > 0) {
+    /* Remember the offset of the node following the tile nodes. */
+    int next_offset = (*offset) + num_nodes;
+
+    /* Find the tile that the UV lies in. */
+    int tx = (int)tex_co.x;
+    int ty = (int)tex_co.y;
+
+    /* Check that we're within a legitimate tile. */
+    if (tx >= 0 && ty >= 0 && tx < 10) {
+      int tile = 1001 + 10 * ty + tx;
+
+      /* Find the index of the tile. */
+      for (int i = 0; i < num_nodes; i++) {
+        uint4 tile_node = read_node(kg, offset);
+        if (tile_node.x == tile) {
+          id = tile_node.y;
+          break;
+        }
+        if (tile_node.z == tile) {
+          id = tile_node.w;
+          break;
+        }
+      }
+
+      /* If we found the tile, offset the UVs to be relative to it. */
+      if (id != -1) {
+        tex_co.x -= tx;
+        tex_co.y -= ty;
+      }
+    }
+
+    /* Skip over the remaining nodes. */
+    *offset = next_offset;
+  }
+  else {
+    id = -num_nodes;
+  }
+
   float4 f = svm_image_texture(kg, id, tex_co.x, tex_co.y, flags);
 
   if (stack_valid(out_offset))
index 03ecc9bce5217a2960dbc1a0f21f01a7589f4b1f..212a867f9cd8a04c9412e90af525af1eb10ed8b1 100644 (file)
@@ -630,6 +630,7 @@ bool ImageManager::file_load_image(Image *img,
     if (FileFormat == TypeDesc::FLOAT) {
       builtin_image_float_pixels_cb(img->filename,
                                     img->builtin_data,
+                                    0, /* TODO(lukas): Support tiles here? */
                                     (float *)&pixels[0],
                                     num_pixels * components,
                                     image_associate_alpha(img),
@@ -638,6 +639,7 @@ bool ImageManager::file_load_image(Image *img,
     else if (FileFormat == TypeDesc::UINT8) {
       builtin_image_pixels_cb(img->filename,
                               img->builtin_data,
+                              0, /* TODO(lukas): Support tiles here? */
                               (uchar *)&pixels[0],
                               num_pixels * components,
                               image_associate_alpha(img),
index 459cd8c056c17dc043a82a57648dc5e54841bebe..bc04a667953596ef77268c42c075f1bc2b8a88be 100644 (file)
@@ -130,6 +130,7 @@ class ImageManager {
       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,
@@ -137,6 +138,7 @@ class ImageManager {
       builtin_image_pixels_cb;
   function<bool(const string &filename,
                 void *data,
+                int tile,
                 float *pixels,
                 const size_t pixels_size,
                 const bool associate_alpha,
index dc3f7c8f8ac35091d36d832feadde8621c27c041..06304205dc9ba3f1ab4a354277ce80ca81fb2756 100644 (file)
@@ -575,7 +575,8 @@ void LightManager::device_update_background(Device *device,
       if (node->type == EnvironmentTextureNode::node_type) {
         EnvironmentTextureNode *env = (EnvironmentTextureNode *)node;
         ImageMetaData metadata;
-        if (env->image_manager && env->image_manager->get_image_metadata(env->slot, metadata)) {
+        if (env->image_manager && !env->slots.empty() &&
+            env->image_manager->get_image_metadata(env->slots[0], metadata)) {
           res.x = max(res.x, metadata.width);
           res.y = max(res.y, metadata.height);
         }
index cffe2bfa70a322c9b3bdb0dba5f49ae865bb1205..2bf1040455f464cb8103916f2ab4434fab73b3bf 100644 (file)
@@ -637,6 +637,50 @@ void Mesh::add_subd_face(int *corners, int num_corners, int shader_, bool smooth
   subd_faces.push_back_reserved(face);
 }
 
+static void get_uv_tiles_from_attribute(Attribute *attr, int num, unordered_set<int> &tiles)
+{
+  if (attr == NULL) {
+    return;
+  }
+
+  const float2 *uv = attr->data_float2();
+  for (int i = 0; i < num; i++, uv++) {
+    float u = uv->x, v = uv->y;
+    int x = (int)u, y = (int)v;
+
+    if (x < 0 || y < 0 || x >= 10) {
+      continue;
+    }
+
+    /* Be conservative in corners - precisely touching the right or upper edge of a tile
+     * should not load its right/upper neighbor as well. */
+    if (x > 0 && (u < x + 1e-6f)) {
+      x--;
+    }
+    if (y > 0 && (v < y + 1e-6f)) {
+      y--;
+    }
+
+    tiles.insert(1001 + 10 * y + x);
+  }
+}
+
+void Mesh::get_uv_tiles(ustring map, unordered_set<int> &tiles)
+{
+  if (map.empty()) {
+    get_uv_tiles_from_attribute(attributes.find(ATTR_STD_UV), num_triangles() * 3, tiles);
+    get_uv_tiles_from_attribute(
+        subd_attributes.find(ATTR_STD_UV), subd_face_corners.size() + num_ngons, tiles);
+    get_uv_tiles_from_attribute(curve_attributes.find(ATTR_STD_UV), num_curves(), tiles);
+  }
+  else {
+    get_uv_tiles_from_attribute(attributes.find(map), num_triangles() * 3, tiles);
+    get_uv_tiles_from_attribute(
+        subd_attributes.find(map), subd_face_corners.size() + num_ngons, tiles);
+    get_uv_tiles_from_attribute(curve_attributes.find(map), num_curves(), tiles);
+  }
+}
+
 void Mesh::compute_bounds()
 {
   BoundBox bnds = BoundBox::empty;
@@ -2085,8 +2129,7 @@ void MeshManager::device_update_displacement_images(Device *device,
           }
 
           ImageSlotTextureNode *image_node = static_cast<ImageSlotTextureNode *>(node);
-          int slot = image_node->slot;
-          if (slot != -1) {
+          foreach (int slot, image_node->slots) {
             bump_images.insert(slot);
           }
         }
index 4a24a9c265650652d0a7595c3e18dd588c90b637..c5be0ba60b93f277b8cd5fda6933d5ea54c153f5 100644 (file)
@@ -28,6 +28,7 @@
 #include "util/util_list.h"
 #include "util/util_map.h"
 #include "util/util_param.h"
+#include "util/util_set.h"
 #include "util/util_transform.h"
 #include "util/util_types.h"
 #include "util/util_vector.h"
@@ -314,6 +315,8 @@ class Mesh : public Node {
   void add_vertex_normals();
   void add_undisplaced();
 
+  void get_uv_tiles(ustring map, unordered_set<int> &tiles);
+
   void pack_shaders(Scene *scene, uint *shader);
   void pack_normals(float4 *vnormal);
   void pack_verts(const vector<uint> &tri_prim_index,
index 5e12d79bc6be2d0dfeed80526063d3b321c84afd..b8847f92153538787f9f900eed8e4bc236d925db 100644 (file)
@@ -19,6 +19,7 @@
 #include "render/image.h"
 #include "render/integrator.h"
 #include "render/light.h"
+#include "render/mesh.h"
 #include "render/nodes.h"
 #include "render/scene.h"
 #include "render/svm.h"
@@ -204,6 +205,27 @@ void TextureMapping::compile(OSLCompiler &compiler)
 
 /* Image Texture */
 
+ImageSlotTextureNode::~ImageSlotTextureNode()
+{
+  if (image_manager) {
+    foreach (int slot, slots) {
+      if (slot != -1) {
+        image_manager->remove_image(slot);
+      }
+    }
+  }
+}
+
+void ImageSlotTextureNode::add_image_user() const
+{
+  /* Increase image user count for new node. */
+  foreach (int slot, slots) {
+    if (slot != -1) {
+      image_manager->add_image_user(slot);
+    }
+  }
+}
+
 NODE_DEFINE(ImageTextureNode)
 {
   NodeType *type = NodeType::add("image_texture", create, NodeType::SHADER);
@@ -253,30 +275,71 @@ NODE_DEFINE(ImageTextureNode)
 
 ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(node_type)
 {
-  image_manager = NULL;
-  slot = -1;
-  is_float = -1;
+  is_float = false;
   compress_as_srgb = false;
   colorspace = u_colorspace_raw;
   builtin_data = NULL;
   animated = false;
+  tiles.push_back(1001);
 }
 
-ImageTextureNode::~ImageTextureNode()
+ShaderNode *ImageTextureNode::clone() const
 {
-  if (image_manager) {
-    image_manager->remove_image(
-        filename.string(), builtin_data, interpolation, extension, alpha_type, colorspace);
-  }
+  add_image_user();
+  return new ImageTextureNode(*this);
 }
 
-ShaderNode *ImageTextureNode::clone() const
+void ImageTextureNode::cull_tiles(Scene *scene, ShaderGraph *graph)
 {
-  /* Increase image user count for new node. */
-  if (slot != -1) {
-    image_manager->add_image_user(slot);
+  if (!scene->params.background) {
+    /* During interactive renders, all tiles are loaded.
+     * While we could support updating this when UVs change, that could lead
+     * to annoying interruptions when loading images while editing UVs. */
+    return;
   }
-  return new ImageTextureNode(*this);
+
+  /* Only check UVs for tile culling if there are multiple tiles. */
+  if (tiles.size() < 2) {
+    return;
+  }
+
+  ShaderInput *vector_in = input("Vector");
+  ustring attribute;
+  if (vector_in->link) {
+    ShaderNode *node = vector_in->link->parent;
+    if (node->type == UVMapNode::node_type) {
+      UVMapNode *uvmap = (UVMapNode *)node;
+      attribute = uvmap->attribute;
+    }
+    else if (node->type == TextureCoordinateNode::node_type) {
+      if (vector_in->link != node->output("UV")) {
+        return;
+      }
+    }
+    else {
+      return;
+    }
+  }
+
+  unordered_set<int> used_tiles;
+  /* TODO(lukas): This is quite inefficient. A fairly simple improvement would
+   * be to have a cache in each mesh that is indexed by attribute.
+   * Additionally, building a graph-to-meshes list once could help. */
+  foreach (Mesh *mesh, scene->meshes) {
+    foreach (Shader *shader, mesh->used_shaders) {
+      if (shader->graph == graph) {
+        mesh->get_uv_tiles(attribute, used_tiles);
+      }
+    }
+  }
+
+  ccl::vector<int> new_tiles;
+  foreach (int tile, tiles) {
+    if (used_tiles.count(tile)) {
+      new_tiles.push_back(tile);
+    }
+  }
+  tiles.swap(new_tiles);
 }
 
 void ImageTextureNode::attributes(Shader *shader, AttributeRequestSet *attributes)
@@ -300,24 +363,61 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
   ShaderOutput *color_out = output("Color");
   ShaderOutput *alpha_out = output("Alpha");
 
-  image_manager = compiler.image_manager;
-  if (is_float == -1) {
-    ImageMetaData metadata;
-    slot = image_manager->add_image(filename.string(),
-                                    builtin_data,
-                                    animated,
-                                    0,
-                                    interpolation,
-                                    extension,
-                                    alpha_type,
-                                    colorspace,
-                                    metadata);
-    is_float = metadata.is_float;
-    compress_as_srgb = metadata.compress_as_srgb;
-    known_colorspace = metadata.colorspace;
+  image_manager = compiler.scene->image_manager;
+  if (slots.empty()) {
+    cull_tiles(compiler.scene, compiler.current_graph);
   }
+  if (slots.size() < tiles.size()) {
+    slots.clear();
+    slots.reserve(tiles.size());
+
+    bool have_metadata = false;
+    foreach (int tile, tiles) {
+      string tile_name = filename.string();
+      if (tiles.size() > 1) {
+        tile_name = string_printf(tile_name.c_str(), tile);
+      }
+
+      ImageMetaData metadata;
+      int slot = image_manager->add_image(tile_name,
+                                          builtin_data,
+                                          animated,
+                                          0,
+                                          interpolation,
+                                          extension,
+                                          alpha_type,
+                                          colorspace,
+                                          metadata);
+      slots.push_back(slot);
+
+      /* We assume that all tiles have the same metadata. */
+      if (!have_metadata) {
+        is_float = metadata.is_float;
+        compress_as_srgb = metadata.compress_as_srgb;
+        known_colorspace = metadata.colorspace;
+        have_metadata = true;
+      }
+    }
+  }
+
+  bool has_image = false;
+  foreach (int slot, slots) {
+    if (slot != -1) {
+      has_image = true;
+      break;
+    }
+  }
+
+  if (has_image) {
+    /* If there only is one image (a very common case), we encode it as a negative value. */
+    int num_nodes;
+    if (slots.size() == 1) {
+      num_nodes = -slots[0];
+    }
+    else {
+      num_nodes = divide_up(slots.size(), 2);
+    }
 
-  if (slot != -1) {
     int vector_offset = tex_mapping.compile_begin(compiler, vector_in);
     uint flags = 0;
 
@@ -336,7 +436,7 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
 
     if (projection != NODE_IMAGE_PROJ_BOX) {
       compiler.add_node(NODE_TEX_IMAGE,
-                        slot,
+                        num_nodes,
                         compiler.encode_uchar4(vector_offset,
                                                compiler.stack_assign_if_linked(color_out),
                                                compiler.stack_assign_if_linked(alpha_out),
@@ -345,7 +445,7 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
     }
     else {
       compiler.add_node(NODE_TEX_IMAGE_BOX,
-                        slot,
+                        num_nodes,
                         compiler.encode_uchar4(vector_offset,
                                                compiler.stack_assign_if_linked(color_out),
                                                compiler.stack_assign_if_linked(alpha_out),
@@ -353,6 +453,23 @@ void ImageTextureNode::compile(SVMCompiler &compiler)
                         __float_as_int(projection_blend));
     }
 
+    if (num_nodes > 0) {
+      for (int i = 0; i < num_nodes; i++) {
+        int4 node;
+        node.x = tiles[2 * i];
+        node.y = slots[2 * i];
+        if (2 * i + 1 < slots.size()) {
+          node.z = tiles[2 * i + 1];
+          node.w = slots[2 * i + 1];
+        }
+        else {
+          node.z = -1;
+          node.w = -1;
+        }
+        compiler.add_node(node.x, node.y, node.z, node.w);
+      }
+    }
+
     tex_mapping.compile_end(compiler, vector_in, vector_offset);
   }
   else {
@@ -375,34 +492,37 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
 
   tex_mapping.compile(compiler);
 
-  image_manager = compiler.image_manager;
-  if (is_float == -1) {
+  image_manager = compiler.scene->image_manager;
+  if (slots.size() == 0) {
     ImageMetaData metadata;
     if (builtin_data == NULL) {
       image_manager->get_image_metadata(filename.string(), NULL, colorspace, metadata);
+      slots.push_back(-1);
     }
     else {
-      slot = image_manager->add_image(filename.string(),
-                                      builtin_data,
-                                      animated,
-                                      0,
-                                      interpolation,
-                                      extension,
-                                      alpha_type,
-                                      colorspace,
-                                      metadata);
+      /* TODO(lukas): OSL UDIMs */
+      int slot = image_manager->add_image(filename.string(),
+                                          builtin_data,
+                                          animated,
+                                          0,
+                                          interpolation,
+                                          extension,
+                                          alpha_type,
+                                          colorspace,
+                                          metadata);
+      slots.push_back(slot);
     }
     is_float = metadata.is_float;
     compress_as_srgb = metadata.compress_as_srgb;
     known_colorspace = metadata.colorspace;
   }
 
-  if (slot == -1) {
+  if (slots[0] == -1) {
     compiler.parameter_texture(
         "filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
   }
   else {
-    compiler.parameter_texture("filename", slot);
+    compiler.parameter_texture("filename", slots[0]);
   }
 
   const bool unassociate_alpha = !(ColorSpaceManager::colorspace_is_data(colorspace) ||
@@ -462,29 +582,16 @@ NODE_DEFINE(EnvironmentTextureNode)
 
 EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(node_type)
 {
-  image_manager = NULL;
-  slot = -1;
-  is_float = -1;
+  is_float = false;
   compress_as_srgb = false;
   colorspace = u_colorspace_raw;
   builtin_data = NULL;
   animated = false;
 }
 
-EnvironmentTextureNode::~EnvironmentTextureNode()
-{
-  if (image_manager) {
-    image_manager->remove_image(
-        filename.string(), builtin_data, interpolation, EXTENSION_REPEAT, alpha_type, colorspace);
-  }
-}
-
 ShaderNode *EnvironmentTextureNode::clone() const
 {
-  /* Increase image user count for new node. */
-  if (slot != -1) {
-    image_manager->add_image_user(slot);
-  }
+  add_image_user();
   return new EnvironmentTextureNode(*this);
 }
 
@@ -507,24 +614,25 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
   ShaderOutput *color_out = output("Color");
   ShaderOutput *alpha_out = output("Alpha");
 
-  image_manager = compiler.image_manager;
-  if (slot == -1) {
+  image_manager = compiler.scene->image_manager;
+  if (slots.empty()) {
     ImageMetaData metadata;
-    slot = image_manager->add_image(filename.string(),
-                                    builtin_data,
-                                    animated,
-                                    0,
-                                    interpolation,
-                                    EXTENSION_REPEAT,
-                                    alpha_type,
-                                    colorspace,
-                                    metadata);
+    int slot = image_manager->add_image(filename.string(),
+                                        builtin_data,
+                                        animated,
+                                        0,
+                                        interpolation,
+                                        EXTENSION_REPEAT,
+                                        alpha_type,
+                                        colorspace,
+                                        metadata);
+    slots.push_back(slot);
     is_float = metadata.is_float;
     compress_as_srgb = metadata.compress_as_srgb;
     known_colorspace = metadata.colorspace;
   }
 
-  if (slot != -1) {
+  if (slots[0] != -1) {
     int vector_offset = tex_mapping.compile_begin(compiler, vector_in);
     uint flags = 0;
 
@@ -533,7 +641,7 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler)
     }
 
     compiler.add_node(NODE_TEX_ENVIRONMENT,
-                      slot,
+                      slots[0],
                       compiler.encode_uchar4(vector_offset,
                                              compiler.stack_assign_if_linked(color_out),
                                              compiler.stack_assign_if_linked(alpha_out),
@@ -563,34 +671,36 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
   /* See comments in ImageTextureNode::compile about support
    * of builtin images.
    */
-  image_manager = compiler.image_manager;
-  if (is_float == -1) {
+  image_manager = compiler.scene->image_manager;
+  if (slots.empty()) {
     ImageMetaData metadata;
     if (builtin_data == NULL) {
       image_manager->get_image_metadata(filename.string(), NULL, colorspace, metadata);
+      slots.push_back(-1);
     }
     else {
-      slot = image_manager->add_image(filename.string(),
-                                      builtin_data,
-                                      animated,
-                                      0,
-                                      interpolation,
-                                      EXTENSION_REPEAT,
-                                      alpha_type,
-                                      colorspace,
-                                      metadata);
+      int slot = image_manager->add_image(filename.string(),
+                                          builtin_data,
+                                          animated,
+                                          0,
+                                          interpolation,
+                                          EXTENSION_REPEAT,
+                                          alpha_type,
+                                          colorspace,
+                                          metadata);
+      slots.push_back(slot);
     }
     is_float = metadata.is_float;
     compress_as_srgb = metadata.compress_as_srgb;
     known_colorspace = metadata.colorspace;
   }
 
-  if (slot == -1) {
+  if (slots[0] == -1) {
     compiler.parameter_texture(
         "filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
   }
   else {
-    compiler.parameter_texture("filename", slot);
+    compiler.parameter_texture("filename", slots[0]);
   }
 
   compiler.parameter(this, "projection");
@@ -1123,7 +1233,7 @@ void IESLightNode::get_slot()
 
 void IESLightNode::compile(SVMCompiler &compiler)
 {
-  light_manager = compiler.light_manager;
+  light_manager = compiler.scene->light_manager;
   get_slot();
 
   ShaderInput *strength_in = input("Strength");
@@ -1145,7 +1255,7 @@ void IESLightNode::compile(SVMCompiler &compiler)
 
 void IESLightNode::compile(OSLCompiler &compiler)
 {
-  light_manager = compiler.light_manager;
+  light_manager = compiler.scene->light_manager;
   get_slot();
 
   tex_mapping.compile(compiler);
@@ -1663,7 +1773,7 @@ void PointDensityTextureNode::compile(SVMCompiler &compiler)
   const bool use_density = !density_out->links.empty();
   const bool use_color = !color_out->links.empty();
 
-  image_manager = compiler.image_manager;
+  image_manager = compiler.scene->image_manager;
 
   if (use_density || use_color) {
     add_image();
@@ -1704,7 +1814,7 @@ void PointDensityTextureNode::compile(OSLCompiler &compiler)
   const bool use_density = !density_out->links.empty();
   const bool use_color = !color_out->links.empty();
 
-  image_manager = compiler.image_manager;
+  image_manager = compiler.scene->image_manager;
 
   if (use_density || use_color) {
     add_image();
index 54124cd2175746bf56676da28a1452b7a6467a36..a8fe764495765b5d107dab53ce90c07761c28c51 100644 (file)
@@ -77,14 +77,17 @@ class ImageSlotTextureNode : public TextureNode {
   explicit ImageSlotTextureNode(const NodeType *node_type) : TextureNode(node_type)
   {
     special_type = SHADER_SPECIAL_TYPE_IMAGE_SLOT;
+    image_manager = NULL;
   }
-  int slot;
+  ~ImageSlotTextureNode();
+  void add_image_user() const;
+  ImageManager *image_manager;
+  vector<int> slots;
 };
 
 class ImageTextureNode : public ImageSlotTextureNode {
  public:
   SHADER_NODE_NO_CLONE_CLASS(ImageTextureNode)
-  ~ImageTextureNode();
   ShaderNode *clone() const;
   void attributes(Shader *shader, AttributeRequestSet *attributes);
   bool has_attribute_dependency()
@@ -110,18 +113,20 @@ class ImageTextureNode : public ImageSlotTextureNode {
   float projection_blend;
   bool animated;
   float3 vector;
+  ccl::vector<int> tiles;
 
   /* Runtime. */
-  ImageManager *image_manager;
-  int is_float;
+  bool is_float;
   bool compress_as_srgb;
   ustring known_colorspace;
+
+ protected:
+  void cull_tiles(Scene *scene, ShaderGraph *graph);
 };
 
 class EnvironmentTextureNode : public ImageSlotTextureNode {
  public:
   SHADER_NODE_NO_CLONE_CLASS(EnvironmentTextureNode)
-  ~EnvironmentTextureNode();
   ShaderNode *clone() const;
   void attributes(Shader *shader, AttributeRequestSet *attributes);
   bool has_attribute_dependency()
@@ -151,8 +156,7 @@ class EnvironmentTextureNode : public ImageSlotTextureNode {
   float3 vector;
 
   /* Runtime. */
-  ImageManager *image_manager;
-  int is_float;
+  bool is_float;
   bool compress_as_srgb;
   ustring known_colorspace;
 };
index 889552f49cd4a7106f231075474819af19377d4a..91f02e42071ab42caa7f130292753a4f2bbbd9a4 100644 (file)
@@ -118,9 +118,9 @@ void OSLShaderManager::device_update(Device *device,
      * compile shaders alternating */
     thread_scoped_lock lock(ss_mutex);
 
-    OSLCompiler compiler(this, services, ss, scene->image_manager, scene->light_manager);
+    OSLCompiler compiler(this, services, ss, scene);
     compiler.background = (shader == scene->default_background);
-    compiler.compile(scene, og, shader);
+    compiler.compile(og, shader);
 
     if (shader->use_mis && shader->has_surface_emission)
       scene->light_manager->need_update = true;
@@ -566,13 +566,8 @@ OSLNode *OSLShaderManager::osl_node(const std::string &filepath,
 OSLCompiler::OSLCompiler(OSLShaderManager *manager,
                          OSLRenderServices *services,
                          OSL::ShadingSystem *ss,
-                         ImageManager *image_manager,
-                         LightManager *light_manager)
-    : image_manager(image_manager),
-      light_manager(light_manager),
-      manager(manager),
-      services(services),
-      ss(ss)
+                         Scene *scene)
+    : scene(scene), manager(manager), services(services), ss(ss)
 {
   current_type = SHADER_TYPE_SURFACE;
   current_shader = NULL;
@@ -1114,7 +1109,7 @@ OSL::ShaderGroupRef OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph
   return group;
 }
 
-void OSLCompiler::compile(Scene *scene, OSLGlobals *og, Shader *shader)
+void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
 {
   if (shader->need_update) {
     ShaderGraph *graph = shader->graph;
index 17bf98a34337bae707c16e5d4994e7c0972280c8..62cbfebf7ebd14c7962c649f1ef57856d1369f12 100644 (file)
@@ -131,10 +131,9 @@ class OSLCompiler {
   OSLCompiler(OSLShaderManager *manager,
               OSLRenderServices *services,
               OSL::ShadingSystem *shadingsys,
-              ImageManager *image_manager,
-              LightManager *light_manager);
+              Scene *scene);
 #endif
-  void compile(Scene *scene, OSLGlobals *og, Shader *shader);
+  void compile(OSLGlobals *og, Shader *shader);
 
   void add(ShaderNode *node, const char *name, bool isfilepath = false);
 
@@ -165,8 +164,7 @@ class OSLCompiler {
   }
 
   bool background;
-  ImageManager *image_manager;
-  LightManager *light_manager;
+  Scene *scene;
 
  private:
 #ifdef WITH_OSL
index 45997bccf5d14382969bbb30081c58984ea4578c..f99510d2d4285266944d0c7374d59a8c61b3f091 100644 (file)
@@ -170,6 +170,8 @@ class SceneParams {
   bool persistent_data;
   int texture_limit;
 
+  bool background;
+
   SceneParams()
   {
     shadingsystem = SHADINGSYSTEM_SVM;
@@ -180,6 +182,7 @@ class SceneParams {
     num_bvh_time_steps = 0;
     persistent_data = false;
     texture_limit = 0;
+    background = true;
   }
 
   bool modified(const SceneParams &params)
index 8466742ae38a7ee0f7d35bbb97de3e9cf9d4989c..f42a2ea818d55a59f1b9e24c1bb2e5c7977dad2d 100644 (file)
@@ -57,9 +57,9 @@ void SVMShaderManager::device_update_shader(Scene *scene,
   svm_nodes->push_back_slow(make_int4(NODE_SHADER_JUMP, 0, 0, 0));
 
   SVMCompiler::Summary summary;
-  SVMCompiler compiler(scene->shader_manager, scene->image_manager, scene->light_manager);
+  SVMCompiler compiler(scene);
   compiler.background = (shader == scene->default_background);
-  compiler.compile(scene, shader, *svm_nodes, 0, &summary);
+  compiler.compile(shader, *svm_nodes, 0, &summary);
 
   VLOG(2) << "Compilation summary:\n"
           << "Shader name: " << shader->name << "\n"
@@ -169,13 +169,8 @@ void SVMShaderManager::device_free(Device *device, DeviceScene *dscene, Scene *s
 
 /* Graph Compiler */
 
-SVMCompiler::SVMCompiler(ShaderManager *shader_manager_,
-                         ImageManager *image_manager_,
-                         LightManager *light_manager_)
+SVMCompiler::SVMCompiler(Scene *scene) : scene(scene)
 {
-  shader_manager = shader_manager_;
-  image_manager = image_manager_;
-  light_manager = light_manager_;
   max_stack_use = 0;
   current_type = SHADER_TYPE_SURFACE;
   current_shader = NULL;
@@ -408,12 +403,12 @@ void SVMCompiler::add_node(const float4 &f)
 
 uint SVMCompiler::attribute(ustring name)
 {
-  return shader_manager->get_attribute_id(name);
+  return scene->shader_manager->get_attribute_id(name);
 }
 
 uint SVMCompiler::attribute(AttributeStandard std)
 {
-  return shader_manager->get_attribute_id(std);
+  return scene->shader_manager->get_attribute_id(std);
 }
 
 uint SVMCompiler::attribute_standard(ustring name)
@@ -838,8 +833,7 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
   }
 }
 
-void SVMCompiler::compile(
-    Scene *scene, Shader *shader, array<int4> &svm_nodes, int index, Summary *summary)
+void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Summary *summary)
 {
   /* copy graph for shader with bump mapping */
   ShaderNode *output = shader->graph->output();
index d1534567beaaea9c41a0feaa507c28462f163ad8..61923fc40ac42cb04ecbf276a0a0c29db58f7797 100644 (file)
@@ -93,11 +93,8 @@ class SVMCompiler {
     string full_report() const;
   };
 
-  SVMCompiler(ShaderManager *shader_manager,
-              ImageManager *image_manager,
-              LightManager *light_manager);
-  void compile(
-      Scene *scene, Shader *shader, array<int4> &svm_nodes, int index, Summary *summary = NULL);
+  SVMCompiler(Scene *scene);
+  void compile(Shader *shader, array<int4> &svm_nodes, int index, Summary *summary = NULL);
 
   int stack_assign(ShaderOutput *output);
   int stack_assign(ShaderInput *input);
@@ -126,9 +123,8 @@ class SVMCompiler {
     return current_type;
   }
 
-  ImageManager *image_manager;
-  ShaderManager *shader_manager;
-  LightManager *light_manager;
+  Scene *scene;
+  ShaderGraph *current_graph;
   bool background;
 
  protected:
@@ -221,7 +217,6 @@ class SVMCompiler {
   array<int4> current_svm_nodes;
   ShaderType current_type;
   Shader *current_shader;
-  ShaderGraph *current_graph;
   Stack active_stack;
   int max_stack_use;
   uint mix_weight_offset;
index ca93f8b02d0fbfd0deeacc38b63a3686cf1a7a56..ace4f29913b4f2a5ec205dc75c59ad306abc9847 100644 (file)
@@ -158,10 +158,11 @@ class RenderGraph : public testing::Test {
   Device *device_cpu;
   SceneParams scene_params;
   Scene *scene;
+  Shader shader;
   ShaderGraph graph;
   ShaderGraphBuilder builder;
 
-  RenderGraph() : testing::Test(), builder(&graph)
+  RenderGraph() : testing::Test(), graph(&shader), builder(&graph)
   {
   }
 
index 8de12f759fc023e9983c8f26acf662bc024a8135..5af9fed83f8dcea643905494bdb26827f037f47c 100644 (file)
@@ -1063,6 +1063,44 @@ class IMAGE_PT_render_slots(Panel):
         col.operator("image.clear_render_slot", icon='X', text="")
 
 
+class IMAGE_UL_udim_tiles(UIList):
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+        tile = item
+        layout.prop(tile, "label", text="", emboss=False)
+
+
+class IMAGE_PT_udim_tiles(Panel):
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'UI'
+    bl_category = "Image"
+    bl_label = "UDIM Tiles"
+
+    @classmethod
+    def poll(cls, context):
+        sima = context.space_data
+        return (sima and sima.image and sima.image.source == 'TILED')
+
+    def draw(self, context):
+        layout = self.layout
+
+        sima = context.space_data
+        ima = sima.image
+
+        row = layout.row()
+        col = row.column()
+        col.template_list("IMAGE_UL_udim_tiles", "", ima, "tiles", ima.tiles, "active_index", rows=4)
+
+        col = row.column()
+        sub = col.column(align=True)
+        sub.operator("image.tile_add", icon='ADD', text="")
+        sub.operator("image.tile_remove", icon='REMOVE', text="")
+
+        tile = ima.tiles.active
+        if tile:
+            col = layout.column(align=True)
+            col.operator("image.tile_fill")
+
+
 class IMAGE_PT_paint(Panel, ImagePaintPanel):
     bl_label = "Brush"
     bl_context = ".paint_common_2d"
@@ -1690,6 +1728,28 @@ class IMAGE_PT_uv_cursor(Panel):
         col.prop(sima, "cursor_location", text="Cursor Location")
 
 
+class IMAGE_PT_udim_grid(Panel):
+    bl_space_type = 'IMAGE_EDITOR'
+    bl_region_type = 'UI'
+    bl_category = "View"
+    bl_label = "UDIM Grid"
+
+    @classmethod
+    def poll(cls, context):
+        sima = context.space_data
+
+        return sima.show_uvedit and sima.image is None
+
+    def draw(self, context):
+        layout = self.layout
+
+        sima = context.space_data
+        uvedit = sima.uv_editor
+
+        col = layout.column()
+        col.prop(uvedit, "tile_grid_shape", text="Grid Shape")
+
+
 # Grease Pencil properties
 class IMAGE_PT_annotation(AnnotationDataPanel, Panel):
     bl_space_type = 'IMAGE_EDITOR'
@@ -1732,6 +1792,8 @@ classes = (
     IMAGE_PT_image_properties,
     IMAGE_UL_render_slots,
     IMAGE_PT_render_slots,
+    IMAGE_UL_udim_tiles,
+    IMAGE_PT_udim_tiles,
     IMAGE_PT_view_display,
     IMAGE_PT_view_display_uv_edit_overlays,
     IMAGE_PT_view_display_uv_edit_overlays_stretch,
@@ -1758,6 +1820,7 @@ classes = (
     IMAGE_PT_scope_sample,
     IMAGE_PT_uv_cursor,
     IMAGE_PT_annotation,
+    IMAGE_PT_udim_grid,
 )
 
 
index bf0aa96df849bab80337976729fcef8fa3394b56..40abf93e94434b20c94112a2d4573248d4fb88b8 100644 (file)
@@ -67,6 +67,8 @@ void BLF_size(int fontid, int size, int dpi);
 void BLF_color4ubv(int fontid, const unsigned char rgba[4]);
 void BLF_color3ubv(int fontid, const unsigned char rgb[3]);
 void BLF_color3ubv_alpha(int fontid, const unsigned char rgb[3], unsigned char alpha);
+void BLF_color4ub(
+    int fontid, unsigned char r, unsigned char g, unsigned char b, unsigned char alpha);
 void BLF_color3ub(int fontid, unsigned char r, unsigned char g, unsigned char b);
 void BLF_color4f(int fontid, float r, float g, float b, float a);
 void BLF_color4fv(int fontid, const float rgba[4]);
index 8e1ff77b1c7f63dcb0e28e63e280ecd2343ca8bc..10bb1bd3c9c7c8fceb436e783700cad8ed93d9f0 100644 (file)
@@ -475,6 +475,19 @@ void BLF_color3ubv(int fontid, const unsigned char rgb[3])
   BLF_color3ubv_alpha(fontid, rgb, 255);
 }
 
+void BLF_color4ub(
+    int fontid, unsigned char r, unsigned char g, unsigned char b, unsigned char alpha)
+{
+  FontBLF *font = blf_get(fontid);
+
+  if (font) {
+    font->color[0] = r;
+    font->color[1] = g;
+    font->color[2] = b;
+    font->color[3] = alpha;
+  }
+}
+
 void BLF_color3ub(int fontid, unsigned char r, unsigned char g, unsigned char b)
 {
   FontBLF *font = blf_get(fontid);
index 82c831ae8e062298afc9177fee8ce34a04f1b1ba..e2bb1f988caa653808911300692336f632ca2559 100644 (file)
@@ -33,6 +33,7 @@ struct ImBuf;
 struct Image;
 struct ImageFormatData;
 struct ImagePool;
+struct ImageTile;
 struct ImbFormatOptions;
 struct Main;
 struct Object;
@@ -43,6 +44,7 @@ struct StampData;
 struct anim;
 
 #define IMA_MAX_SPACE 64
+#define IMA_UDIM_MAX 1999
 
 void BKE_images_init(void);
 void BKE_images_exit(void);
@@ -206,7 +208,8 @@ struct Image *BKE_image_add_generated(struct Main *bmain,
                                       short gen_type,
                                       const float color[4],
                                       const bool stereo3d,
-                                      const bool is_data);
+                                      const bool is_data,
+                                      const bool tiled);
 /* adds image from imbuf, owns imbuf */
 struct Image *BKE_image_add_from_imbuf(struct Main *bmain, struct ImBuf *ibuf, const char *name);
 
@@ -302,6 +305,32 @@ bool BKE_image_has_alpha(struct Image *image);
 /* check if texture has gpu texture code */
 bool BKE_image_has_opengl_texture(struct Image *ima);
 
+/* get tile index for tiled images */
+void BKE_image_get_tile_label(struct Image *ima,
+                              struct ImageTile *tile,
+                              char *label,
+                              int len_label);
+
+struct ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label);
+bool BKE_image_remove_tile(struct Image *ima, struct ImageTile *tile);
+
+bool BKE_image_fill_tile(struct Image *ima,
+                         struct ImageTile *tile,
+                         int width,
+                         int height,
+                         const float color[4],
+                         int gen_type,
+                         int planes,
+                         bool is_float);
+
+struct ImageTile *BKE_image_get_tile(struct Image *ima, int tile_number);
+struct ImageTile *BKE_image_get_tile_from_iuser(struct Image *ima, struct ImageUser *iuser);
+
+int BKE_image_get_tile_from_pos(struct Image *ima,
+                                const float uv[2],
+                                float new_uv[2],
+                                float ofs[2]);
+
 void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *width, int *height);
 void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float size[2]);
 void BKE_image_get_aspect(struct Image *image, float *aspx, float *aspy);
@@ -316,8 +345,8 @@ void BKE_image_buf_fill_checker_color(unsigned char *rect,
                                       int height);
 
 /* Cycles hookup */
-unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame);
-float *BKE_image_get_float_pixels_for_frame(struct Image *image, int frame);
+unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame, int tile);
+float *BKE_image_get_float_pixels_for_frame(struct Image *image, int frame, int tile);
 
 /* Image modifications */
 bool BKE_image_is_dirty(struct Image *image);
@@ -331,6 +360,7 @@ bool BKE_image_has_anim(struct Image *image);
 bool BKE_image_has_packedfile(struct Image *image);
 bool BKE_image_has_filepath(struct Image *ima);
 bool BKE_image_is_animated(struct Image *image);
+bool BKE_image_has_multiple_ibufs(struct Image *image);
 void BKE_image_file_format_set(struct Image *image,
                                int ftype,
                                const struct ImbFormatOptions *options);
index fa7af53df2d57dbe76cfd57ab1e734df4cebae6a..de7837cdd90bee180361562fd40075245e06a060 100644 (file)
@@ -453,7 +453,8 @@ void BKE_bpath_traverse_id(
         /* Skip empty file paths, these are typically from generated images and
          * don't make sense to add directories to until the image has been saved
          * once to give it a meaningful value. */
-        if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE) && ima->name[0]) {
+        if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED) &&
+            ima->name[0]) {
           if (rewrite_path_fixed(ima->name, visit_cb, absbase, bpath_user_data)) {
             if (flag & BKE_BPATH_TRAVERSE_RELOAD_EDITED) {
               if (!BKE_image_has_packedfile(ima) &&
index 4c81bd4b01916aa48cb80331ce789fe951b4155b..fca81acf03891443d87a5a6f2e5de81561978b34 100644 (file)
@@ -87,6 +87,7 @@
 #include "RE_pipeline.h"
 
 #include "GPU_draw.h"
+#include "GPU_texture.h"
 
 #include "BLI_sys_types.h"  // for intptr_t support
 
@@ -111,9 +112,9 @@ static void image_add_view(Image *ima, const char *viewname, const char *filepat
 /* max int, to indicate we don't store sequences in ibuf */
 #define IMA_NO_INDEX 0x7FEFEFEF
 
-/* quick lookup: supports 1 million frames, thousand passes */
-#define IMA_MAKE_INDEX(frame, index) (((frame) << 10) + (index))
-#define IMA_INDEX_FRAME(index) ((index) >> 10)
+/* quick lookup: supports 1 million entries, thousand passes */
+#define IMA_MAKE_INDEX(entry, index) (((entry) << 10) + (index))
+#define IMA_INDEX_ENTRY(index) ((index) >> 10)
 #if 0
 #  define IMA_INDEX_PASS(index) (index & ~1023)
 #endif
@@ -142,7 +143,7 @@ static void imagecache_keydata(void *userkey, int *framenr, int *proxy, int *ren
 {
   ImageCacheKey *key = userkey;
 
-  *framenr = IMA_INDEX_FRAME(key->index);
+  *framenr = IMA_INDEX_ENTRY(key->index);
   *proxy = IMB_PROXY_NONE;
   *render_flags = 0;
 }
@@ -165,6 +166,17 @@ static void imagecache_put(Image *image, int index, ImBuf *ibuf)
   IMB_moviecache_put(image->cache, &key, ibuf);
 }
 
+static void imagecache_remove(Image *image, int index)
+{
+  if (image->cache == NULL) {
+    return;
+  }
+
+  ImageCacheKey key;
+  key.index = index;
+  IMB_moviecache_remove(image->cache, &key);
+}
+
 static struct ImBuf *imagecache_get(Image *image, int index)
 {
   if (image->cache) {
@@ -257,7 +269,9 @@ void BKE_image_free_buffers_ex(Image *ima, bool do_lock)
     GPU_free_image(ima);
   }
 
-  ima->ok = IMA_OK;
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    tile->ok = IMA_OK;
+  }
 
   if (do_lock) {
     BLI_mutex_unlock(image_mutex);
@@ -290,6 +304,8 @@ void BKE_image_free(Image *ima)
 
   BKE_icon_id_delete(&ima->id);
   BKE_previewimg_free(&ima->preview);
+
+  BLI_freelistN(&ima->tiles);
 }
 
 /* only image block itself */
@@ -299,8 +315,6 @@ static void image_init(Image *ima, short source, short type)
 
   MEMCPY_STRUCT_AFTER(ima, DNA_struct_default_get(Image), id);
 
-  ima->ok = IMA_OK;
-
   ima->source = source;
   ima->type = type;
 
@@ -308,6 +322,11 @@ static void image_init(Image *ima, short source, short type)
     ima->flag |= IMA_VIEW_AS_RENDER;
   }
 
+  ImageTile *tile = MEM_callocN(sizeof(ImageTile), "Image Tiles");
+  tile->ok = IMA_OK;
+  tile->tile_number = 1001;
+  BLI_addtail(&ima->tiles, tile);
+
   if (type == IMA_TYPE_R_RESULT) {
     for (int i = 0; i < 8; i++) {
       BKE_image_add_renderslot(ima, NULL);
@@ -337,34 +356,42 @@ static Image *image_alloc(Main *bmain, const char *name, short source, short typ
   return ima;
 }
 
-/* Get the ibuf from an image cache by it's index and frame.
+/* Get the ibuf from an image cache by it's index and entry.
  * Local use here only.
  *
  * Returns referenced image buffer if it exists, callee is to
  * call IMB_freeImBuf to de-reference the image buffer after
  * it's done handling it.
  */
-static ImBuf *image_get_cached_ibuf_for_index_frame(Image *ima, int index, int frame)
+static ImBuf *image_get_cached_ibuf_for_index_entry(Image *ima, int index, int entry)
 {
   if (index != IMA_NO_INDEX) {
-    index = IMA_MAKE_INDEX(frame, index);
+    index = IMA_MAKE_INDEX(entry, index);
   }
 
   return imagecache_get(ima, index);
 }
 
 /* no ima->ibuf anymore, but listbase */
-static void image_assign_ibuf(Image *ima, ImBuf *ibuf, int index, int frame)
+static void image_assign_ibuf(Image *ima, ImBuf *ibuf, int index, int entry)
 {
   if (ibuf) {
     if (index != IMA_NO_INDEX) {
-      index = IMA_MAKE_INDEX(frame, index);
+      index = IMA_MAKE_INDEX(entry, index);
     }
 
     imagecache_put(ima, index, ibuf);
   }
 }
 
+static void image_remove_ibuf(Image *ima, int index, int entry)
+{
+  if (index != IMA_NO_INDEX) {
+    index = IMA_MAKE_INDEX(entry, index);
+  }
+  imagecache_remove(ima, index);
+}
+
 static void copy_image_packedfiles(ListBase *lb_dst, const ListBase *lb_src)
 {
   const ImagePackedFile *imapf_src;
@@ -413,8 +440,11 @@ void BKE_image_copy_data(Main *UNUSED(bmain), Image *ima_dst, const Image *ima_s
 
   BLI_listbase_clear(&ima_dst->anims);
 
-  for (int i = 0; i < TEXTARGET_COUNT; i++) {
-    ima_dst->gputexture[i] = NULL;
+  BLI_duplicatelist(&ima_dst->tiles, &ima_src->tiles);
+  LISTBASE_FOREACH (ImageTile *, tile, &ima_dst->tiles) {
+    for (int i = 0; i < TEXTARGET_COUNT; i++) {
+      tile->gputexture[i] = NULL;
+    }
   }
 
   if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) {
@@ -480,14 +510,82 @@ bool BKE_image_scale(Image *image, int width, int height)
 
 bool BKE_image_has_opengl_texture(Image *ima)
 {
-  for (int i = 0; i < TEXTARGET_COUNT; i++) {
-    if (ima->gputexture[i]) {
-      return true;
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    for (int i = 0; i < TEXTARGET_COUNT; i++) {
+      if (tile->gputexture[i] != NULL) {
+        return true;
+      }
     }
   }
   return false;
 }
 
+ImageTile *BKE_image_get_tile(Image *ima, int tile_number)
+{
+  if (ima == NULL) {
+    return NULL;
+  }
+
+  /* Verify valid tile range. */
+  if ((tile_number != 0) && (tile_number < 1001 || tile_number > IMA_UDIM_MAX)) {
+    return NULL;
+  }
+
+  /* Tile number 0 is a special case and refers to the first tile, typically
+   * coming from non-UDIM-aware code. */
+  if (tile_number == 0 || tile_number == 1001) {
+    return ima->tiles.first;
+  }
+
+  if (ima->source != IMA_SRC_TILED) {
+    return NULL;
+  }
+
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    if (tile->tile_number == tile_number) {
+      return tile;
+    }
+  }
+
+  return NULL;
+}
+
+ImageTile *BKE_image_get_tile_from_iuser(Image *ima, ImageUser *iuser)
+{
+  return BKE_image_get_tile(ima, (iuser && iuser->tile) ? iuser->tile : 1001);
+}
+
+int BKE_image_get_tile_from_pos(struct Image *ima,
+                                const float uv[2],
+                                float new_uv[2],
+                                float ofs[2])
+{
+  float local_ofs[2];
+  if (ofs == NULL) {
+    ofs = local_ofs;
+  }
+
+  copy_v2_v2(new_uv, uv);
+  zero_v2(ofs);
+
+  if ((ima->source != IMA_SRC_TILED) || uv[0] < 0.0f || uv[1] < 0.0f || uv[0] >= 10.0f) {
+    return 0;
+  }
+
+  int ix = (int)uv[0];
+  int iy = (int)uv[1];
+  int tile_number = 1001 + 10 * iy + ix;
+
+  if (BKE_image_get_tile(ima, tile_number) == NULL) {
+    return 0;
+  }
+  ofs[0] = ix;
+  ofs[1] = iy;
+  sub_v2_v2(new_uv, ofs);
+
+  return tile_number;
+}
+
 static void image_init_color_management(Image *ima)
 {
   ImBuf *ibuf;
@@ -580,8 +678,10 @@ Image *BKE_image_load_exists_ex(Main *bmain, const char *filepath, bool *r_exist
       if (BLI_path_cmp(strtest, str) == 0) {
         if ((BKE_image_has_anim(ima) == false) || (ima->id.us == 0)) {
           id_us_plus(&ima->id); /* officially should not, it doesn't link here! */
-          if (ima->ok == 0) {
-            ima->ok = IMA_OK;
+          LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+            if (tile->ok == 0) {
+              tile->ok = IMA_OK;
+            }
           }
           if (r_exists) {
             *r_exists = true;
@@ -690,10 +790,17 @@ Image *BKE_image_add_generated(Main *bmain,
                                short gen_type,
                                const float color[4],
                                const bool stereo3d,
-                               const bool is_data)
+                               const bool is_data,
+                               const bool tiled)
 {
   /* on save, type is changed to FILE in editsima.c */
-  Image *ima = image_alloc(bmain, name, IMA_SRC_GENERATED, IMA_TYPE_UV_TEST);
+  Image *ima;
+  if (tiled) {
+    ima = image_alloc(bmain, name, IMA_SRC_TILED, IMA_TYPE_IMAGE);
+  }
+  else {
+    ima = image_alloc(bmain, name, IMA_SRC_GENERATED, IMA_TYPE_UV_TEST);
+  }
   if (ima == NULL) {
     return NULL;
   }
@@ -718,7 +825,9 @@ Image *BKE_image_add_generated(Main *bmain,
     ImBuf *ibuf;
     ibuf = add_ibuf_size(
         width, height, ima->name, depth, floatbuf, gen_type, color, &ima->colorspace_settings);
-    image_assign_ibuf(ima, ibuf, stereo3d ? view_id : IMA_NO_INDEX, 0);
+    int index = tiled ? 0 : IMA_NO_INDEX;
+    int entry = tiled ? 1001 : 0;
+    image_assign_ibuf(ima, ibuf, stereo3d ? view_id : index, entry);
 
     /* image_assign_ibuf puts buffer to the cache, which increments user counter. */
     IMB_freeImBuf(ibuf);
@@ -729,7 +838,8 @@ Image *BKE_image_add_generated(Main *bmain,
     image_add_view(ima, names[view_id], "");
   }
 
-  ima->ok = IMA_OK_LOADED;
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
+  tile->ok = IMA_OK_LOADED;
 
   return ima;
 }
@@ -751,7 +861,8 @@ Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name)
   if (ima) {
     STRNCPY(ima->name, ibuf->name);
     image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0);
-    ima->ok = IMA_OK_LOADED;
+    ImageTile *tile = BKE_image_get_tile(ima, 0);
+    tile->ok = IMA_OK_LOADED;
   }
 
   return ima;
@@ -802,7 +913,7 @@ bool BKE_image_memorypack(Image *ima)
     int i;
 
     for (i = 0, iv = ima->views.first; iv; iv = iv->next, i++) {
-      ImBuf *ibuf = image_get_cached_ibuf_for_index_frame(ima, i, 0);
+      ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, i, 0);
 
       if (!ibuf) {
         ok = false;
@@ -822,7 +933,7 @@ bool BKE_image_memorypack(Image *ima)
     ima->views_format = R_IMF_VIEWS_INDIVIDUAL;
   }
   else {
-    ImBuf *ibuf = image_get_cached_ibuf_for_index_frame(ima, IMA_NO_INDEX, 0);
+    ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
 
     if (ibuf) {
       ok = ok && image_memorypack_imbuf(ima, ibuf, ibuf->name);
@@ -1009,7 +1120,7 @@ static bool imagecache_check_free_anim(ImBuf *ibuf, void *UNUSED(userkey), void
 {
   int except_frame = *(int *)userdata;
   return (ibuf->userflags & IB_BITMAPDIRTY) == 0 && (ibuf->index != IMA_NO_INDEX) &&
-         (except_frame != IMA_INDEX_FRAME(ibuf->index));
+         (except_frame != IMA_INDEX_ENTRY(ibuf->index));
 }
 
 /* except_frame is weak, only works for seqs without offset... */
@@ -3162,7 +3273,7 @@ static void image_tag_reload(Image *ima, ID *iuser_id, ImageUser *iuser, void *c
 void BKE_imageuser_default(ImageUser *iuser)
 {
   memset(iuser, 0, sizeof(ImageUser));
-  iuser->ok = true;
+  iuser->ok = 1;
   iuser->frames = 100;
   iuser->sfra = 1;
 }
@@ -3179,6 +3290,26 @@ void BKE_image_init_imageuser(Image *ima, ImageUser *iuser)
   }
 }
 
+static void image_free_tile(Image *ima, ImageTile *tile)
+{
+  for (int i = 0; i < TEXTARGET_COUNT; i++) {
+    if (tile->gputexture[i] != NULL) {
+      GPU_texture_free(tile->gputexture[i]);
+      tile->gputexture[i] = NULL;
+    }
+  }
+
+  if (BKE_image_is_multiview(ima)) {
+    const int totviews = BLI_listbase_count(&ima->views);
+    for (int i = 0; i < totviews; i++) {
+      image_remove_ibuf(ima, i, tile->tile_number);
+    }
+  }
+  else {
+    image_remove_ibuf(ima, 0, tile->tile_number);
+  }
+}
+
 void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
 {
   if (ima == NULL) {
@@ -3207,7 +3338,7 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
 
       if (ima->source == IMA_SRC_GENERATED) {
         if (ima->gen_x == 0 || ima->gen_y == 0) {
-          ImBuf *ibuf = image_get_cached_ibuf_for_index_frame(ima, IMA_NO_INDEX, 0);
+          ImBuf *ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
           if (ibuf) {
             ima->gen_x = ibuf->x;
             ima->gen_y = ibuf->y;
@@ -3225,6 +3356,17 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
         ima->name[0] = '\0';
       }
 
+      if (ima->source != IMA_SRC_TILED) {
+        /* Free all but the first tile. */
+        ImageTile *base_tile = BKE_image_get_tile(ima, 0);
+        for (ImageTile *tile = base_tile->next; tile; tile = tile->next) {
+          image_free_tile(ima, tile);
+          MEM_freeN(tile);
+        }
+        base_tile->next = NULL;
+        ima->tiles.last = base_tile;
+      }
+
       /* image buffers for non-sequence multilayer will share buffers with RenderResult,
        * however sequence multilayer will own buffers. Such logic makes switching from
        * single multilayer file to sequence completely unstable
@@ -3234,7 +3376,10 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
        */
       BKE_image_free_buffers(ima);
 
-      ima->ok = 1;
+      LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+        tile->ok = 1;
+      }
+
       if (iuser) {
         image_tag_frame_recalc(ima, NULL, iuser, ima);
       }
@@ -3283,7 +3428,7 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
     case IMA_SIGNAL_USER_NEW_IMAGE:
       if (iuser) {
         iuser->ok = 1;
-        if (ima->source == IMA_SRC_FILE || ima->source == IMA_SRC_SEQUENCE) {
+        if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) {
           if (ima->type == IMA_TYPE_MULTILAYER) {
             BKE_image_init_imageuser(ima, iuser);
           }
@@ -3293,7 +3438,9 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal)
     case IMA_SIGNAL_COLORMANAGE:
       BKE_image_free_buffers(ima);
 
-      ima->ok = 1;
+      LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+        tile->ok = 1;
+      }
 
       if (iuser) {
         iuser->ok = 1;
@@ -3360,6 +3507,107 @@ static RenderPass *image_render_pass_get(RenderLayer *rl,
   return rpass_ret;
 }
 
+void BKE_image_get_tile_label(Image *ima, ImageTile *tile, char *label, int len_label)
+{
+  label[0] = '\0';
+  if (ima == NULL || tile == NULL) {
+    return;
+  }
+
+  if (tile->label[0]) {
+    BLI_strncpy(label, tile->label, len_label);
+  }
+  else {
+    BLI_snprintf(label, len_label, "%d", tile->tile_number);
+  }
+}
+
+ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label)
+{
+  if (ima->source != IMA_SRC_TILED) {
+    return NULL;
+  }
+
+  if (tile_number < 1001 || tile_number > IMA_UDIM_MAX) {
+    return NULL;
+  }
+
+  /* Search the first tile that has a higher number.
+   * We then insert before that to keep the list sorted. */
+  ImageTile *next_tile;
+  for (next_tile = ima->tiles.first; next_tile; next_tile = next_tile->next) {
+    if (next_tile->tile_number == tile_number) {
+      /* Tile already exists. */
+      return NULL;
+    }
+    if (next_tile->tile_number > tile_number) {
+      break;
+    }
+  }
+
+  ImageTile *tile = MEM_callocN(sizeof(ImageTile), "image new tile");
+  tile->ok = 1;
+  tile->tile_number = tile_number;
+
+  if (next_tile) {
+    BLI_insertlinkbefore(&ima->tiles, next_tile, tile);
+  }
+  else {
+    BLI_addtail(&ima->tiles, tile);
+  }
+
+  if (label) {
+    BLI_strncpy(tile->label, label, sizeof(tile->label));
+  }
+
+  return tile;
+}
+
+bool BKE_image_remove_tile(struct Image *ima, ImageTile *tile)
+{
+  if (ima == NULL || tile == NULL || ima->source != IMA_SRC_TILED) {
+    return false;
+  }
+
+  if (tile == ima->tiles.first) {
+    /* Can't remove first tile. */
+    return false;
+  }
+
+  image_free_tile(ima, tile);
+  BLI_remlink(&ima->tiles, tile);
+  MEM_freeN(tile);
+
+  return true;
+}
+
+bool BKE_image_fill_tile(struct Image *ima,
+                         ImageTile *tile,
+                         int width,
+                         int height,
+                         const float color[4],
+                         int gen_type,
+                         int planes,
+                         bool is_float)
+{
+  if (ima == NULL || tile == NULL || ima->source != IMA_SRC_TILED) {
+    return false;
+  }
+
+  image_free_tile(ima, tile);
+
+  ImBuf *tile_ibuf = add_ibuf_size(
+      width, height, ima->name, planes, is_float, gen_type, color, &ima->colorspace_settings);
+
+  if (tile_ibuf != NULL) {
+    image_assign_ibuf(ima, tile_ibuf, 0, tile->tile_number);
+    BKE_image_release_ibuf(ima, tile_ibuf, NULL);
+    tile->ok = 1;
+    return true;
+  }
+  return false;
+}
+
 /* if layer or pass changes, we need an index for the imbufs list */
 /* note it is called for rendered results, but it doesn't use the index! */
 /* and because rendered results use fake layer/passes, don't correct for wrong indices here */
@@ -3421,7 +3669,7 @@ void BKE_image_multiview_index(Image *ima, ImageUser *iuser)
 /* and because rendered results use fake layer/passes, don't correct for wrong indices here */
 bool BKE_image_is_multilayer(Image *ima)
 {
-  if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE)) {
+  if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) {
     if (ima->type == IMA_TYPE_MULTILAYER) {
       return true;
     }
@@ -3512,7 +3760,7 @@ void BKE_image_release_renderresult(Scene *scene, Image *ima)
 bool BKE_image_is_openexr(struct Image *ima)
 {
 #ifdef WITH_OPENEXR
-  if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE)) {
+  if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) {
     return BLI_path_extension_check(ima->name, ".exr");
   }
 #else
@@ -3617,7 +3865,7 @@ static void image_create_multilayer(Image *ima, ImBuf *ibuf, int framenr)
 #endif /* WITH_OPENEXR */
 
 /* common stuff to do with images after loading */
-static void image_initialize_after_load(Image *ima, ImBuf *UNUSED(ibuf))
+static void image_initialize_after_load(Image *ima, ImageUser *iuser, ImBuf *UNUSED(ibuf))
 {
   /* Preview is NULL when it has never been used as an icon before.
    * Never handle previews/icons outside of main thread. */
@@ -3628,7 +3876,8 @@ static void image_initialize_after_load(Image *ima, ImBuf *UNUSED(ibuf))
   /* timer */
   BKE_image_tag_time(ima);
 
-  ima->ok = IMA_OK_LOADED;
+  ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+  tile->ok = IMA_OK_LOADED;
 }
 
 static int imbuf_alpha_flags_for_image(Image *ima)
@@ -3712,19 +3961,25 @@ static ImBuf *load_sequence_single(
       }
     }
     else {
-      image_initialize_after_load(ima, ibuf);
+      image_initialize_after_load(ima, iuser, ibuf);
       *r_assign = true;
     }
 #else
-    image_initialize_after_load(ima, ibuf);
+    image_initialize_after_load(ima, iuser, ibuf);
     *r_assign = true;
 #endif
   }
+  else {
+    ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+    if (tile != NULL) {
+      tile->ok = 0;
+    }
+  }
 
   return ibuf;
 }
 
-static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int frame)
+static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry, int frame)
 {
   struct ImBuf *ibuf = NULL;
   const bool is_multiview = BKE_image_is_multiview(ima);
@@ -3734,7 +3989,7 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int frame)
   if (!is_multiview) {
     ibuf = load_sequence_single(ima, iuser, frame, 0, &assign);
     if (assign) {
-      image_assign_ibuf(ima, ibuf, 0, frame);
+      image_assign_ibuf(ima, ibuf, 0, entry);
     }
   }
   else {
@@ -3757,7 +4012,7 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int frame)
 
     if (assign) {
       for (i = 0; i < totviews; i++) {
-        image_assign_ibuf(ima, ibuf_arr[i], i, frame);
+        image_assign_ibuf(ima, ibuf_arr[i], i, entry);
       }
     }
 
@@ -3775,9 +4030,10 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int frame)
   return ibuf;
 }
 
-static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int frame)
+static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int entry, int frame)
 {
   struct ImBuf *ibuf = NULL;
+  ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
 
   /* either we load from RenderResult, or we have to load a new one */
 
@@ -3793,7 +4049,7 @@ static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int f
       ima->rr = NULL;
     }
 
-    ibuf = image_load_sequence_file(ima, iuser, frame);
+    ibuf = image_load_sequence_file(ima, iuser, entry, frame);
 
     if (ibuf) { /* actually an error */
       ima->type = IMA_TYPE_IMAGE;
@@ -3814,17 +4070,17 @@ static ImBuf *image_load_sequence_multilayer(Image *ima, ImageUser *iuser, int f
 
       BKE_imbuf_stamp_info(ima->rr, ibuf);
 
-      image_initialize_after_load(ima, ibuf);
-      image_assign_ibuf(ima, ibuf, iuser ? iuser->multi_index : 0, frame);
+      image_initialize_after_load(ima, iuser, ibuf);
+      image_assign_ibuf(ima, ibuf, iuser ? iuser->multi_index : 0, entry);
     }
     // else printf("pass not found\n");
   }
   else {
-    ima->ok = 0;
+    tile->ok = 0;
   }
 
   if (iuser) {
-    iuser->ok = ima->ok;
+    iuser->ok = tile->ok;
   }
 
   return ibuf;
@@ -3837,6 +4093,8 @@ static ImBuf *load_movie_single(Image *ima, ImageUser *iuser, int frame, const i
 
   ia = BLI_findlink(&ima->anims, view_id);
 
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
+
   if (ia->anim == NULL) {
     char str[FILE_MAX];
     int flags = IB_rect;
@@ -3876,14 +4134,14 @@ static ImBuf *load_movie_single(Image *ima, ImageUser *iuser, int frame, const i
     ibuf = IMB_makeSingleUser(IMB_anim_absolute(ia->anim, fra, IMB_TC_RECORD_RUN, IMB_PROXY_NONE));
 
     if (ibuf) {
-      image_initialize_after_load(ima, ibuf);
+      image_initialize_after_load(ima, iuser, ibuf);
     }
     else {
-      ima->ok = 0;
+      tile->ok = 0;
     }
   }
   else {
-    ima->ok = 0;
+    tile->ok = 0;
   }
 
   return ibuf;
@@ -3894,6 +4152,7 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
   struct ImBuf *ibuf = NULL;
   const bool is_multiview = BKE_image_is_multiview(ima);
   const int totfiles = image_num_files(ima);
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
   int i;
 
   if (totfiles != BLI_listbase_count_at_most(&ima->anims, totfiles + 1)) {
@@ -3929,7 +4188,7 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
         image_assign_ibuf(ima, ibuf_arr[i], i, frame);
       }
       else {
-        ima->ok = 0;
+        tile->ok = 0;
       }
     }
 
@@ -3948,7 +4207,7 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame)
   }
 
   if (iuser) {
-    iuser->ok = ima->ok;
+    iuser->ok = tile->ok;
   }
 
   return ibuf;
@@ -4020,7 +4279,7 @@ static ImBuf *load_image_single(Image *ima,
     else
 #endif
     {
-      image_initialize_after_load(ima, ibuf);
+      image_initialize_after_load(ima, iuser, ibuf);
       *r_assign = true;
 
       /* make packed file for autopack */
@@ -4035,7 +4294,8 @@ static ImBuf *load_image_single(Image *ima,
     }
   }
   else {
-    ima->ok = 0;
+    ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+    tile->ok = 0;
   }
 
   return ibuf;
@@ -4109,7 +4369,8 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra)
   }
 
   if (iuser) {
-    iuser->ok = ima->ok;
+    ImageTile *tile = BKE_image_get_tile(ima, 0);
+    iuser->ok = tile->ok;
   }
 
   return ibuf;
@@ -4132,7 +4393,7 @@ static ImBuf *image_get_ibuf_multilayer(Image *ima, ImageUser *iuser)
     if (rpass) {
       ibuf = IMB_allocImBuf(ima->rr->rectx, ima->rr->recty, 32, 0);
 
-      image_initialize_after_load(ima, ibuf);
+      image_initialize_after_load(ima, iuser, ibuf);
 
       ibuf->rect_float = rpass->rect;
       ibuf->flags |= IB_rectfloat;
@@ -4144,11 +4405,12 @@ static ImBuf *image_get_ibuf_multilayer(Image *ima, ImageUser *iuser)
     }
   }
 
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
   if (ibuf == NULL) {
-    ima->ok = 0;
+    tile->ok = 0;
   }
   if (iuser) {
-    iuser->ok = ima->ok;
+    iuser->ok = tile->ok;
   }
 
   return ibuf;
@@ -4268,7 +4530,7 @@ static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_loc
     }
   }
 
-  ibuf = image_get_cached_ibuf_for_index_frame(ima, IMA_NO_INDEX, 0);
+  ibuf = image_get_cached_ibuf_for_index_entry(ima, IMA_NO_INDEX, 0);
 
   /* make ibuf if needed, and initialize it */
   if (ibuf == NULL) {
@@ -4345,7 +4607,8 @@ static ImBuf *image_get_render_result(Image *ima, ImageUser *iuser, void **r_loc
 
   ibuf->dither = dither;
 
-  ima->ok = IMA_OK_LOADED;
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
+  tile->ok = IMA_OK_LOADED;
 
   return ibuf;
 }
@@ -4355,7 +4618,7 @@ static int image_get_multiview_index(Image *ima, ImageUser *iuser)
   const bool is_multilayer = BKE_image_is_multilayer(ima);
   const bool is_backdrop = (ima->source == IMA_SRC_VIEWER) && (ima->type == IMA_TYPE_COMPOSITE) &&
                            (iuser == NULL);
-  int index = BKE_image_is_animated(ima) ? 0 : IMA_NO_INDEX;
+  int index = BKE_image_has_multiple_ibufs(ima) ? 0 : IMA_NO_INDEX;
 
   if (is_multilayer) {
     return iuser ? iuser->multi_index : index;
@@ -4373,7 +4636,7 @@ static int image_get_multiview_index(Image *ima, ImageUser *iuser)
   return index;
 }
 
-static void image_get_frame_and_index(Image *ima, ImageUser *iuser, int *r_frame, int *r_index)
+static void image_get_entry_and_index(Image *ima, ImageUser *iuser, int *r_entry, int *r_index)
 {
   int frame = 0, index = image_get_multiview_index(ima, iuser);
 
@@ -4390,7 +4653,7 @@ static void image_get_frame_and_index(Image *ima, ImageUser *iuser, int *r_frame
     }
   }
 
-  *r_frame = frame;
+  *r_entry = frame;
   *r_index = index;
 }
 
@@ -4400,58 +4663,75 @@ static void image_get_frame_and_index(Image *ima, ImageUser *iuser, int *r_frame
  * call IMB_freeImBuf to de-reference the image buffer after
  * it's done handling it.
  */
-static ImBuf *image_get_cached_ibuf(Image *ima, ImageUser *iuser, int *r_frame, int *r_index)
+static ImBuf *image_get_cached_ibuf(Image *ima, ImageUser *iuser, int *r_entry, int *r_index)
 {
   ImBuf *ibuf = NULL;
-  int frame = 0, index = image_get_multiview_index(ima, iuser);
+  int entry = 0, index = image_get_multiview_index(ima, iuser);
 
   /* see if we already have an appropriate ibuf, with image source and type */
   if (ima->source == IMA_SRC_MOVIE) {
-    frame = iuser ? iuser->framenr : ima->lastframe;
-    ibuf = image_get_cached_ibuf_for_index_frame(ima, index, frame);
-    ima->lastframe = frame;
+    entry = iuser ? iuser->framenr : ima->lastframe;
+    ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+    ima->lastframe = entry;
   }
   else if (ima->source == IMA_SRC_SEQUENCE) {
     if (ima->type == IMA_TYPE_IMAGE) {
-      frame = iuser ? iuser->framenr : ima->lastframe;
-      ibuf = image_get_cached_ibuf_for_index_frame(ima, index, frame);
-      ima->lastframe = frame;
+      entry = iuser ? iuser->framenr : ima->lastframe;
+      ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+      ima->lastframe = entry;
 
       /* counter the fact that image is set as invalid when loading a frame
        * that is not in the cache (through image_acquire_ibuf for instance),
        * yet we have valid frames in the cache loaded */
       if (ibuf) {
-        ima->ok = IMA_OK_LOADED;
+        ImageTile *tile = BKE_image_get_tile(ima, 0);
+        tile->ok = IMA_OK_LOADED;
 
         if (iuser) {
-          iuser->ok = ima->ok;
+          iuser->ok = tile->ok;
         }
       }
     }
     else if (ima->type == IMA_TYPE_MULTILAYER) {
-      frame = iuser ? iuser->framenr : ima->lastframe;
-      ibuf = image_get_cached_ibuf_for_index_frame(ima, index, frame);
+      entry = iuser ? iuser->framenr : ima->lastframe;
+      ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
     }
   }
   else if (ima->source == IMA_SRC_FILE) {
     if (ima->type == IMA_TYPE_IMAGE) {
-      ibuf = image_get_cached_ibuf_for_index_frame(ima, index, 0);
+      ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
     }
     else if (ima->type == IMA_TYPE_MULTILAYER) {
-      ibuf = image_get_cached_ibuf_for_index_frame(ima, index, 0);
+      ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
     }
   }
   else if (ima->source == IMA_SRC_GENERATED) {
-    ibuf = image_get_cached_ibuf_for_index_frame(ima, index, 0);
+    ibuf = image_get_cached_ibuf_for_index_entry(ima, index, 0);
   }
   else if (ima->source == IMA_SRC_VIEWER) {
     /* always verify entirely, not that this shouldn't happen
      * as part of texture sampling in rendering anyway, so not
      * a big bottleneck */
   }
+  else if (ima->source == IMA_SRC_TILED) {
+    if (ELEM(ima->type, IMA_TYPE_IMAGE, IMA_TYPE_MULTILAYER)) {
+      entry = (iuser && iuser->tile) ? iuser->tile : 1001;
+      ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
+
+      if ((ima->type == IMA_TYPE_IMAGE) && ibuf != NULL) {
+        ImageTile *tile = BKE_image_get_tile(ima, entry);
+        tile->ok = IMA_OK_LOADED;
+
+        /* iuser->ok is useless for tiled images because iuser->tile changes all the time. */
+        if (iuser != NULL) {
+          iuser->ok = 1;
+        }
+      }
+    }
+  }
 
-  if (r_frame) {
-    *r_frame = frame;
+  if (r_entry) {
+    *r_entry = entry;
   }
 
   if (r_index) {
@@ -4467,12 +4747,17 @@ BLI_INLINE bool image_quick_test(Image *ima, ImageUser *iuser)
     return false;
   }
 
+  ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+
   if (iuser) {
     if (iuser->ok == 0) {
       return false;
     }
   }
-  else if (ima->ok == 0) {
+  else if (tile == NULL) {
+    return false;
+  }
+  else if (tile->ok == 0) {
     return false;
   }
 
@@ -4486,7 +4771,7 @@ BLI_INLINE bool image_quick_test(Image *ima, ImageUser *iuser)
 static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
 {
   ImBuf *ibuf = NULL;
-  int frame = 0, index = 0;
+  int entry = 0, index = 0;
 
   if (r_lock) {
     *r_lock = NULL;
@@ -4497,29 +4782,40 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
     return NULL;
   }
 
-  ibuf = image_get_cached_ibuf(ima, iuser, &frame, &index);
+  ibuf = image_get_cached_ibuf(ima, iuser, &entry, &index);
 
   if (ibuf == NULL) {
     /* we are sure we have to load the ibuf, using source and type */
     if (ima->source == IMA_SRC_MOVIE) {
       /* source is from single file, use flipbook to store ibuf */
-      ibuf = image_load_movie_file(ima, iuser, frame);
+      ibuf = image_load_movie_file(ima, iuser, entry);
     }
     else if (ima->source == IMA_SRC_SEQUENCE) {
       if (ima->type == IMA_TYPE_IMAGE) {
         /* regular files, ibufs in flipbook, allows saving */
-        ibuf = image_load_sequence_file(ima, iuser, frame);
+        ibuf = image_load_sequence_file(ima, iuser, entry, entry);
       }
       /* no else; on load the ima type can change */
       if (ima->type == IMA_TYPE_MULTILAYER) {
         /* only 1 layer/pass stored in imbufs, no exrhandle anim storage, no saving */
-        ibuf = image_load_sequence_multilayer(ima, iuser, frame);
+        ibuf = image_load_sequence_multilayer(ima, iuser, entry, entry);
+      }
+    }
+    else if (ima->source == IMA_SRC_TILED) {
+      if (ima->type == IMA_TYPE_IMAGE) {
+        /* regular files, ibufs in flipbook, allows saving */
+        ibuf = image_load_sequence_file(ima, iuser, entry, 0);
+      }
+      /* no else; on load the ima type can change */
+      if (ima->type == IMA_TYPE_MULTILAYER) {
+        /* only 1 layer/pass stored in imbufs, no exrhandle anim storage, no saving */
+        ibuf = image_load_sequence_multilayer(ima, iuser, entry, 0);
       }
     }
     else if (ima->source == IMA_SRC_FILE) {
 
       if (ima->type == IMA_TYPE_IMAGE) {
-        ibuf = image_load_image_file(ima, iuser, frame); /* cfra only for '#', this global is OK */
+        ibuf = image_load_image_file(ima, iuser, entry); /* cfra only for '#', this global is OK */
       }
       /* no else; on load the ima type can change */
       if (ima->type == IMA_TYPE_MULTILAYER) {
@@ -4548,7 +4844,8 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
                            ima->gen_color,
                            &ima->colorspace_settings);
       image_assign_ibuf(ima, ibuf, index, 0);
-      ima->ok = IMA_OK_LOADED;
+      ImageTile *tile = BKE_image_get_tile(ima, 0);
+      tile->ok = IMA_OK_LOADED;
     }
     else if (ima->source == IMA_SRC_VIEWER) {
       if (ima->type == IMA_TYPE_R_RESULT) {
@@ -4564,14 +4861,14 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
           *r_lock = ima;
 
           /* XXX anim play for viewer nodes not yet supported */
-          frame = 0;  // XXX iuser ? iuser->framenr : 0;
-          ibuf = image_get_cached_ibuf_for_index_frame(ima, index, frame);
+          entry = 0;  // XXX iuser ? iuser->framenr : 0;
+          ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry);
 
           if (!ibuf) {
             /* Composite Viewer, all handled in compositor */
             /* fake ibuf, will be filled in compositor */
             ibuf = IMB_allocImBuf(256, 256, 32, IB_rect | IB_rectfloat);
-            image_assign_ibuf(ima, ibuf, index, frame);
+            image_assign_ibuf(ima, ibuf, index, entry);
           }
         }
       }
@@ -4655,13 +4952,13 @@ bool BKE_image_has_ibuf(Image *ima, ImageUser *iuser)
 
 /* ******** Pool for image buffers ********  */
 
-typedef struct ImagePoolEntry {
-  struct ImagePoolEntry *next, *prev;
+typedef struct ImagePoolItem {
+  struct ImagePoolItem *next, *prev;
   Image *image;
   ImBuf *ibuf;
   int index;
-  int frame;
-} ImagePoolEntry;
+  int entry;
+} ImagePoolItem;
 
 typedef struct ImagePool {
   ListBase image_buffers;
@@ -4671,7 +4968,7 @@ typedef struct ImagePool {
 ImagePool *BKE_image_pool_new(void)
 {
   ImagePool *pool = MEM_callocN(sizeof(ImagePool), "Image Pool");
-  pool->memory_pool = BLI_mempool_create(sizeof(ImagePoolEntry), 0, 128, BLI_MEMPOOL_NOP);
+  pool->memory_pool = BLI_mempool_create(sizeof(ImagePoolItem), 0, 128, BLI_MEMPOOL_NOP);
 
   return pool;
 }
@@ -4680,9 +4977,9 @@ void BKE_image_pool_free(ImagePool *pool)
 {
   /* Use single lock to dereference all the image buffers. */
   BLI_mutex_lock(image_mutex);
-  for (ImagePoolEntry *entry = pool->image_buffers.first; entry != NULL; entry = entry->next) {
-    if (entry->ibuf) {
-      IMB_freeImBuf(entry->ibuf);
+  for (ImagePoolItem *item = pool->image_buffers.first; item != NULL; item = item->next) {
+    if (item->ibuf != NULL) {
+      IMB_freeImBuf(item->ibuf);
     }
   }
   BLI_mutex_unlock(image_mutex);
@@ -4691,17 +4988,17 @@ void BKE_image_pool_free(ImagePool *pool)
   MEM_freeN(pool);
 }
 
-BLI_INLINE ImBuf *image_pool_find_entry(
-    ImagePool *pool, Image *image, int frame, int index, bool *found)
+BLI_INLINE ImBuf *image_pool_find_item(
+    ImagePool *pool, Image *image, int entry, int index, bool *found)
 {
-  ImagePoolEntry *entry;
+  ImagePoolItem *item;
 
   *found = false;
 
-  for (entry = pool->image_buffers.first; entry; entry = entry->next) {
-    if (entry->image == image && entry->frame == frame && entry->index == index) {
+  for (item = pool->image_buffers.first; item; item = item->next) {
+    if (item->image == image && item->entry == entry && item->index == index) {
       *found = true;
-      return entry->ibuf;
+      return item->ibuf;
     }
   }
 
@@ -4711,7 +5008,7 @@ BLI_INLINE ImBuf *image_pool_find_entry(
 ImBuf *BKE_image_pool_acquire_ibuf(Image *ima, ImageUser *iuser, ImagePool *pool)
 {
   ImBuf *ibuf;
-  int index, frame;
+  int index, entry;
   bool found;
 
   if (!image_quick_test(ima, iuser)) {
@@ -4723,32 +5020,32 @@ ImBuf *BKE_image_pool_acquire_ibuf(Image *ima, ImageUser *iuser, ImagePool *pool
     return BKE_image_acquire_ibuf(ima, iuser, NULL);
   }
 
-  image_get_frame_and_index(ima, iuser, &frame, &index);
+  image_get_entry_and_index(ima, iuser, &entry, &index);
 
-  ibuf = image_pool_find_entry(pool, ima, frame, index, &found);
+  ibuf = image_pool_find_item(pool, ima, entry, index, &found);
   if (found) {
     return ibuf;
   }
 
   BLI_mutex_lock(image_mutex);
 
-  ibuf = image_pool_find_entry(pool, ima, frame, index, &found);
+  ibuf = image_pool_find_item(pool, ima, entry, index, &found);
 
-  /* will also create entry even in cases image buffer failed to load,
+  /* will also create item even in cases image buffer failed to load,
    * prevents trying to load the same buggy file multiple times
    */
   if (!found) {
-    ImagePoolEntry *entry;
+    ImagePoolItem *item;
 
     ibuf = image_acquire_ibuf(ima, iuser, NULL);
 
-    entry = BLI_mempool_alloc(pool->memory_pool);
-    entry->image = ima;
-    entry->frame = frame;
-    entry->index = index;
-    entry->ibuf = ibuf;
+    item = BLI_mempool_alloc(pool->memory_pool);
+    item->image = ima;
+    item->entry = entry;
+    item->index = index;
+    item->ibuf = ibuf;
 
-    BLI_addtail(&pool->image_buffers, entry);
+    BLI_addtail(&pool->image_buffers, item);
   }
 
   BLI_mutex_unlock(image_mutex);
@@ -4946,13 +5243,20 @@ void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath)
     BLI_strncpy(filepath, ima->name, FILE_MAX);
   }
 
-  if (ima->source == IMA_SRC_SEQUENCE) {
+  if (ELEM(ima->source, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) {
     char head[FILE_MAX], tail[FILE_MAX];
     unsigned short numlen;
-    int frame = iuser ? iuser->framenr : ima->lastframe;
+
+    int index;
+    if (ima->source == IMA_SRC_SEQUENCE) {
+      index = iuser ? iuser->framenr : ima->lastframe;
+    }
+    else {
+      index = (iuser && iuser->tile) ? iuser->tile : 1001;
+    }
 
     BLI_stringdec(filepath, head, tail, &numlen);
-    BLI_stringenc(filepath, head, tail, numlen, frame);
+    BLI_stringenc(filepath, head, tail, numlen, index);
   }
 
   BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id));
@@ -5031,15 +5335,16 @@ void BKE_image_get_aspect(Image *image, float *aspx, float *aspy)
   }
 }
 
-unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame)
+unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame, int tile)
 {
-  ImageUser iuser = {NULL};
+  ImageUser iuser;
+  BKE_imageuser_default(&iuser);
   void *lock;
   ImBuf *ibuf;
   unsigned char *pixels = NULL;
 
   iuser.framenr = frame;
-  iuser.ok = true;
+  iuser.tile = tile;
 
   ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
 
@@ -5060,15 +5365,16 @@ unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame)
   return pixels;
 }
 
-float *BKE_image_get_float_pixels_for_frame(struct Image *image, int frame)
+float *BKE_image_get_float_pixels_for_frame(struct Image *image, int frame, int tile)
 {
-  ImageUser iuser = {NULL};
+  ImageUser iuser;
+  BKE_imageuser_default(&iuser);
   void *lock;
   ImBuf *ibuf;
   float *pixels = NULL;
 
   iuser.framenr = frame;
-  iuser.ok = true;
+  iuser.tile = tile;
 
   ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
 
@@ -5117,6 +5423,12 @@ bool BKE_image_is_animated(Image *image)
   return ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE);
 }
 
+/* Checks whether the image consists of multiple buffers. */
+bool BKE_image_has_multiple_ibufs(Image *image)
+{
+  return ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED);
+}
+
 /* Image modifications */
 bool BKE_image_is_dirty_writable(Image *image, bool *r_is_writable)
 {
@@ -5233,7 +5545,7 @@ ImBuf *BKE_image_get_ibuf_with_name(Image *image, const char *name)
  * References the result, #BKE_image_release_ibuf is to be called to de-reference.
  * Use lock=NULL when calling #BKE_image_release_ibuf().
  *
- * TODO(sergey): This is actually "get first entry from the cache", which is
+ * TODO(sergey): This is actually "get first item from the cache", which is
  *               not so much predictable. But using first loaded image buffer
  *               was also malicious logic and all the areas which uses this
  *               function are to be re-considered.
index cc621e8468c42768283366a99f56172aa5294186..4768957e2c8f8f4627156f2b7ca629185a47a253 100644 (file)
@@ -144,7 +144,7 @@ static void imbuf_save_post(ImBuf *ibuf, ImBuf *colormanaged_ibuf)
  * \note ``ima->name`` and ``ibuf->name`` should end up the same.
  * \note for multiview the first ``ibuf`` is important to get the settings.
  */
-bool BKE_image_save(
+static bool image_save_single(
     ReportList *reports, Main *bmain, Image *ima, ImageUser *iuser, ImageSaveOptions *opts)
 {
   void *lock;
@@ -392,3 +392,55 @@ cleanup:
 
   return ok;
 }
+
+bool BKE_image_save(
+    ReportList *reports, Main *bmain, Image *ima, ImageUser *iuser, ImageSaveOptions *opts)
+{
+  ImageUser save_iuser;
+  BKE_imageuser_default(&save_iuser);
+
+  if (ima->source == IMA_SRC_TILED) {
+    /* Verify filepath for tiles images. */
+    if (BLI_stringdec(opts->filepath, NULL, NULL, NULL) != 1001) {
+      BKE_reportf(reports,
+                  RPT_ERROR,
+                  "When saving a tiled image, the path '%s' must contain the UDIM tag 1001",
+                  opts->filepath);
+      return false;
+    }
+
+    /* For saving a tiled image we need an iuser, so use a local one if there isn't already one. */
+    if (iuser == NULL) {
+      iuser = &save_iuser;
+    }
+  }
+
+  /* Save image - or, for tiled images, the first tile. */
+  bool ok = image_save_single(reports, bmain, ima, iuser, opts);
+
+  if (ok && ima->source == IMA_SRC_TILED) {
+    char filepath[FILE_MAX];
+    BLI_strncpy(filepath, opts->filepath, sizeof(filepath));
+
+    char head[FILE_MAX], tail[FILE_MAX];
+    unsigned short numlen;
+    BLI_stringdec(filepath, head, tail, &numlen);
+
+    /* Save all other tiles. */
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      /* Tile 1001 was already saved before the loop. */
+      if (tile->tile_number == 1001 || !ok) {
+        continue;
+      }
+
+      /* Build filepath of the tile. */
+      BLI_stringenc(opts->filepath, head, tail, numlen, tile->tile_number);
+
+      iuser->tile = tile->tile_number;
+      ok = ok && image_save_single(reports, bmain, ima, iuser, opts);
+    }
+    BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath));
+  }
+
+  return ok;
+}
index 5fa3352d497a09a416e4ead333fae06e37030f0e..d69527e86260cb617473d957e766ddf225f7c5ce 100644 (file)
@@ -242,10 +242,10 @@ void BKE_packedfile_pack_all(Main *bmain, ReportList *reports, bool verbose)
         BKE_image_packfiles(reports, ima, ID_BLEND_PATH(bmain, &ima->id));
         tot++;
       }
-      else if (BKE_image_is_animated(ima) && verbose) {
+      else if (BKE_image_has_multiple_ibufs(ima) && verbose) {
         BKE_reportf(reports,
                     RPT_WARNING,
-                    "Image '%s' skipped, movies and image sequences not supported",
+                    "Image '%s' skipped, movies, image sequences and packed files not supported",
                     ima->id.name + 2);
       }
     }
index 9fbd0227b7e8aab95ddc57441a0f72d1952521c0..b39e979ec4745bdf99b766e6a3c8dd95ace13759 100644 (file)
@@ -323,6 +323,8 @@ MINLINE bool equals_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_
 MINLINE bool equals_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT;
 MINLINE bool equals_v4v4(const float a[4], const float b[4]) ATTR_WARN_UNUSED_RESULT;
 
+MINLINE bool equals_v2v2_int(const int v1[2], const int v2[2]) ATTR_WARN_UNUSED_RESULT;
+
 MINLINE bool compare_v2v2(const float a[2],
                           const float b[2],
                           const float limit) ATTR_WARN_UNUSED_RESULT;
index b4b53a1dd580fc175a15fb2b855241b0254260b5..67bc5c2fa503bd6df877f2cf3434aabc7a7b4e05 100644 (file)
@@ -1220,6 +1220,11 @@ MINLINE bool equals_v4v4(const float v1[4], const float v2[4])
   return ((v1[0] == v2[0]) && (v1[1] == v2[1]) && (v1[2] == v2[2]) && (v1[3] == v2[3]));
 }
 
+MINLINE bool equals_v2v2_int(const int v1[2], const int v2[2])
+{
+  return ((v1[0] == v2[0]) && (v1[1] == v2[1]));
+}
+
 MINLINE bool compare_v2v2(const float v1[2], const float v2[2], const float limit)
 {
   return (compare_ff(v1[0], v2[0], limit) && compare_ff(v1[1], v2[1], limit));
index e7a2390b2c4dca00448616c04291ac87eeee3f2e..2a51a57f887d3c26c9e099331f89793731c4de03 100644 (file)
@@ -1909,9 +1909,11 @@ void blo_make_image_pointer_map(FileData *fd, Main *oldmain)
     if (ima->cache) {
       oldnewmap_insert(fd->imamap, ima->cache, ima->cache, 0);
     }
-    for (a = 0; a < TEXTARGET_COUNT; a++) {
-      if (ima->gputexture[a]) {
-        oldnewmap_insert(fd->imamap, ima->gputexture[a], ima->gputexture[a], 0);
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      for (a = 0; a < TEXTARGET_COUNT; a++) {
+        if (tile->gputexture[a] != NULL) {
+          oldnewmap_insert(fd->imamap, tile->gputexture[a], tile->gputexture[a], 0);
+        }
       }
     }
     if (ima->rr) {
@@ -1955,8 +1957,10 @@ void blo_end_image_pointer_map(FileData *fd, Main *oldmain)
     if (ima->cache == NULL) {
       ima->gpuflag = 0;
       ima->gpuframenr = INT_MAX;
-      for (i = 0; i < TEXTARGET_COUNT; i++) {
-        ima->gputexture[i] = NULL;
+      LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+        for (i = 0; i < TEXTARGET_COUNT; i++) {
+          tile->gputexture[i] = NULL;
+        }
       }
       ima->rr = NULL;
     }
@@ -1964,8 +1968,10 @@ void blo_end_image_pointer_map(FileData *fd, Main *oldmain)
       slot->render = newimaadr(fd, slot->render);
     }
 
-    for (i = 0; i < TEXTARGET_COUNT; i++) {
-      ima->gputexture[i] = newimaadr(fd, ima->gputexture[i]);
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      for (i = 0; i < TEXTARGET_COUNT; i++) {
+        tile->gputexture[i] = newimaadr(fd, tile->gputexture[i]);
+      }
     }
     ima->rr = newimaadr(fd, ima->rr);
   }
@@ -4256,18 +4262,24 @@ static void direct_link_image(FileData *fd, Image *ima)
     ima->cache = NULL;
   }
 
+  link_list(fd, &ima->tiles);
+
   /* if not restored, we keep the binded opengl index */
   if (!ima->cache) {
     ima->gpuflag = 0;
     ima->gpuframenr = INT_MAX;
-    for (int i = 0; i < TEXTARGET_COUNT; i++) {
-      ima->gputexture[i] = NULL;
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      for (int i = 0; i < TEXTARGET_COUNT; i++) {
+        tile->gputexture[i] = NULL;
+      }
     }
     ima->rr = NULL;
   }
   else {
-    for (int i = 0; i < TEXTARGET_COUNT; i++) {
-      ima->gputexture[i] = newimaadr(fd, ima->gputexture[i]);
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      for (int i = 0; i < TEXTARGET_COUNT; i++) {
+        tile->gputexture[i] = newimaadr(fd, tile->gputexture[i]);
+      }
     }
     ima->rr = newimaadr(fd, ima->rr);
   }
@@ -4302,7 +4314,9 @@ static void direct_link_image(FileData *fd, Image *ima)
   BLI_listbase_clear(&ima->anims);
   ima->preview = direct_link_preview_image(fd, ima->preview);
   ima->stereo3d_format = newdataadr(fd, ima->stereo3d_format);
-  ima->ok = 1;
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    tile->ok = 1;
+  }
 }
 
 /** \} */
index 68ea821996f94ff1ec648b329a619f40d1521224..5b063655f75d57a62febf1ea39a99e9e421a339c 100644 (file)
@@ -119,6 +119,20 @@ static bScreen *screen_parent_find(const bScreen *screen)
   return NULL;
 }
 
+static int cmp_image_tile(const void *a, const void *b)
+{
+  const ImageTile *tile_a = a;
+  const ImageTile *tile_b = b;
+
+  if (tile_a->tile_number < tile_b->tile_number) {
+    return -1;
+  }
+  if (tile_a->tile_number > tile_b->tile_number) {
+    return 1;
+  }
+  return 0;
+}
+
 static void do_version_workspaces_create_from_screens(Main *bmain)
 {
   for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
@@ -4292,5 +4306,30 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
         }
       }
     }
+
+    /* Add primary tile to images. */
+    if (!DNA_struct_elem_find(fd->filesdna, "Image", "ListBase", "tiles")) {
+      for (Image *ima = bmain->images.first; ima; ima = ima->id.next) {
+        ImageTile *tile = MEM_callocN(sizeof(ImageTile), "Image Tile");
+        tile->ok = 1;
+        tile->tile_number = 1001;
+        BLI_addtail(&ima->tiles, tile);
+      }
+    }
+
+    /* UDIM Image Editor change. */
+    if (!DNA_struct_elem_find(fd->filesdna, "SpaceImage", "int", "tile_grid_shape[2]")) {
+      for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+        for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
+          for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
+            if (sl->spacetype == SPACE_IMAGE) {
+              SpaceImage *sima = (SpaceImage *)sl;
+              sima->tile_grid_shape[0] = 1;
+              sima->tile_grid_shape[1] = 1;
+            }
+          }
+        }
+      }
+    }
   }
 }
index b3a16b1fb4df4fa302450d010a219ed95277a971..f8ac4e4062c558462353852a4af6ba54129474c8 100644 (file)
@@ -2266,6 +2266,8 @@ static void write_image(WriteData *wd, Image *ima)
     }
     writestruct(wd, DATA, Stereo3dFormat, 1, ima->stereo3d_format);
 
+    writelist(wd, DATA, ImageTile, &ima->tiles);
+
     ima->packedfile = NULL;
 
     writelist(wd, DATA, RenderSlot, &ima->renderslots);
index 3f7619523e37514f6cbfd3daf221f7067ff72622..b6caf52a9f7ef951f9197821889832749e7254a6 100644 (file)
@@ -153,7 +153,8 @@ void ViewerOperation::initImage()
     if (ibuf->x > 0 && ibuf->y > 0) {
       imb_addrectfloatImBuf(ibuf);
     }
-    ima->ok = IMA_OK_LOADED;
+    ImageTile *tile = BKE_image_get_tile(ima, 0);
+    tile->ok = IMA_OK_LOADED;
 
     ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
   }
index 4b3be3ab924c64580a94902f2af586ee12b39c8a..83b764317a926080640b18b9975433704865daf5 100644 (file)
@@ -25,6 +25,7 @@
 #include "BKE_anim.h"
 #include "BKE_curve.h"
 #include "BKE_global.h"
+#include "BKE_image.h"
 #include "BKE_mesh.h"
 #include "BKE_object.h"
 #include "BKE_paint.h"
@@ -1213,9 +1214,17 @@ static DRWShadingGroup *drw_shgroup_material_inputs(DRWShadingGroup *grp,
       GPUTexture *tex = NULL;
 
       if (input->ima) {
+        /* If there's no specified iuser but we need a different tile, create a temporary one. */
+        ImageUser local_iuser;
+        BKE_imageuser_default(&local_iuser);
+        local_iuser.tile = input->image_tile;
+
+        ImageUser *iuser = input->iuser ? input->iuser : &local_iuser;
+        iuser->tile = input->image_tile;
+
         GPUTexture **tex_ref = BLI_memblock_alloc(DST.vmempool->images);
 
-        *tex_ref = tex = GPU_texture_from_blender(input->ima, input->iuser, GL_TEXTURE_2D);
+        *tex_ref = tex = GPU_texture_from_blender(input->ima, iuser, GL_TEXTURE_2D);
 
         GPU_texture_ref(tex);
       }
index 69742af9f50b80f2923b596281d1afaccb28feca..e6d8684a8b27ad9f86c021d0fc134dd3726ef408 100644 (file)
@@ -50,7 +50,7 @@ bool ED_space_image_color_sample(struct SpaceImage *sima,
                                  struct ARegion *ar,
                                  int mval[2],
                                  float r_col[3]);
-struct ImBuf *ED_space_image_acquire_buffer(struct SpaceImage *sima, void **r_lock);
+struct ImBuf *ED_space_image_acquire_buffer(struct SpaceImage *sima, void **r_lock, int tile);
 void ED_space_image_release_buffer(struct SpaceImage *sima, struct ImBuf *ibuf, void *lock);
 bool ED_space_image_has_buffer(struct SpaceImage *sima);
 
index fec4beea809c6ae70762dd8052fcb67049347852..e15e4c45c4ad836909d0607e0e330ad5f5941da5 100644 (file)
@@ -36,15 +36,25 @@ void ED_keymap_paint(struct wmKeyConfig *keyconf);
 
 /* paint_image.c */
 void ED_imapaint_clear_partial_redraw(void);
-void ED_imapaint_dirty_region(
-    struct Image *ima, struct ImBuf *ibuf, int x, int y, int w, int h, bool find_old);
-void ED_imapaint_bucket_fill(struct bContext *C, float color[3], struct wmOperator *op);
+void ED_imapaint_dirty_region(struct Image *ima,
+                              struct ImBuf *ibuf,
+                              int tile_number,
+                              int x,
+                              int y,
+                              int w,
+                              int h,
+                              bool find_old);
+void ED_imapaint_bucket_fill(struct bContext *C,
+                             float color[3],
+                             struct wmOperator *op,
+                             const int mouse[2]);
 
 /* image_undo.c */
 void ED_image_undo_push_begin(const char *name, int paint_mode);
 void ED_image_undo_push_begin_with_image(const char *name,
                                          struct Image *image,
-                                         struct ImBuf *ibuf);
+                                         struct ImBuf *ibuf,
+                                         int tile_number);
 
 void ED_image_undo_push_end(void);
 void ED_image_undo_restore(struct UndoStep *us);
@@ -54,6 +64,7 @@ void ED_image_undosys_type(struct UndoType *ut);
 void *ED_image_paint_tile_find(struct ListBase *undo_tiles,
                                struct Image *ima,
                                struct ImBuf *ibuf,
+                               int tile_number,
                                int x_tile,
                                int y_tile,
                                unsigned short **r_mask,
@@ -62,6 +73,7 @@ void *ED_image_paint_tile_push(struct ListBase *undo_tiles,
                                struct Image *ima,
                                struct ImBuf *ibuf,
                                struct ImBuf **tmpibuf,
+                               int tile_number,
                                int x_tile,
                                int y_tile,
                                unsigned short **r_mask,
index 4ca7c8f96ad0759e4afe3936da28a5ff54736f1a..6a801fc992870a1516e7da2cd820ba41fa92c9de 100644 (file)
@@ -120,7 +120,7 @@ void ED_region_info_draw_multiline(ARegion *ar,
 void ED_region_image_metadata_draw(
     int x, int y, struct ImBuf *ibuf, const rctf *frame, float zoomx, float zoomy);
 void ED_region_image_metadata_panel_draw(struct ImBuf *ibuf, struct uiLayout *layout);
-void ED_region_grid_draw(struct ARegion *ar, float zoomx, float zoomy);
+void ED_region_grid_draw(struct ARegion *ar, float zoomx, float zoomy, float x0, float y0);
 float ED_region_blend_alpha(struct ARegion *ar);
 void ED_region_visible_rect_calc(struct ARegion *ar, struct rcti *rect);
 const rcti *ED_region_visible_rect(ARegion *ar);
index 2a9ebdfaea90c063f269de99e07be64ddea592d1..cc677f8f7cb0d566b497d4df8b57265e94a26f95 100644 (file)
@@ -1708,7 +1708,7 @@ void UI_drop_color_copy(wmDrag *drag, wmDropBox *drop)
   RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected);
 }
 
-static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
   ARegion *ar = CTX_wm_region(C);
   uiBut *but = NULL;
@@ -1751,7 +1751,7 @@ static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(
       srgb_to_linearrgb_v3_v3(color, color);
     }
 
-    ED_imapaint_bucket_fill(C, color, op);
+    ED_imapaint_bucket_fill(C, color, op, event->mval);
   }
 
   ED_region_tag_redraw(ar);
index 9e9cfe1beedb86c44f5323381243bbf7e23bf44c..acecbc47c28d8d6d242db2ee4e4bd97799652df2 100644 (file)
@@ -305,9 +305,12 @@ static void refresh_images(BakeImages *bake_images)
   int i;
   for (i = 0; i < bake_images->size; i++) {
     Image *ima = bake_images->data[i].image;
-    if (ima->ok == IMA_OK_LOADED) {
-      GPU_free_image(ima);
-      DEG_id_tag_update(&ima->id, 0);
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      if (tile->ok == IMA_OK_LOADED) {
+        GPU_free_image(ima);
+        DEG_id_tag_update(&ima->id, 0);
+        break;
+      }
     }
   }
 }
index 1fe299e58a949e96c9170680ad03239b6d42e6bd..0ed37bbc5af43e47cf2819cea4edc3378e19ddd5 100644 (file)
@@ -1118,10 +1118,16 @@ static void icon_preview_startjob(void *customdata, short *stop, short *do_updat
     if (idtype == ID_IM) {
       Image *ima = (Image *)id;
       ImBuf *ibuf = NULL;
-      ImageUser iuser = {NULL};
+      ImageUser iuser;
+      BKE_imageuser_default(&iuser);
 
-      /* ima->ok is zero when Image cannot load */
-      if (ima == NULL || ima->ok == 0) {
+      if (ima == NULL) {
+        return;
+      }
+
+      ImageTile *tile = BKE_image_get_tile(ima, 0);
+      /* tile->ok is zero when Image cannot load */
+      if (tile->ok == 0) {
         return;
       }
 
index 36a2b4c2893a993c117d570ecae35a9585e287cc..e3070903ccccd8fa712d4b9d51d118d8de31a393 100644 (file)
@@ -3212,15 +3212,15 @@ void ED_region_image_metadata_panel_draw(ImBuf *ibuf, uiLayout *layout)
   IMB_metadata_foreach(ibuf, metadata_panel_draw_field, &ctx);
 }
 
-void ED_region_grid_draw(ARegion *ar, float zoomx, float zoomy)
+void ED_region_grid_draw(ARegion *ar, float zoomx, float zoomy, float x0, float y0)
 {
   float gridsize, gridstep = 1.0f / 32.0f;
   float fac, blendfac;
   int x1, y1, x2, y2;
 
-  /* the image is located inside (0, 0), (1, 1) as set by view2d */
-  UI_view2d_view_to_region(&ar->v2d, 0.0f, 0.0f, &x1, &y1);
-  UI_view2d_view_to_region(&ar->v2d, 1.0f, 1.0f, &x2, &y2);
+  /* the image is located inside (x0, y0), (x0+1, y0+1) as set by view2d */
+  UI_view2d_view_to_region(&ar->v2d, x0, y0, &x1, &y1);
+  UI_view2d_view_to_region(&ar->v2d, x0 + 1.0f, y0 + 1.0f, &x2, &y2);
 
   GPUVertFormat *format = immVertexFormat();
   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
index 0b770f17314f6bcaffc06f81cfce693c0aaeea11..de09a52258f96a2849dddefe6bb1a885fa5db4be 100644 (file)
@@ -119,7 +119,8 @@ void imapaint_region_tiles(
   *ty = (y >> ED_IMAGE_UNDO_TILE_BITS);
 }
 
-void ED_imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int h, bool find_old)
+void ED_imapaint_dirty_region(
+    Image *ima, ImBuf *ibuf, int tile_number, int x, int y, int w, int h, bool find_old)
 {
   ImBuf *tmpibuf = NULL;
   int tilex, tiley, tilew, tileh, tx, ty;
@@ -152,7 +153,7 @@ void ED_imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int
   for (ty = tiley; ty <= tileh; ty++) {
     for (tx = tilex; tx <= tilew; tx++) {
       ED_image_paint_tile_push(
-          undo_tiles, ima, ibuf, &tmpibuf, tx, ty, NULL, NULL, false, find_old);
+          undo_tiles, ima, ibuf, &tmpibuf, tile_number, tx, ty, NULL, NULL, false, find_old);
     }
   }
 
@@ -163,7 +164,8 @@ void ED_imapaint_dirty_region(Image *ima, ImBuf *ibuf, int x, int y, int w, int
   }
 }
 
-void imapaint_image_update(SpaceImage *sima, Image *image, ImBuf *ibuf, short texpaint)
+void imapaint_image_update(
+    SpaceImage *sima, Image *image, ImBuf *ibuf, ImageUser *iuser, short texpaint)
 {
   if (imapaintpartial.x1 != imapaintpartial.x2 && imapaintpartial.y1 != imapaintpartial.y2) {
     IMB_partial_display_buffer_update_delayed(
@@ -180,8 +182,7 @@ void imapaint_image_update(SpaceImage *sima, Image *image, ImBuf *ibuf, short te
     int h = imapaintpartial.y2 - imapaintpartial.y1;
     if (w && h) {
       /* Testing with partial update in uv editor too */
-      GPU_paint_update_image(
-          image, (sima ? &sima->iuser : NULL), imapaintpartial.x1, imapaintpartial.y1, w, h);
+      GPU_paint_update_image(image, iuser, imapaintpartial.x1, imapaintpartial.y1, w, h);
     }
   }
 }
@@ -623,7 +624,7 @@ static void paint_stroke_done(const bContext *C, struct PaintStroke *stroke)
         else {
           srgb_to_linearrgb_v3_v3(color, BKE_brush_color_get(scene, brush));
         }
-        paint_2d_bucket_fill(C, color, brush, pop->prevmouse, pop->custom_paint);
+        paint_2d_bucket_fill(C, color, brush, pop->startmouse, pop->prevmouse, pop->custom_paint);
       }
       else {
         paint_proj_stroke(C,
@@ -1297,7 +1298,10 @@ void PAINT_OT_brush_colors_flip(wmOperatorType *ot)
   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
-void ED_imapaint_bucket_fill(struct bContext *C, float color[3], wmOperator *op)
+void ED_imapaint_bucket_fill(struct bContext *C,
+                             float color[3],
+                             wmOperator *op,
+                             const int mouse[2])
 {
   wmWindowManager *wm = CTX_wm_manager(C);
   SpaceImage *sima = CTX_wm_space_image(C);
@@ -1307,7 +1311,8 @@ void ED_imapaint_bucket_fill(struct bContext *C, float color[3], wmOperator *op)
 
   ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D);
 
-  paint_2d_bucket_fill(C, color, NULL, NULL, NULL);
+  float mouse_init[2] = {mouse[0], mouse[1]};
+  paint_2d_bucket_fill(C, color, NULL, mouse_init, NULL, NULL);
 
   BKE_undosys_step_push(wm->undo_stack, C, op->type->name);
 
index 004caae8a0062e0cda36310681a8da4bc7dd6f7c..06d79b8a49da45a7df67494c29c3597d0180ae64 100644 (file)
@@ -30,6 +30,7 @@
 #include "DNA_space_types.h"
 #include "DNA_object_types.h"
 
+#include "BLI_listbase.h"
 #include "BLI_math_color_blend.h"
 #include "BLI_stack.h"
 #include "BLI_bitmap.h"
@@ -84,22 +85,21 @@ typedef struct BrushPainterCache {
   unsigned short *tex_mask_old;
   unsigned int tex_mask_old_w;
   unsigned int tex_mask_old_h;
+
+  int image_size[2];
 } BrushPainterCache;
 
 typedef struct BrushPainter {
   Scene *scene;
   Brush *brush;
 
-  float lastpaintpos[2];  /* position of last paint op */
-  float startpaintpos[2]; /* position of first paint */
-
   short firsttouch; /* first paint op */
 
   struct ImagePool *pool; /* image pool */
   rctf tex_mapping;       /* texture coordinate mapping */
   rctf mask_mapping;      /* mask texture coordinate mapping */
 
-  BrushPainterCache cache;
+  bool cache_invert;
 } BrushPainter;
 
 typedef struct ImagePaintRegion {
@@ -108,6 +108,27 @@ typedef struct ImagePaintRegion {
   int width, height;
 } ImagePaintRegion;
 
+typedef enum ImagePaintTileState {
+  PAINT2D_TILE_UNINITIALIZED = 0,
+  PAINT2D_TILE_MISSING,
+  PAINT2D_TILE_READY,
+} ImagePaintTileState;
+
+typedef struct ImagePaintTile {
+  ImageUser iuser;
+  ImBuf *canvas;
+  float radius_fac;
+  int size[2];
+  float uv_origin[2]; /* Stores the position of this tile in UV space. */
+  bool need_redraw;
+  BrushPainterCache cache;
+
+  ImagePaintTileState state;
+
+  float last_paintpos[2];  /* position of last paint op */
+  float start_paintpos[2]; /* position of first paint */
+} ImagePaintTile;
+
 typedef struct ImagePaintState {
   BrushPainter *painter;
   SpaceImage *sima;
@@ -119,10 +140,7 @@ typedef struct ImagePaintState {
   Brush *brush;
   short tool, blend;
   Image *image;
-  ImBuf *canvas;
   ImBuf *clonecanvas;
-  const char *warnpackedfile;
-  const char *warnmultifile;
 
   bool do_masking;
 
@@ -133,7 +151,8 @@ typedef struct ImagePaintState {
   int do_facesel;
   int symmetry;
 
-  bool need_redraw;
+  ImagePaintTile *tiles;
+  int num_tiles;
 
   BlurKernel *blurkernel;
 } ImagePaintState;
@@ -145,63 +164,60 @@ static BrushPainter *brush_painter_2d_new(Scene *scene, Brush *brush, bool inver
   painter->brush = brush;
   painter->scene = scene;
   painter->firsttouch = 1;
-  painter->cache.lastdiameter = -1; /* force ibuf create in refresh */
-  painter->cache.invert = invert;
+  painter->cache_invert = invert;
 
   return painter;
 }
 
-static void brush_painter_2d_require_imbuf(BrushPainter *painter,
-                                           bool use_float,
-                                           bool use_color_correction)
+static void brush_painter_2d_require_imbuf(
+    Brush *brush, ImagePaintTile *tile, bool use_float, bool use_color_correction, bool invert)
 {
-  Brush *brush = painter->brush;
+  BrushPainterCache *cache = &tile->cache;
 
-  if ((painter->cache.use_float != use_float)) {
-    if (painter->cache.ibuf) {
-      IMB_freeImBuf(painter->cache.ibuf);
+  if ((cache->use_float != use_float)) {
+    if (cache->ibuf) {
+      IMB_freeImBuf(cache->ibuf);
     }
-    if (painter->cache.curve_mask) {
-      MEM_freeN(painter->cache.curve_mask);
+    if (cache->curve_mask) {
+      MEM_freeN(cache->curve_mask);
     }
-    if (painter->cache.tex_mask) {
-      MEM_freeN(painter->cache.tex_mask);
+    if (cache->tex_mask) {
+      MEM_freeN(cache->tex_mask);
     }
-    if (painter->cache.tex_mask_old) {
-      MEM_freeN(painter->cache.tex_mask_old);
+    if (cache->tex_mask_old) {
+      MEM_freeN(cache->tex_mask_old);
     }
-    painter->cache.ibuf = NULL;
-    painter->cache.curve_mask = NULL;
-    painter->cache.tex_mask = NULL;
-    painter->cache.lastdiameter = -1; /* force ibuf create in refresh */
-  }
-
-  painter->cache.use_float = use_float;
-  painter->cache.use_color_correction = use_float && use_color_correction;
-  painter->cache.is_texbrush = (brush->mtex.tex && brush->imagepaint_tool == PAINT_TOOL_DRAW) ?
-                                   true :
-                                   false;
-  painter->cache.is_maskbrush = (brush->mask_mtex.tex) ? true : false;
+    cache->ibuf = NULL;
+    cache->curve_mask = NULL;
+    cache->tex_mask = NULL;
+    cache->lastdiameter = -1; /* force ibuf create in refresh */
+    cache->invert = invert;
+  }
+
+  cache->use_float = use_float;
+  cache->use_color_correction = use_float && use_color_correction;
+  cache->is_texbrush = (brush->mtex.tex && brush->imagepaint_tool == PAINT_TOOL_DRAW) ? true :
+                                                                                        false;
+  cache->is_maskbrush = (brush->mask_mtex.tex) ? true : false;
 }
 
-static void brush_painter_2d_free(BrushPainter *painter)
+static void brush_painter_cache_2d_free(BrushPainterCache *cache)
 {
-  if (painter->cache.ibuf) {
-    IMB_freeImBuf(painter->cache.ibuf);
+  if (cache->ibuf) {
+    IMB_freeImBuf(cache->ibuf);
   }
-  if (painter->cache.texibuf) {
-    IMB_freeImBuf(painter->cache.texibuf);
+  if (cache->texibuf) {
+    IMB_freeImBuf(cache->texibuf);
   }
-  if (painter->cache.curve_mask) {
-    MEM_freeN(painter->cache.curve_mask);
+  if (cache->curve_mask) {
+    MEM_freeN(cache->curve_mask);
   }
-  if (painter->cache.tex_mask) {
-    MEM_freeN(painter->cache.tex_mask);
+  if (cache->tex_mask) {
+    MEM_freeN(cache->tex_mask);
   }
-  if (painter->cache.tex_mask_old) {
-    MEM_freeN(painter->cache.tex_mask_old);
+  if (cache->tex_mask_old) {
+    MEM_freeN(cache->tex_mask_old);
   }
-  MEM_freeN(painter);
 }
 
 static void brush_imbuf_tex_co(rctf *mapping, int x, int y, float texco[3])
@@ -212,7 +228,7 @@ static void brush_imbuf_tex_co(rctf *mapping, int x, int y, float texco[3])
 }
 
 /* create a mask with the mask texture */
-static unsigned short *brush_painter_mask_ibuf_new(BrushPainter *painter, int size)
+static unsigned short *brush_painter_mask_ibuf_new(BrushPainter *painter, const int size)
 {
   Scene *scene = painter->scene;
   Brush *brush = painter->brush;
@@ -240,6 +256,7 @@ static unsigned short *brush_painter_mask_ibuf_new(BrushPainter *painter, int si
 
 /* update rectangular section of the brush image */
 static void brush_painter_mask_imbuf_update(BrushPainter *painter,
+                                            ImagePaintTile *tile,
                                             unsigned short *tex_mask_old,
                                             int origx,
                                             int origy,
@@ -247,10 +264,11 @@ static void brush_painter_mask_imbuf_update(BrushPainter *painter,
                                             int h,
                                             int xt,
                                             int yt,
-                                            int diameter)
+                                            const int diameter)
 {
   Scene *scene = painter->scene;
   Brush *brush = painter->brush;
+  BrushPainterCache *cache = &tile->cache;
   rctf tex_mapping = painter->mask_mapping;
   struct ImagePool *pool = painter->pool;
   unsigned short res;
@@ -259,8 +277,8 @@ static void brush_painter_mask_imbuf_update(BrushPainter *painter,
 
   int x, y, thread = 0;
 
-  unsigned short *tex_mask = painter->cache.tex_mask;
-  unsigned short *tex_mask_cur = painter->cache.tex_mask_old;
+  unsigned short *tex_mask = cache->tex_mask;
+  unsigned short *tex_mask_cur = cache->tex_mask_old;
 
   /* fill pixels */
   for (y = origy; y < h; y++) {
@@ -280,8 +298,7 @@ static void brush_painter_mask_imbuf_update(BrushPainter *painter,
 
       /* read from old texture buffer */
       if (use_texture_old) {
-        res = *(tex_mask_old +
-                ((y - origy + yt) * painter->cache.tex_mask_old_w + (x - origx + xt)));
+        res = *(tex_mask_old + ((y - origy + yt) * cache->tex_mask_old_w + (x - origx + xt)));
       }
 
       /* write to new texture mask */
@@ -298,10 +315,11 @@ static void brush_painter_mask_imbuf_update(BrushPainter *painter,
  * textures that stick to the surface where only part of the pixels are new
  */
 static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter,
+                                                    ImagePaintTile *tile,
                                                     const float pos[2],
-                                                    int diameter)
+                                                    const int diameter)
 {
-  BrushPainterCache *cache = &painter->cache;
+  BrushPainterCache *cache = &tile->cache;
   unsigned short *tex_mask_old;
   int destx, desty, srcx, srcy, w, h, x1, y1, x2, y2;
 
@@ -319,15 +337,16 @@ static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter,
   if (tex_mask_old) {
     ImBuf maskibuf;
     ImBuf maskibuf_old;
-    maskibuf.x = maskibuf.y = diameter;
+    maskibuf.x = diameter;
+    maskibuf.y = diameter;
     maskibuf_old.x = cache->tex_mask_old_w;
     maskibuf_old.y = cache->tex_mask_old_h;
 
     srcx = srcy = 0;
     w = cache->tex_mask_old_w;
     h = cache->tex_mask_old_h;
-    destx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
-    desty = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
+    destx = (int)floorf(tile->last_paintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
+    desty = (int)floorf(tile->last_paintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
 
     /* hack, use temporary rects so that clipping works */
     IMB_rectclip(&maskibuf, &maskibuf_old, &destx, &desty, &srcx, &srcy, &w, &h);
@@ -345,7 +364,8 @@ static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter,
 
   /* blend existing texture in new position */
   if ((x1 < x2) && (y1 < y2)) {
-    brush_painter_mask_imbuf_update(painter, tex_mask_old, x1, y1, x2, y2, srcx, srcy, diameter);
+    brush_painter_mask_imbuf_update(
+        painter, tile, tex_mask_old, x1, y1, x2, y2, srcx, srcy, diameter);
   }
 
   if (tex_mask_old) {
@@ -354,16 +374,17 @@ static void brush_painter_mask_imbuf_partial_update(BrushPainter *painter,
 
   /* sample texture in new areas */
   if ((0 < x1) && (0 < diameter)) {
-    brush_painter_mask_imbuf_update(painter, NULL, 0, 0, x1, diameter, 0, 0, diameter);
+    brush_painter_mask_imbuf_update(painter, tile, NULL, 0, 0, x1, diameter, 0, 0, diameter);
   }
   if ((x2 < diameter) && (0 < diameter)) {
-    brush_painter_mask_imbuf_update(painter, NULL, x2, 0, diameter, diameter, 0, 0, diameter);
+    brush_painter_mask_imbuf_update(
+        painter, tile, NULL, x2, 0, diameter, diameter, 0, 0, diameter);
   }
   if ((x1 < x2) && (0 < y1)) {
-    brush_painter_mask_imbuf_update(painter, NULL, x1, 0, x2, y1, 0, 0, diameter);
+    brush_painter_mask_imbuf_update(painter, tile, NULL, x1, 0, x2, y1, 0, 0, diameter);
   }
   if ((x1 < x2) && (y2 < diameter)) {
-    brush_painter_mask_imbuf_update(painter, NULL, x1, y2, x2, diameter, 0, 0, diameter);
+    brush_painter_mask_imbuf_update(painter, tile, NULL, x1, y2, x2, diameter, 0, 0, diameter);
   }
 
   /* through with sampling, now update sizes */
@@ -445,13 +466,12 @@ static unsigned short *brush_painter_curve_mask_new(BrushPainter *painter,
 }
 
 /* create imbuf with brush color */
-static ImBuf *brush_painter_imbuf_new(BrushPainter *painter,
-                                      int size,
-                                      float pressure,
-                                      float distance)
+static ImBuf *brush_painter_imbuf_new(
+    BrushPainter *painter, ImagePaintTile *tile, const int size, float pressure, float distance)
 {
   Scene *scene = painter->scene;
   Brush *brush = painter->brush;
+  BrushPainterCache *cache = &tile->cache;
 
   const char *display_device = scene->display_settings.display_device;
   struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device);
@@ -459,9 +479,9 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter,
   rctf tex_mapping = painter->tex_mapping;
   struct ImagePool *pool = painter->pool;
 
-  bool use_color_correction = painter->cache.use_color_correction;
-  bool use_float = painter->cache.use_float;
-  bool is_texbrush = painter->cache.is_texbrush;
+  bool use_color_correction = cache->use_color_correction;
+  bool use_float = cache->use_float;
+  bool is_texbrush = cache->is_texbrush;
 
   int x, y, thread = 0;
   float brush_rgb[3];
@@ -471,14 +491,8 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter,
 
   /* get brush color */
   if (brush->imagepaint_tool == PAINT_TOOL_DRAW) {
-    paint_brush_color_get(scene,
-                          brush,
-                          use_color_correction,
-                          painter->cache.invert,
-                          distance,
-                          pressure,
-                          brush_rgb,
-                          display);
+    paint_brush_color_get(
+        scene, brush, use_color_correction, cache->invert, distance, pressure, brush_rgb, display);
   }
   else {
     brush_rgb[0] = 1.0f;
@@ -526,11 +540,19 @@ static ImBuf *brush_painter_imbuf_new(BrushPainter *painter,
 }
 
 /* update rectangular section of the brush image */
-static void brush_painter_imbuf_update(
-    BrushPainter *painter, ImBuf *oldtexibuf, int origx, int origy, int w, int h, int xt, int yt)
+static void brush_painter_imbuf_update(BrushPainter *painter,
+                                       ImagePaintTile *tile,
+                                       ImBuf *oldtexibuf,
+                                       int origx,
+                                       int origy,
+                                       int w,
+                                       int h,
+                                       int xt,
+                                       int yt)
 {
   Scene *scene = painter->scene;
   Brush *brush = painter->brush;
+  BrushPainterCache *cache = &tile->cache;
 
   const char *display_device = scene->display_settings.display_device;
   struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device);
@@ -538,21 +560,21 @@ static void brush_painter_imbuf_update(
   rctf tex_mapping = painter->tex_mapping;
   struct ImagePool *pool = painter->pool;
 
-  bool use_color_correction = painter->cache.use_color_correction;
-  bool use_float = painter->cache.use_float;
-  bool is_texbrush = painter->cache.is_texbrush;
+  bool use_color_correction = cache->use_color_correction;
+  bool use_float = cache->use_float;
+  bool is_texbrush = cache->is_texbrush;
   bool use_texture_old = (oldtexibuf != NULL);
 
   int x, y, thread = 0;
   float brush_rgb[3];
 
-  ImBuf *ibuf = painter->cache.ibuf;
-  ImBuf *texibuf = painter->cache.texibuf;
+  ImBuf *ibuf = cache->ibuf;
+  ImBuf *texibuf = cache->texibuf;
 
   /* get brush color */
   if (brush->imagepaint_tool == PAINT_TOOL_DRAW) {
     paint_brush_color_get(
-        scene, brush, use_color_correction, painter->cache.invert, 0.0, 1.0, brush_rgb, display);
+        scene, brush, use_color_correction, cache->invert, 0.0, 1.0, brush_rgb, display);
   }
   else {
     brush_rgb[0] = 1.0f;
@@ -641,10 +663,11 @@ static void brush_painter_imbuf_update(
  * can be considerably faster for brushes that change size due to pressure or
  * textures that stick to the surface where only part of the pixels are new */
 static void brush_painter_imbuf_partial_update(BrushPainter *painter,
+                                               ImagePaintTile *tile,
                                                const float pos[2],
-                                               int diameter)
+                                               const int diameter)
 {
-  BrushPainterCache *cache = &painter->cache;
+  BrushPainterCache *cache = &tile->cache;
   ImBuf *oldtexibuf, *ibuf;
   int imbflag, destx, desty, srcx, srcy, w, h, x1, y1, x2, y2;
 
@@ -663,8 +686,8 @@ static void brush_painter_imbuf_partial_update(BrushPainter *painter,
     srcx = srcy = 0;
     w = oldtexibuf->x;
     h = oldtexibuf->y;
-    destx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
-    desty = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
+    destx = (int)floorf(tile->last_paintpos[0]) - (int)floorf(pos[0]) + (diameter / 2 - w / 2);
+    desty = (int)floorf(tile->last_paintpos[1]) - (int)floorf(pos[1]) + (diameter / 2 - h / 2);
 
     IMB_rectclip(cache->texibuf, oldtexibuf, &destx, &desty, &srcx, &srcy, &w, &h);
   }
@@ -681,7 +704,7 @@ static void brush_painter_imbuf_partial_update(BrushPainter *painter,
 
   /* blend existing texture in new position */
   if ((x1 < x2) && (y1 < y2)) {
-    brush_painter_imbuf_update(painter, oldtexibuf, x1, y1, x2, y2, srcx, srcy);
+    brush_painter_imbuf_update(painter, tile, oldtexibuf, x1, y1, x2, y2, srcx, srcy);
   }
 
   if (oldtexibuf) {
@@ -690,29 +713,30 @@ static void brush_painter_imbuf_partial_update(BrushPainter *painter,
 
   /* sample texture in new areas */
   if ((0 < x1) && (0 < ibuf->y)) {
-    brush_painter_imbuf_update(painter, NULL, 0, 0, x1, ibuf->y, 0, 0);
+    brush_painter_imbuf_update(painter, tile, NULL, 0, 0, x1, ibuf->y, 0, 0);
   }
   if ((x2 < ibuf->x) && (0 < ibuf->y)) {
-    brush_painter_imbuf_update(painter, NULL, x2, 0, ibuf->x, ibuf->y, 0, 0);
+    brush_painter_imbuf_update(painter, tile, NULL, x2, 0, ibuf->x, ibuf->y, 0, 0);
   }
   if ((x1 < x2) && (0 < y1)) {
-    brush_painter_imbuf_update(painter, NULL, x1, 0, x2, y1, 0, 0);
+    brush_painter_imbuf_update(painter, tile, NULL, x1, 0, x2, y1, 0, 0);
   }
   if ((x1 < x2) && (y2 < ibuf->y)) {
-    brush_painter_imbuf_update(painter, NULL, x1, y2, x2, ibuf->y, 0, 0);
+    brush_painter_imbuf_update(painter, tile, NULL, x1, y2, x2, ibuf->y, 0, 0);
   }
 }
 
 static void brush_painter_2d_tex_mapping(ImagePaintState *s,
-                                         int diameter,
+                                         ImBuf *canvas,
+                                         const int diameter,
                                          const float startpos[2],
                                          const float pos[2],
                                          const float mouse[2],
                                          int mapmode,
                                          rctf *mapping)
 {
-  float invw = 1.0f / (float)s->canvas->x;
-  float invh = 1.0f / (float)s->canvas->y;
+  float invw = 1.0f / (float)canvas->x;
+  float invh = 1.0f / (float)canvas->y;
   int xmin, ymin, xmax, ymax;
   int ipos[2];
 
@@ -756,6 +780,7 @@ static void brush_painter_2d_tex_mapping(ImagePaintState *s,
 
 static void brush_painter_2d_refresh_cache(ImagePaintState *s,
                                            BrushPainter *painter,
+                                           ImagePaintTile *tile,
                                            const float pos[2],
                                            const float mouse[2],
                                            float pressure,
@@ -765,7 +790,7 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   const Scene *scene = painter->scene;
   UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
   Brush *brush = painter->brush;
-  BrushPainterCache *cache = &painter->cache;
+  BrushPainterCache *cache = &tile->cache;
   /* Adding 4 pixels of padding for brush antialiasing */
   const int diameter = MAX2(1, size * 2) + 4;
 
@@ -782,7 +807,7 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   painter->pool = BKE_image_pool_new();
 
   /* determine how can update based on textures used */
-  if (painter->cache.is_texbrush) {
+  if (cache->is_texbrush) {
     if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_VIEW) {
       tex_rotation += ups->brush_rotation;
     }
@@ -794,15 +819,16 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
     }
 
     brush_painter_2d_tex_mapping(s,
+                                 tile->canvas,
                                  diameter,
-                                 painter->startpaintpos,
+                                 tile->start_paintpos,
                                  pos,
                                  mouse,
                                  brush->mtex.brush_map_mode,
                                  &painter->tex_mapping);
   }
 
-  if (painter->cache.is_maskbrush) {
+  if (cache->is_maskbrush) {
     bool renew_maxmask = false;
     bool do_partial_update_mask = false;
     /* invalidate case for all mapping modes */
@@ -822,7 +848,7 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
       renew_maxmask = true;
     }
 
-    if ((diameter != cache->lastdiameter) || (mask_rotation != cache->last_mask_rotation) ||
+    if (diameter != cache->lastdiameter || (mask_rotation != cache->last_mask_rotation) ||
         renew_maxmask) {
       if (cache->tex_mask) {
         MEM_freeN(cache->tex_mask);
@@ -830,15 +856,16 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
       }
 
       brush_painter_2d_tex_mapping(s,
+                                   tile->canvas,
                                    diameter,
-                                   painter->startpaintpos,
+                                   tile->start_paintpos,
                                    pos,
                                    mouse,
                                    brush->mask_mtex.brush_map_mode,
                                    &painter->mask_mapping);
 
       if (do_partial_update_mask) {
-        brush_painter_mask_imbuf_partial_update(painter, pos, diameter);
+        brush_painter_mask_imbuf_partial_update(painter, tile, pos, diameter);
       }
       else {
         cache->tex_mask = brush_painter_mask_ibuf_new(painter, diameter);
@@ -856,8 +883,8 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   cache->curve_mask = brush_painter_curve_mask_new(painter, diameter, size, pos);
 
   /* detect if we need to recreate image brush buffer */
-  if ((diameter != cache->lastdiameter) || (tex_rotation != cache->last_tex_rotation) ||
-      do_random || update_color) {
+  if (diameter != cache->lastdiameter || (tex_rotation != cache->last_tex_rotation) || do_random ||
+      update_color) {
     if (cache->ibuf) {
       IMB_freeImBuf(cache->ibuf);
       cache->ibuf = NULL;
@@ -865,11 +892,11 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
 
     if (do_partial_update) {
       /* do partial update of texture */
-      brush_painter_imbuf_partial_update(painter, pos, diameter);
+      brush_painter_imbuf_partial_update(painter, tile, pos, diameter);
     }
     else {
       /* create brush from scratch */
-      cache->ibuf = brush_painter_imbuf_new(painter, diameter, pressure, distance);
+      cache->ibuf = brush_painter_imbuf_new(painter, tile, diameter, pressure, distance);
     }
 
     cache->lastdiameter = diameter;
@@ -878,11 +905,11 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   }
   else if (do_partial_update) {
     /* do only partial update of texture */
-    int dx = (int)floorf(painter->lastpaintpos[0]) - (int)floorf(pos[0]);
-    int dy = (int)floorf(painter->lastpaintpos[1]) - (int)floorf(pos[1]);
+    int dx = (int)floorf(tile->last_paintpos[0]) - (int)floorf(pos[0]);
+    int dy = (int)floorf(tile->last_paintpos[1]) - (int)floorf(pos[1]);
 
     if ((dx != 0) || (dy != 0)) {
-      brush_painter_imbuf_partial_update(painter, pos, diameter);
+      brush_painter_imbuf_partial_update(painter, tile, pos, diameter);
     }
   }
 
@@ -890,6 +917,56 @@ static void brush_painter_2d_refresh_cache(ImagePaintState *s,
   painter->pool = NULL;
 }
 
+static bool paint_2d_ensure_tile_canvas(ImagePaintState *s, int i)
+{
+  if (i == 0) {
+    return true;
+  }
+  if (i >= s->num_tiles) {
+    return false;
+  }
+
+  if (s->tiles[i].state == PAINT2D_TILE_READY) {
+    return true;
+  }
+  if (s->tiles[i].state == PAINT2D_TILE_MISSING) {
+    return false;
+  }
+
+  s->tiles[i].cache.lastdiameter = -1;
+
+  s->tiles[i].iuser.ok = true;
+
+  ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, &s->tiles[i].iuser, NULL);
+  if (ibuf != NULL) {
+    if (ibuf->channels != 4) {
+      s->tiles[i].state = PAINT2D_TILE_MISSING;
+    }
+    else if ((s->tiles[0].canvas->rect && !ibuf->rect) ||
+             (s->tiles[0].canvas->rect_float && !ibuf->rect_float)) {
+      s->tiles[i].state = PAINT2D_TILE_MISSING;
+    }
+    else {
+      s->tiles[i].size[0] = ibuf->x;
+      s->tiles[i].size[1] = ibuf->y;
+      s->tiles[i].radius_fac = sqrtf(((float)ibuf->x * (float)ibuf->y) /
+                                     (s->tiles[0].size[0] * s->tiles[0].size[1]));
+      s->tiles[i].state = PAINT2D_TILE_READY;
+    }
+  }
+  else {
+    s->tiles[i].state = PAINT2D_TILE_MISSING;
+  }
+
+  if (s->tiles[i].state == PAINT2D_TILE_MISSING) {
+    BKE_image_release_ibuf(s->image, ibuf, NULL);
+    return false;
+  }
+
+  s->tiles[i].canvas = ibuf;
+  return true;
+}
+
 /* keep these functions in sync */
 static void paint_2d_ibuf_rgb_get(ImBuf *ibuf, int x, int y, float r_rgb[4])
 {
@@ -935,15 +1012,15 @@ static void paint_2d_ibuf_rgb_set(
   }
 }
 
-static void paint_2d_ibuf_tile_convert(ImBuf *ibuf, int *x, int *y, short tile)
+static void paint_2d_ibuf_tile_convert(ImBuf *ibuf, int *x, int *y, short paint_tile)
 {
-  if (tile & PAINT_TILE_X) {
+  if (paint_tile & PAINT_TILE_X) {
     *x %= ibuf->x;
     if (*x < 0) {
       *x += ibuf->x;
     }
   }
-  if (tile & PAINT_TILE_Y) {
+  if (paint_tile & PAINT_TILE_Y) {
     *y %= ibuf->y;
     if (*y < 0) {
       *y += ibuf->y;
@@ -951,12 +1028,13 @@ static void paint_2d_ibuf_tile_convert(ImBuf *ibuf, int *x, int *y, short tile)
   }
 }
 
-static float paint_2d_ibuf_add_if(ImBuf *ibuf, int x, int y, float *outrgb, short tile, float w)
+static float paint_2d_ibuf_add_if(
+    ImBuf *ibuf, int x, int y, float *outrgb, short paint_tile, float w)
 {
   float inrgb[4];
 
-  if (tile) {
-    paint_2d_ibuf_tile_convert(ibuf, &x, &y, tile);
+  if (paint_tile) {
+    paint_2d_ibuf_tile_convert(ibuf, &x, &y, paint_tile);
   }
   /* need to also do clipping here always since tiled coordinates
    * are not always within bounds */
@@ -973,10 +1051,14 @@ static float paint_2d_ibuf_add_if(ImBuf *ibuf, int x, int y, float *outrgb, shor
   return w;
 }
 
-static void paint_2d_lift_soften(
-    ImagePaintState *s, ImBuf *ibuf, ImBuf *ibufb, int *pos, const short tile)
+static void paint_2d_lift_soften(ImagePaintState *s,
+                                 ImagePaintTile *tile,
+                                 ImBuf *ibuf,
+                                 ImBuf *ibufb,
+                                 int *pos,
+                                 const short paint_tile)
 {
-  bool sharpen = (s->painter->cache.invert ^ ((s->brush->flag & BRUSH_DIR_IN) != 0));
+  bool sharpen = (tile->cache.invert ^ ((s->brush->flag & BRUSH_DIR_IN) != 0));
   float threshold = s->brush->sharp_threshold;
   int x, y, xi, yi, xo, yo, xk, yk;
   float count;
@@ -992,7 +1074,7 @@ static void paint_2d_lift_soften(
   in_off[1] = pos[1];
   out_off[0] = out_off[1] = 0;
 
-  if (!tile) {
+  if (!paint_tile) {
     IMB_rectclip(ibuf, ibufb, &in_off[0], &in_off[1], &out_off[0], &out_off[1], &dim[0], &dim[1]);
 
     if ((dim[0] == 0) || (dim[1] == 0)) {
@@ -1010,8 +1092,8 @@ static void paint_2d_lift_soften(
       yi = in_off[1] + y;
 
       count = 0.0;
-      if (tile) {
-        paint_2d_ibuf_tile_convert(ibuf, &xi, &yi, tile);
+      if (paint_tile) {
+        paint_2d_ibuf_tile_convert(ibuf, &xi, &yi, paint_tile);
         if (xi < ibuf->x && xi >= 0 && yi < ibuf->y && yi >= 0) {
           paint_2d_ibuf_rgb_get(ibuf, xi, yi, rgba);
         }
@@ -1031,7 +1113,7 @@ static void paint_2d_lift_soften(
                                         xi + xk - kernel->pixel_len,
                                         yi + yk - kernel->pixel_len,
                                         outrgb,
-                                        tile,
+                                        paint_tile,
                                         kernel->wdata[xk + yk * kernel->side]);
         }
       }
@@ -1085,7 +1167,7 @@ static void paint_2d_set_region(
 static int paint_2d_torus_split_region(ImagePaintRegion region[4],
                                        ImBuf *dbuf,
                                        ImBuf *sbuf,
-                                       short tile)
+                                       short paint_tile)
 {
   int destx = region->destx;
   int desty = region->desty;
@@ -1096,7 +1178,7 @@ static int paint_2d_torus_split_region(ImagePaintRegion region[4],
   int origw, origh, w, h, tot = 0;
 
   /* convert destination and source coordinates to be within image */
-  if (tile & PAINT_TILE_X) {
+  if (paint_tile & PAINT_TILE_X) {
     destx = destx % dbuf->x;
     if (destx < 0) {
       destx += dbuf->x;
@@ -1106,7 +1188,7 @@ static int paint_2d_torus_split_region(ImagePaintRegion region[4],
       srcx += sbuf->x;
     }
   }
-  if (tile & PAINT_TILE_Y) {
+  if (paint_tile & PAINT_TILE_Y) {
     desty = desty % dbuf->y;
     if (desty < 0) {
       desty += dbuf->y;
@@ -1126,15 +1208,15 @@ static int paint_2d_torus_split_region(ImagePaintRegion region[4],
   paint_2d_set_region(&region[tot++], destx, desty, srcx, srcy, w, h);
 
   /* do 3 other rects if needed */
-  if ((tile & PAINT_TILE_X) && w < origw) {
+  if ((paint_tile & PAINT_TILE_X) && w < origw) {
     paint_2d_set_region(
         &region[tot++], (destx + w) % dbuf->x, desty, (srcx + w) % sbuf->x, srcy, origw - w, h);
   }
-  if ((tile & PAINT_TILE_Y) && h < origh) {
+  if ((paint_tile & PAINT_TILE_Y) && h < origh) {
     paint_2d_set_region(
         &region[tot++], destx, (desty + h) % dbuf->y, srcx, (srcy + h) % sbuf->y, w, origh - h);
   }
-  if ((tile & PAINT_TILE_X) && (tile & PAINT_TILE_Y) && (w < origw) && (h < origh)) {
+  if ((paint_tile & PAINT_TILE_X) && (paint_tile & PAINT_TILE_Y) && (w < origw) && (h < origh)) {
     paint_2d_set_region(&region[tot++],
                         (destx + w) % dbuf->x,
                         (desty + h) % dbuf->y,
@@ -1147,13 +1229,13 @@ static int paint_2d_torus_split_region(ImagePaintRegion region[4],
   return tot;
 }
 
-static void paint_2d_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos, short tile)
+static void paint_2d_lift_smear(ImBuf *ibuf, ImBuf *ibufb, int *pos, short paint_tile)
 {
   ImagePaintRegion region[4];
   int a, tot;
 
   paint_2d_set_region(region, 0, 0, pos[0], pos[1], ibufb->x, ibufb->y);
-  tot = paint_2d_torus_split_region(region, ibufb, ibuf, tile);
+  tot = paint_2d_torus_split_region(region, ibufb, ibuf, paint_tile);
 
   for (a = 0; a < tot; a++) {
     IMB_rectblend(ibufb,
@@ -1229,9 +1311,8 @@ static void paint_2d_convert_brushco(ImBuf *ibufb, const float pos[2], int ipos[
 }
 
 static void paint_2d_do_making_brush(ImagePaintState *s,
+                                     ImagePaintTile *tile,
                                      ImagePaintRegion *region,
-                                     unsigned short *curveb,
-                                     unsigned short *texmaskb,
                                      ImBuf *frombuf,
                                      float mask_max,
                                      short blend,
@@ -1252,21 +1333,21 @@ static void paint_2d_do_making_brush(ImagePaintState *s,
       int origx = region->destx - tx * ED_IMAGE_UNDO_TILE_SIZE;
       int origy = region->desty - ty * ED_IMAGE_UNDO_TILE_SIZE;
 
-      if (s->canvas->rect_float) {
+      if (tile->canvas->rect_float) {
         tmpbuf.rect_float = ED_image_paint_tile_find(
-            undo_tiles, s->image, s->canvas, tx, ty, &mask, false);
+            undo_tiles, s->image, tile->canvas, tile->iuser.tile, tx, ty, &mask, false);
       }
       else {
         tmpbuf.rect = ED_image_paint_tile_find(
-            undo_tiles, s->image, s->canvas, tx, ty, &mask, false);
+            undo_tiles, s->image, tile->canvas, tile->iuser.tile, tx, ty, &mask, false);
       }
 
-      IMB_rectblend(s->canvas,
+      IMB_rectblend(tile->canvas,
                     &tmpbuf,
                     frombuf,
                     mask,
-                    curveb,
-                    texmaskb,
+                    tile->cache.curve_mask,
+                    tile->cache.tex_mask,
                     mask_max,
                     region->destx,
                     region->desty,
@@ -1284,9 +1365,8 @@ static void paint_2d_do_making_brush(ImagePaintState *s,
 
 typedef struct Paint2DForeachData {
   ImagePaintState *s;
+  ImagePaintTile *tile;
   ImagePaintRegion *region;
-  unsigned short *curveb;
-  unsigned short *texmaskb;
   ImBuf *frombuf;
   float mask_max;
   short blend;
@@ -1300,9 +1380,8 @@ static void paint_2d_op_foreach_do(void *__restrict data_v,
 {
   Paint2DForeachData *data = (Paint2DForeachData *)data_v;
   paint_2d_do_making_brush(data->s,
+                           data->tile,
                            data->region,
-                           data->curveb,
-                           data->texmaskb,
                            data->frombuf,
                            data->mask_max,
                            data->blend,
@@ -1313,16 +1392,16 @@ static void paint_2d_op_foreach_do(void *__restrict data_v,
 }
 
 static int paint_2d_op(void *state,
-                       ImBuf *ibufb,
-                       unsigned short *curveb,
-                       unsigned short *texmaskb,
+                       ImagePaintTile *tile,
                        const float lastpos[2],
                        const float pos[2])
 {
   ImagePaintState *s = ((ImagePaintState *)state);
   ImBuf *clonebuf = NULL, *frombuf;
+  ImBuf *canvas = tile->canvas;
+  ImBuf *ibufb = tile->cache.ibuf;
   ImagePaintRegion region[4];
-  short tile = s->symmetry & (PAINT_TILE_X | PAINT_TILE_Y);
+  short paint_tile = s->symmetry & (PAINT_TILE_X | PAINT_TILE_Y);
   short blend = s->blend;
   const float *offset = s->brush->clone.offset;
   float liftpos[2];
@@ -1334,7 +1413,7 @@ static int paint_2d_op(void *state,
 
   /* lift from canvas */
   if (s->tool == PAINT_TOOL_SOFTEN) {
-    paint_2d_lift_soften(s, s->canvas, ibufb, bpos, tile);
+    paint_2d_lift_soften(s, tile, canvas, ibufb, bpos, paint_tile);
     blend = IMB_BLEND_INTERPOLATE;
   }
   else if (s->tool == PAINT_TOOL_SMEAR) {
@@ -1343,12 +1422,12 @@ static int paint_2d_op(void *state,
     }
 
     paint_2d_convert_brushco(ibufb, lastpos, blastpos);
-    paint_2d_lift_smear(s->canvas, ibufb, blastpos, tile);
+    paint_2d_lift_smear(canvas, ibufb, blastpos, paint_tile);
     blend = IMB_BLEND_INTERPOLATE;
   }
   else if (s->tool == PAINT_TOOL_CLONE && s->clonecanvas) {
-    liftpos[0] = pos[0] - offset[0] * s->canvas->x;
-    liftpos[1] = pos[1] - offset[1] * s->canvas->y;
+    liftpos[0] = pos[0] - offset[0] * canvas->x;
+    liftpos[1] = pos[1] - offset[1] * canvas->y;
 
     paint_2d_convert_brushco(ibufb, liftpos, bliftpos);
     clonebuf = paint_2d_lift_clone(s->clonecanvas, ibufb, bliftpos);
@@ -1356,9 +1435,9 @@ static int paint_2d_op(void *state,
 
   frombuf = (clonebuf) ? clonebuf : ibufb;
 
-  if (tile) {
+  if (paint_tile) {
     paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y);
-    tot = paint_2d_torus_split_region(region, s->canvas, frombuf, tile);
+    tot = paint_2d_torus_split_region(region, canvas, frombuf, paint_tile);
   }
   else {
     paint_2d_set_region(region, bpos[0], bpos[1], 0, 0, frombuf->x, frombuf->y);
@@ -1368,7 +1447,8 @@ static int paint_2d_op(void *state,
   /* blend into canvas */
   for (a = 0; a < tot; a++) {
     ED_imapaint_dirty_region(s->image,
-                             s->canvas,
+                             canvas,
+                             tile->iuser.tile,
                              region[a].destx,
                              region[a].desty,
                              region[a].width,
@@ -1379,7 +1459,7 @@ static int paint_2d_op(void *state,
       /* masking, find original pixels tiles from undo buffer to composite over */
       int tilex, tiley, tilew, tileh;
 
-      imapaint_region_tiles(s->canvas,
+      imapaint_region_tiles(canvas,
                             region[a].destx,
                             region[a].desty,
                             region[a].width,
@@ -1391,14 +1471,13 @@ static int paint_2d_op(void *state,
 
       if (tiley == tileh) {
         paint_2d_do_making_brush(
-            s, &region[a], curveb, texmaskb, frombuf, mask_max, blend, tilex, tiley, tilew, tileh);
+            s, tile, &region[a], frombuf, mask_max, blend, tilex, tiley, tilew, tileh);
       }
       else {
         Paint2DForeachData data;
         data.s = s;
+        data.tile = tile;
         data.region = &region[a];
-        data.curveb = curveb;
-        data.texmaskb = texmaskb;
         data.frombuf = frombuf;
         data.mask_max = mask_max;
         data.blend = blend;
@@ -1412,12 +1491,12 @@ static int paint_2d_op(void *state,
     }
     else {
       /* no masking, composite brush directly onto canvas */
-      IMB_rectblend_threaded(s->canvas,
-                             s->canvas,
+      IMB_rectblend_threaded(canvas,
+                             canvas,
                              frombuf,
                              NULL,
-                             curveb,
-                             texmaskb,
+                             tile->cache.curve_mask,
+                             tile->cache.tex_mask,
                              mask_max,
                              region[a].destx,
                              region[a].desty,
@@ -1439,47 +1518,25 @@ static int paint_2d_op(void *state,
   return 1;
 }
 
-static int paint_2d_canvas_set(ImagePaintState *s, Image *ima)
+static int paint_2d_canvas_set(ImagePaintState *s)
 {
-  ImBuf *ibuf = BKE_image_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL);
-
-  /* verify that we can paint and set canvas */
-  if (ima == NULL) {
-    return 0;
-  }
-  else if (BKE_image_has_packedfile(ima) && ima->rr) {
-    s->warnpackedfile = ima->id.name + 2;
-    return 0;
-  }
-  else if (ibuf && ibuf->channels != 4) {
-    s->warnmultifile = ima->id.name + 2;
-    return 0;
-  }
-  else if (!ibuf || !(ibuf->rect || ibuf->rect_float)) {
-    return 0;
-  }
-
-  s->image = ima;
-  s->canvas = ibuf;
-
   /* set clone canvas */
   if (s->tool == PAINT_TOOL_CLONE) {
-    ima = s->brush->clone.image;
-    ibuf = BKE_image_acquire_ibuf(ima, s->sima ? &s->sima->iuser : NULL, NULL);
+    Image *ima = s->brush->clone.image;
+    ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
 
     if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) {
       BKE_image_release_ibuf(ima, ibuf, NULL);
-      BKE_image_release_ibuf(s->image, s->canvas, NULL);
       return 0;
     }
 
     s->clonecanvas = ibuf;
 
     /* temporarily add float rect for cloning */
-    if (s->canvas->rect_float && !s->clonecanvas->rect_float) {
+    if (s->tiles[0].canvas->rect_float && !s->clonecanvas->rect_float) {
       IMB_float_from_rect(s->clonecanvas);
     }
-    else if (!s->canvas->rect_float && !s->clonecanvas->rect) {
+    else if (!s->tiles[0].canvas->rect_float && !s->clonecanvas->rect) {
       IMB_rect_from_float(s->clonecanvas);
     }
   }
@@ -1492,7 +1549,9 @@ static int paint_2d_canvas_set(ImagePaintState *s, Image *ima)
 
 static void paint_2d_canvas_free(ImagePaintState *s)
 {
-  BKE_image_release_ibuf(s->image, s->canvas, NULL);
+  for (int i = 0; i < s->num_tiles; i++) {
+    BKE_image_release_ibuf(s->image, s->tiles[i].canvas, NULL);
+  }
   BKE_image_release_ibuf(s->brush->clone.image, s->clonecanvas, NULL);
 
   if (s->blurkernel) {
@@ -1501,71 +1560,107 @@ static void paint_2d_canvas_free(ImagePaintState *s)
   }
 }
 
+static void paint_2d_transform_mouse(ImagePaintState *s, const float in[2], float out[2])
+{
+  UI_view2d_region_to_view(s->v2d, in[0], in[1], &out[0], &out[1]);
+}
+
+static bool is_inside_tile(const int size[2], const float pos[2], const float brush[2])
+{
+  return (pos[0] >= -brush[0]) && (pos[0] < size[0] + brush[0]) && (pos[1] >= -brush[1]) &&
+         (pos[1] < size[1] + brush[1]);
+}
+
+static void paint_2d_uv_to_coord(ImagePaintTile *tile, const float uv[2], float coord[2])
+{
+  coord[0] = (uv[0] - tile->uv_origin[0]) * tile->size[0];
+  coord[1] = (uv[1] - tile->uv_origin[1]) * tile->size[1];
+}
+
 void paint_2d_stroke(void *ps,
                      const float prev_mval[2],
                      const float mval[2],
                      const bool eraser,
                      float pressure,
                      float distance,
-                     float size)
+                     float base_size)
 {
-  float newuv[2], olduv[2];
+  float new_uv[2], old_uv[2];
   ImagePaintState *s = ps;
   BrushPainter *painter = s->painter;
-  ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, s->sima ? &s->sima->iuser : NULL, NULL);
-  const bool is_data = (ibuf && ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA);
 
-  if (!ibuf) {
-    return;
-  }
+  const bool is_data = s->tiles[0].canvas->colormanage_flag & IMB_COLORMANAGE_IS_DATA;
 
   s->blend = s->brush->blend;
   if (eraser) {
     s->blend = IMB_BLEND_ERASE_ALPHA;
   }
 
-  UI_view2d_region_to_view(s->v2d, mval[0], mval[1], &newuv[0], &newuv[1]);
-  UI_view2d_region_to_view(s->v2d, prev_mval[0], prev_mval[1], &olduv[0], &olduv[1]);
-
-  newuv[0] *= ibuf->x;
-  newuv[1] *= ibuf->y;
-
-  olduv[0] *= ibuf->x;
-  olduv[1] *= ibuf->y;
+  UI_view2d_region_to_view(s->v2d, mval[0], mval[1], &new_uv[0], &new_uv[1]);
+  UI_view2d_region_to_view(s->v2d, prev_mval[0], prev_mval[1], &old_uv[0], &old_uv[1]);
 
+  float last_uv[2], start_uv[2];
+  UI_view2d_region_to_view(s->v2d, 0.0f, 0.0f, &start_uv[0], &start_uv[1]);
   if (painter->firsttouch) {
-    float startuv[2];
-
-    UI_view2d_region_to_view(s->v2d, 0, 0, &startuv[0], &startuv[1]);
-
     /* paint exactly once on first touch */
-    painter->startpaintpos[0] = startuv[0] * ibuf->x;
-    painter->startpaintpos[1] = startuv[1] * ibuf->y;
-
-    painter->firsttouch = 0;
-    copy_v2_v2(painter->lastpaintpos, newuv);
+    copy_v2_v2(last_uv, new_uv);
   }
   else {
-    copy_v2_v2(painter->lastpaintpos, olduv);
+    copy_v2_v2(last_uv, old_uv);
   }
 
-  /* OCIO_TODO: float buffers are now always linear, so always use color correction
-   *            this should probably be changed when texture painting color space is supported
-   */
-  brush_painter_2d_require_imbuf(painter, (ibuf->rect_float != NULL), !is_data);
+  float uv_brush_size[2] = {base_size / s->tiles[0].size[0], base_size / s->tiles[0].size[1]};
+
+  for (int i = 0; i < s->num_tiles; i++) {
+    ImagePaintTile *tile = &s->tiles[i];
+
+    /* First test: Project brush into UV space, clip against tile. */
+    const int uv_size[2] = {1, 1};
+    float local_new_uv[2], local_old_uv[2];
+    sub_v2_v2v2(local_new_uv, new_uv, tile->uv_origin);
+    sub_v2_v2v2(local_old_uv, old_uv, tile->uv_origin);
+    if (!(is_inside_tile(uv_size, local_new_uv, uv_brush_size) ||
+          is_inside_tile(uv_size, local_old_uv, uv_brush_size))) {
+      continue;
+    }
+
+    /* Lazy tile loading to get size in pixels. */
+    if (!paint_2d_ensure_tile_canvas(s, i)) {
+      continue;
+    }
+
+    float size = base_size * tile->radius_fac;
+
+    float new_coord[2], old_coord[2];
+    paint_2d_uv_to_coord(tile, new_uv, new_coord);
+    paint_2d_uv_to_coord(tile, old_uv, old_coord);
+    if (painter->firsttouch) {
+      paint_2d_uv_to_coord(tile, start_uv, tile->start_paintpos);
+    }
+    paint_2d_uv_to_coord(tile, last_uv, tile->last_paintpos);
+
+    /* Second check in pixel coordinates. */
+    const float pixel_brush_size[] = {size, size};
+    if (!(is_inside_tile(tile->size, new_coord, pixel_brush_size) ||
+          is_inside_tile(tile->size, old_coord, pixel_brush_size))) {
+      continue;
+    }
+
+    ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, &tile->iuser, NULL);
 
-  brush_painter_2d_refresh_cache(s, painter, newuv, mval, pressure, distance, size);
+    /* OCIO_TODO: float buffers are now always linear, so always use color correction
+     *            this should probably be changed when texture painting color space is supported
+     */
+    brush_painter_2d_require_imbuf(
+        painter->brush, tile, (ibuf->rect_float != NULL), !is_data, painter->cache_invert);
 
-  if (paint_2d_op(s,
-                  painter->cache.ibuf,
-                  painter->cache.curve_mask,
-                  painter->cache.tex_mask,
-                  olduv,
-                  newuv)) {
-    s->need_redraw = true;
+    brush_painter_2d_refresh_cache(s, painter, tile, new_coord, mval, pressure, distance, size);
+
+    if (paint_2d_op(s, tile, old_coord, new_coord))
+      tile->need_redraw = true;
   }
 
-  BKE_image_release_ibuf(s->image, ibuf, NULL);
+  painter->firsttouch = 0;
 }
 
 void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode)
@@ -1588,13 +1683,57 @@ void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode)
   s->image = s->sima->image;
   s->symmetry = settings->imapaint.paint.symmetry_flags;
 
-  if (!paint_2d_canvas_set(s, s->image)) {
-    if (s->warnmultifile) {
-      BKE_report(op->reports, RPT_WARNING, "Image requires 4 color channels to paint");
-    }
-    if (s->warnpackedfile) {
-      BKE_report(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted");
-    }
+  if (s->image == NULL) {
+    MEM_freeN(s);
+    return NULL;
+  }
+  if (BKE_image_has_packedfile(s->image) && s->image->rr != NULL) {
+    BKE_report(op->reports, RPT_WARNING, "Packed MultiLayer files cannot be painted");
+    MEM_freeN(s);
+    return 0;
+  }
+
+  s->num_tiles = BLI_listbase_count(&s->image->tiles);
+  s->tiles = MEM_callocN(sizeof(ImagePaintTile) * s->num_tiles, "ImagePaintTile");
+  for (int i = 0; i < s->num_tiles; i++) {
+    BKE_imageuser_default(&s->tiles[i].iuser);
+  }
+  s->tiles[0].iuser.ok = true;
+
+  zero_v2(s->tiles[0].uv_origin);
+
+  ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, &s->tiles[0].iuser, NULL);
+  if (ibuf == NULL) {
+    MEM_freeN(s->tiles);
+    MEM_freeN(s);
+    return NULL;
+  }
+
+  if (ibuf->channels != 4) {
+    BKE_report(op->reports, RPT_WARNING, "Image requires 4 color channels to paint");
+    MEM_freeN(s->tiles);
+    MEM_freeN(s);
+    return NULL;
+  }
+
+  s->tiles[0].size[0] = ibuf->x;
+  s->tiles[0].size[1] = ibuf->y;
+  s->tiles[0].radius_fac = 1.0f;
+
+  s->tiles[0].canvas = ibuf;
+  s->tiles[0].state = PAINT2D_TILE_READY;
+
+  /* Initialize offsets here, they're needed for the uv space clip test before lazy-loading the
+   * tile properly. */
+  int tile_idx = 0;
+  for (ImageTile *tile = s->image->tiles.first; tile; tile = tile->next, tile_idx++) {
+    s->tiles[tile_idx].iuser.tile = tile->tile_number;
+    s->tiles[tile_idx].uv_origin[0] = ((tile->tile_number - 1001) % 10);
+    s->tiles[tile_idx].uv_origin[1] = ((tile->tile_number - 1001) / 10);
+  }
+
+  if (!paint_2d_canvas_set(s)) {
+    MEM_freeN(s->tiles);
 
     MEM_freeN(s);
     return NULL;
@@ -1616,18 +1755,28 @@ void paint_2d_redraw(const bContext *C, void *ps, bool final)
 {
   ImagePaintState *s = ps;
 
-  if (s->need_redraw) {
-    ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, s->sima ? &s->sima->iuser : NULL, NULL);
+  bool had_redraw = false;
+  for (int i = 0; i < s->num_tiles; i++) {
+    if (s->tiles[i].need_redraw) {
+      ImBuf *ibuf = BKE_image_acquire_ibuf(s->image, &s->tiles[i].iuser, NULL);
 
-    imapaint_image_update(s->sima, s->image, ibuf, false);
-    ED_imapaint_clear_partial_redraw();
+      imapaint_image_update(s->sima, s->image, ibuf, &s->tiles[i].iuser, false);
 
-    BKE_image_release_ibuf(s->image, ibuf, NULL);
+      BKE_image_release_ibuf(s->image, ibuf, NULL);
 
-    s->need_redraw = false;
+      s->tiles[i].need_redraw = false;
+      had_redraw = true;
+    }
   }
-  else if (!final) {
-    return;
+
+  if (had_redraw) {
+    ED_imapaint_clear_partial_redraw();
+    if (s->sima == NULL || !s->sima->lock) {
+      ED_region_tag_redraw(CTX_wm_region(C));
+    }
+    else {
+      WM_event_add_notifier(C, NC_IMAGE | NA_PAINTING, s->image);
+    }
   }
 
   if (final) {
@@ -1639,14 +1788,6 @@ void paint_2d_redraw(const bContext *C, void *ps, bool final)
     WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, s->image);
     DEG_id_tag_update(&s->image->id, 0);
   }
-  else {
-    if (!s->sima || !s->sima->lock) {
-      ED_region_tag_redraw(CTX_wm_region(C));
-    }
-    else {
-      WM_event_add_notifier(C, NC_IMAGE | NA_PAINTING, s->image);
-    }
-  }
 }
 
 void paint_2d_stroke_done(void *ps)
@@ -1654,7 +1795,11 @@ void paint_2d_stroke_done(void *ps)
   ImagePaintState *s = ps;
 
   paint_2d_canvas_free(s);
-  brush_painter_2d_free(s->painter);
+  for (int i = 0; i < s->num_tiles; i++) {
+    brush_painter_cache_2d_free(&s->tiles[i].cache);
+  }
+  MEM_freeN(s->painter);
+  MEM_freeN(s->tiles);
   paint_brush_exit_tex(s->brush);
 
   MEM_freeN(s);
@@ -1713,9 +1858,29 @@ static void paint_2d_fill_add_pixel_float(const int x_px,
   }
 }
 
+static ImageUser *paint_2d_get_tile_iuser(ImagePaintState *s, int tile_number)
+{
+  ImageUser *iuser = &s->tiles[0].iuser;
+  for (int i = 0; i < s->num_tiles; i++) {
+    if (s->tiles[i].iuser.tile == tile_number) {
+      if (!paint_2d_ensure_tile_canvas(s, i)) {
+        return NULL;
+      }
+      iuser = &s->tiles[i].iuser;
+      break;
+    }
+  }
+
+  return iuser;
+}
+
 /* this function expects linear space color values */
-void paint_2d_bucket_fill(
-    const bContext *C, const float color[3], Brush *br, const float mouse_init[2], void *ps)
+void paint_2d_bucket_fill(const bContext *C,
+                          const float color[3],
+                          Brush *br,
+                          const float mouse_init[2],
+                          const float mouse_final[2],
+                          void *ps)
 {
   SpaceImage *sima = CTX_wm_space_image(C);
   Image *ima = sima->image;
@@ -1734,8 +1899,17 @@ void paint_2d_bucket_fill(
     return;
   }
 
-  ibuf = BKE_image_acquire_ibuf(ima, &sima->iuser, NULL);
+  float uv_origin[2];
+  float image_init[2];
+  paint_2d_transform_mouse(s, mouse_init, image_init);
 
+  int tile_number = BKE_image_get_tile_from_pos(ima, image_init, image_init, uv_origin);
+  ImageUser *iuser = paint_2d_get_tile_iuser(s, tile_number);
+  if (!iuser) {
+    return;
+  }
+
+  ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
   if (!ibuf) {
     return;
   }
@@ -1753,9 +1927,9 @@ void paint_2d_bucket_fill(
     color_f[3] = strength;
   }
 
-  if (!mouse_init || !br) {
+  if (!mouse_final || !br) {
     /* first case, no image UV, fill the whole image */
-    ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
+    ED_imapaint_dirty_region(ima, ibuf, tile_number, 0, 0, ibuf->x, ibuf->y, false);
 
     if (do_float) {
       for (x_px = 0; x_px < ibuf->x; x_px++) {
@@ -1783,15 +1957,12 @@ void paint_2d_bucket_fill(
     BLI_bitmap *touched;
     size_t coordinate;
     int width = ibuf->x;
-    float image_init[2];
     int minx = ibuf->x, miny = ibuf->y, maxx = 0, maxy = 0;
     float pixel_color[4];
     /* We are comparing to sum of three squared values
      * (assumed in range [0,1]), so need to multiply... */
     float threshold_sq = br->fill_threshold * br->fill_threshold * 3;
 
-    UI_view2d_region_to_view(s->v2d, mouse_init[0], mouse_init[1], &image_init[0], &image_init[1]);
-
     x_px = image_init[0] * ibuf->x;
     y_px = image_init[1] * ibuf->y;
 
@@ -1801,7 +1972,7 @@ void paint_2d_bucket_fill(
     }
 
     /* change image invalidation method later */
-    ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
+    ED_imapaint_dirty_region(ima, ibuf, tile_number, 0, 0, ibuf->x, ibuf->y, false);
 
     stack = BLI_stack_new(sizeof(size_t), __func__);
     touched = BLI_BITMAP_NEW(((size_t)ibuf->x) * ibuf->y, "bucket_fill_bitmap");
@@ -1913,7 +2084,7 @@ void paint_2d_bucket_fill(
     BLI_stack_free(stack);
   }
 
-  imapaint_image_update(sima, ima, ibuf, false);
+  imapaint_image_update(sima, ima, ibuf, iuser, false);
   ED_imapaint_clear_partial_redraw();
 
   BKE_image_release_ibuf(ima, ibuf, NULL);
@@ -1938,19 +2109,26 @@ void paint_2d_gradient_fill(
 
   bool do_float;
 
-  if (!ima) {
+  if (ima == NULL) {
     return;
   }
 
-  ibuf = BKE_image_acquire_ibuf(ima, &sima->iuser, NULL);
+  float uv_origin[2];
+  int tile_number = BKE_image_get_tile_from_pos(ima, image_init, image_init, uv_origin);
+  ImageUser *iuser = paint_2d_get_tile_iuser(s, tile_number);
+  if (!iuser) {
+    return;
+  }
 
-  if (!ibuf) {
+  ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
+  if (ibuf == NULL) {
     return;
   }
 
-  UI_view2d_region_to_view(
-      s->v2d, mouse_final[0], mouse_final[1], &image_final[0], &image_final[1]);
-  UI_view2d_region_to_view(s->v2d, mouse_init[0], mouse_init[1], &image_init[0], &image_init[1]);
+  paint_2d_transform_mouse(s, mouse_final, image_final);
+  paint_2d_transform_mouse(s, mouse_init, image_init);
+  sub_v2_v2(image_init, uv_origin);
+  sub_v2_v2(image_final, uv_origin);
 
   image_final[0] *= ibuf->x;
   image_final[1] *= ibuf->y;
@@ -1967,7 +2145,7 @@ void paint_2d_gradient_fill(
   do_float = (ibuf->rect_float != NULL);
 
   /* this will be substituted by something else when selection is available */
-  ED_imapaint_dirty_region(ima, ibuf, 0, 0, ibuf->x, ibuf->y, false);
+  ED_imapaint_dirty_region(ima, ibuf, tile_number, 0, 0, ibuf->x, ibuf->y, false);
 
   if (do_float) {
     for (x_px = 0; x_px < ibuf->x; x_px++) {
@@ -2027,7 +2205,7 @@ void paint_2d_gradient_fill(
     }
   }
 
-  imapaint_image_update(sima, ima, ibuf, false);
+  imapaint_image_update(sima, ima, ibuf, iuser, false);
   ED_imapaint_clear_partial_redraw();
 
   BKE_image_release_ibuf(ima, ibuf, NULL);
index c57490041bcc85f531fd596db40609d623e6c1d0..6a67c4699557f2e337d6dff53839447d0517822b 100644 (file)
@@ -197,6 +197,7 @@ BLI_INLINE unsigned char f_to_char(const float val)
  */
 typedef struct ProjPaintImage {
   Image *ima;
+  ImageUser iuser;
   ImBuf *ibuf;
   ImagePaintPartialRedraw *partRedrawRect;
   /** Only used to build undo tiles during painting. */
@@ -530,6 +531,18 @@ BLI_INLINE const MPoly *ps_tri_index_to_mpoly(const ProjPaintState *ps, int tri_
 
 /* Finish projection painting structs */
 
+static int project_paint_face_paint_tile(Image *ima, const float *uv)
+{
+  if (ima == NULL || ima->source != IMA_SRC_TILED) {
+    return 0;
+  }
+
+  /* Currently, faces are assumed to belong to one tile, so checking the first loop is enough. */
+  int tx = (int)uv[0];
+  int ty = (int)uv[1];
+  return 1001 + 10 * ty + tx;
+}
+
 static TexPaintSlot *project_paint_face_paint_slot(const ProjPaintState *ps, int tri_index)
 {
   const MPoly *mp = ps_tri_index_to_mpoly(ps, tri_index);
@@ -729,9 +742,17 @@ static bool project_paint_PickColor(const ProjPaintState *ps,
 
   ima = project_paint_face_paint_image(ps, tri_index);
   /** we must have got the imbuf before getting here. */
-  ibuf = BKE_image_get_first_ibuf(ima);
-  if (!ibuf) {
-    return 0;
+  int tile_number = project_paint_face_paint_tile(ima, lt_tri_uv[0]);
+  ImageUser iuser;
+  BKE_imageuser_default(&iuser);
+  iuser.tile = tile_number;
+  ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL);
+  if (ibuf == NULL) {
+    iuser.tile = 0;
+    ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL);
+    if (ibuf == NULL) {
+      return 0;
+    }
   }
 
   if (interp) {
@@ -1154,6 +1175,8 @@ static bool check_seam(const ProjPaintState *ps,
         const float *lt_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt)};
         Image *tpage = project_paint_face_paint_image(ps, tri_index);
         Image *orig_tpage = project_paint_face_paint_image(ps, orig_face);
+        int tile = project_paint_face_paint_tile(tpage, lt_tri_uv[0]);
+        int orig_tile = project_paint_face_paint_tile(orig_tpage, orig_lt_tri_uv[0]);
 
         BLI_assert(i1_fidx != -1);
 
@@ -1171,7 +1194,8 @@ static bool check_seam(const ProjPaintState *ps,
         }
 
         /* first test if they have the same image */
-        if ((orig_tpage == tpage) && cmp_uv(orig_lt_tri_uv[orig_i1_fidx], lt_tri_uv[i1_fidx]) &&
+        if ((orig_tpage == tpage) && (orig_tile == tile) &&
+            cmp_uv(orig_lt_tri_uv[orig_i1_fidx], lt_tri_uv[i1_fidx]) &&
             cmp_uv(orig_lt_tri_uv[orig_i2_fidx], lt_tri_uv[i2_fidx])) {
           /* if faces don't have the same winding in uv space,
            * they are on the same side so edge is boundary */
@@ -1817,6 +1841,7 @@ static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty)
                                           pjIma->ima,
                                           pjIma->ibuf,
                                           tinf->tmpibuf,
+                                          pjIma->iuser.tile,
                                           tx,
                                           ty,
                                           &pjIma->maskRect[tile_index],
@@ -1829,6 +1854,7 @@ static int project_paint_undo_subtiles(const TileInfo *tinf, int tx, int ty)
                                           pjIma->ima,
                                           pjIma->ibuf,
                                           tinf->tmpibuf,
+                                          pjIma->iuser.tile,
                                           tx,
                                           ty,
                                           NULL,
@@ -3486,6 +3512,7 @@ static void project_bucket_init(const ProjPaintState *ps,
   ImBuf *ibuf = NULL;
   Image *tpage_last = NULL, *tpage;
   ImBuf *tmpibuf = NULL;
+  int tile_last = 0;
 
   if (ps->image_tot == 1) {
     /* Simple loop, no context switching */
@@ -3509,17 +3536,34 @@ static void project_bucket_init(const ProjPaintState *ps,
     for (node = ps->bucketFaces[bucket_index]; node; node = node->next) {
       tri_index = POINTER_AS_INT(node->link);
 
+      const MLoopTri *lt = &ps->mlooptri_eval[tri_index];
+      const float *lt_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt)};
+
       /* Image context switching */
       tpage = project_paint_face_paint_image(ps, tri_index);
-      if (tpage_last != tpage) {
+      int tile = project_paint_face_paint_tile(tpage, lt_tri_uv[0]);
+      if (tpage_last != tpage || tile_last != tile) {
         tpage_last = tpage;
+        tile_last = tile;
 
+        ibuf = NULL;
         for (image_index = 0; image_index < ps->image_tot; image_index++) {
-          if (ps->projImages[image_index].ima == tpage_last) {
-            ibuf = ps->projImages[image_index].ibuf;
+          ProjPaintImage *projIma = &ps->projImages[image_index];
+          if ((projIma->ima == tpage) && (projIma->iuser.tile == tile)) {
+            ibuf = projIma->ibuf;
             break;
           }
         }
+        if (ibuf == NULL) {
+          /* Failed to find the specific tile, fall back to the primary tile. */
+          for (image_index = 0; image_index < ps->image_tot; image_index++) {
+            ProjPaintImage *projIma = &ps->projImages[image_index];
+            if ((projIma->ima == tpage) && (projIma->iuser.tile == 0)) {
+              ibuf = projIma->ibuf;
+              break;
+            }
+          }
+        }
       }
       /* context switching done */
 
@@ -4232,22 +4276,36 @@ static bool project_paint_winclip(const ProjPaintState *ps, const ProjPaintFaceC
 }
 #endif  // PROJ_DEBUG_WINCLIP
 
+typedef struct PrepareImageEntry {
+  struct PrepareImageEntry *next, *prev;
+  Image *ima;
+  int tile;
+} PrepareImageEntry;
+
 static void project_paint_build_proj_ima(ProjPaintState *ps,
                                          MemArena *arena,
-                                         LinkNode *image_LinkList)
+                                         ListBase *used_images)
 {
   ProjPaintImage *projIma;
-  LinkNode *node;
+  PrepareImageEntry *entry;
   int i;
 
   /* build an array of images we use */
   projIma = ps->projImages = BLI_memarena_alloc(arena, sizeof(ProjPaintImage) * ps->image_tot);
 
-  for (node = image_LinkList, i = 0; node; node = node->next, i++, projIma++) {
+  for (entry = used_images->first, i = 0; entry; entry = entry->next, i++, projIma++) {
+    memset(&projIma->iuser, 0, sizeof(ImageUser));
+    BKE_imageuser_default(&projIma->iuser);
+    projIma->iuser.tile = entry->tile;
     int size;
-    projIma->ima = node->link;
+    projIma->ima = entry->ima;
     projIma->touch = 0;
-    projIma->ibuf = BKE_image_acquire_ibuf(projIma->ima, NULL, NULL);
+    projIma->ibuf = BKE_image_acquire_ibuf(projIma->ima, &projIma->iuser, NULL);
+    if (projIma->ibuf == NULL) {
+      projIma->iuser.tile = 0;
+      projIma->ibuf = BKE_image_acquire_ibuf(projIma->ima, &projIma->iuser, NULL);
+      BLI_assert(projIma->ibuf != NULL);
+    }
     size = sizeof(void **) * ED_IMAGE_UNDO_TILE_NUMBER(projIma->ibuf->x) *
            ED_IMAGE_UNDO_TILE_NUMBER(projIma->ibuf->y);
     projIma->partRedrawRect = BLI_memarena_alloc(
@@ -4270,15 +4328,18 @@ static void project_paint_prepare_all_faces(ProjPaintState *ps,
                                             const bool is_multi_view)
 {
   /* Image Vars - keep track of images we have used */
-  LinkNodePair image_LinkList = {NULL, NULL};
+  ListBase used_images = {NULL};
 
   Image *tpage_last = NULL, *tpage;
   TexPaintSlot *slot_last = NULL;
   TexPaintSlot *slot = NULL;
+  int tile_last = -1, tile;
   const MLoopTri *lt;
   int image_index = -1, tri_index;
   int prev_poly = -1;
 
+  BLI_assert(ps->image_tot == 0);
+
   for (tri_index = 0, lt = ps->mlooptri_eval; tri_index < ps->totlooptri_eval; tri_index++, lt++) {
     bool is_face_sel;
     bool skip_tri = false;
@@ -4321,6 +4382,8 @@ static void project_paint_prepare_all_faces(ProjPaintState *ps,
 
     ps->poly_to_loop_uv[lt->poly] = mloopuv_base;
 
+    tile = project_paint_face_paint_tile(tpage, mloopuv_base[lt->tri[0]].uv);
+
 #ifndef PROJ_DEBUG_NOSEAMBLEED
     project_paint_bleed_add_face_user(ps, arena, lt, tri_index);
 #endif
@@ -4382,18 +4445,24 @@ static void project_paint_prepare_all_faces(ProjPaintState *ps,
         }
       }
 
-      if (tpage_last != tpage) {
-
-        image_index = BLI_linklist_index(image_LinkList.list, tpage);
+      if (tpage_last != tpage || tile_last != tile) {
+        image_index = 0;
+        for (PrepareImageEntry *e = used_images.first; e; e = e->next, image_index++) {
+          if (e->ima == tpage && e->tile == tile) {
+            break;
+          }
+        }
 
-        if (image_index == -1 && BKE_image_has_ibuf(tpage, NULL)) {
-          /* MemArena doesn't have an append func */
-          BLI_linklist_append(&image_LinkList, tpage);
-          image_index = ps->image_tot;
+        if (image_index == ps->image_tot) {
+          PrepareImageEntry *e = MEM_callocN(sizeof(PrepareImageEntry), "PrepareImageEntry");
+          e->ima = tpage;
+          e->tile = tile;
+          BLI_addtail(&used_images, e);
           ps->image_tot++;
         }
 
         tpage_last = tpage;
+        tile_last = tile;
       }
 
       if (image_index != -1) {
@@ -4406,11 +4475,11 @@ static void project_paint_prepare_all_faces(ProjPaintState *ps,
 
   /* build an array of images we use*/
   if (ps->is_shared_user == false) {
-    project_paint_build_proj_ima(ps, arena, image_LinkList.list);
+    project_paint_build_proj_ima(ps, arena, &used_images);
   }
 
   /* we have built the array, discard the linked list */
-  BLI_linklist_free(image_LinkList.list, NULL);
+  BLI_freelistN(&used_images);
 }
 
 /* run once per stroke before projection painting */
@@ -4675,7 +4744,7 @@ static bool project_image_refresh_tagged(ProjPaintState *ps)
         pr = &(projIma->partRedrawRect[i]);
         if (pr->x2 != -1) { /* TODO - use 'enabled' ? */
           set_imapaintpartial(pr);
-          imapaint_image_update(NULL, projIma->ima, projIma->ibuf, true);
+          imapaint_image_update(NULL, projIma->ima, projIma->ibuf, &projIma->iuser, true);
           redraw = 1;
         }
 
@@ -6441,7 +6510,8 @@ static Image *proj_paint_image_create(wmOperator *op, Main *bmain, bool is_data)
                                 gen_type,
                                 color,
                                 false,
-                                is_data);
+                                is_data,
+                                false); /* TODO(lukas): Add option */
 
   return ima;
 }
index 84665728e17fc233ba1f679623219c792fc0e6de..53beb9815221cb05bc77198394feeeda7ec003a3 100644 (file)
@@ -186,6 +186,7 @@ bool image_texture_paint_poll(struct bContext *C);
 void imapaint_image_update(struct SpaceImage *sima,
                            struct Image *image,
                            struct ImBuf *ibuf,
+                           struct ImageUser *iuser,
                            short texpaint);
 struct ImagePaintPartialRedraw *get_imapaintpartial(void);
 void set_imapaintpartial(struct ImagePaintPartialRedraw *ippr);
@@ -206,6 +207,7 @@ void paint_2d_bucket_fill(const struct bContext *C,
                           const float color[3],
                           struct Brush *br,
                           const float mouse_init[2],
+                          const float mouse_final[2],
                           void *ps);
 void paint_2d_gradient_fill(const struct bContext *C,
                             struct Brush *br,
index 74cf28ce5d4d0c0249bc42d6a722118aadc897a9..f9899135e2dcba369a1847d2f5834ef11e1b80e7 100644 (file)
@@ -1947,7 +1947,7 @@ void clip_draw_main(const bContext *C, SpaceClip *sc, ARegion *ar)
 
   /* if no clip, nothing to do */
   if (!clip) {
-    ED_region_grid_draw(ar, zoomx, zoomy);
+    ED_region_grid_draw(ar, zoomx, zoomy, 0.0f, 0.0f);
     return;
   }
 
@@ -1994,7 +1994,7 @@ void clip_draw_main(const bContext *C, SpaceClip *sc, ARegion *ar)
     draw_movieclip_muted(ar, width, height, zoomx, zoomy);
   }
   else {
-    ED_region_grid_draw(ar, zoomx, zoomy);
+    ED_region_grid_draw(ar, zoomx, zoomy, 0.0f, 0.0f);
   }
 
   if (width && height) {
index 2d4ca6dc15aec8ff4d0b0ef4ac62861fe6c2dfa3..9a633427d8291436b043756433738c0fa6fe1352 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "PIL_time.h"
 
+#include "BLI_listbase.h"
 #include "BLI_math.h"
 #include "BLI_rect.h"
 #include "BLI_threads.h"
@@ -531,6 +532,36 @@ static void sima_draw_zbuffloat_pixels(Scene *scene,
   MEM_freeN(rectf);
 }
 
+static void draw_udim_label(ARegion *ar, float fx, float fy, const char *label)
+{
+  if (label == NULL || !label[0]) {
+    return;
+  }
+
+  /* find window pixel coordinates of origin */
+  int x, y;
+  UI_view2d_view_to_region(&ar->v2d, fx, fy, &x, &y);
+
+  GPU_blend_set_func_separate(
+      GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
+  GPU_blend(true);
+
+  int textwidth = BLF_width(blf_mono_font, label, strlen(label)) + 10;
+  float stepx = BLI_rcti_size_x(&ar->v2d.mask) / BLI_rctf_size_x(&ar->v2d.cur);
+  float opacity;
+  if (textwidth < 0.5f * (stepx - 10))
+    opacity = 1.0f;
+  else if (textwidth < (stepx - 10))
+    opacity = 2.0f - 2.0f * (textwidth / (stepx - 10));
+  else
+    opacity = 0.0f;
+  BLF_color4ub(blf_mono_font, 220, 220, 220, 150 * opacity);
+  BLF_position(blf_mono_font, (int)(x + 10), (int)(y + 10), 0);
+  BLF_draw_ascii(blf_mono_font, label, strlen(label));
+
+  GPU_blend(false);
+}
+
 static void draw_image_buffer(const bContext *C,
                               SpaceImage *sima,
                               ARegion *ar,
@@ -760,6 +791,83 @@ static void draw_image_paint_helpers(
   }
 }
 
+static void draw_udim_tile_grid(unsigned int pos_attr,
+                                unsigned int color_attr,
+                                ARegion *ar,
+                                int x,
+                                int y,
+                                float stepx,
+                                float stepy,
+                                const float color[3])
+{
+  float x1, y1;
+  UI_view2d_view_to_region_fl(&ar->v2d, x, y, &x1, &y1);
+  int gridpos[5][2] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
+  for (int i = 0; i < 4; i++) {
+    immAttr3fv(color_attr, color);
+    immVertex2f(pos_attr, x1 + gridpos[i][0] * stepx, y1 + gridpos[i][1] * stepy);
+    immAttr3fv(color_attr, color);
+    immVertex2f(pos_attr, x1 + gridpos[i + 1][0] * stepx, y1 + gridpos[i + 1][1] * stepy);
+  }
+}
+
+static void draw_udim_tile_grids(ARegion *ar, SpaceImage *sima, Image *ima)
+{
+  int num_tiles;
+  if (ima != NULL) {
+    num_tiles = BLI_listbase_count(&ima->tiles);
+
+    if (ima->source != IMA_SRC_TILED) {
+      return;
+    }
+  }
+  else {
+    num_tiles = sima->tile_grid_shape[0] * sima->tile_grid_shape[1];
+  }
+
+  float stepx = BLI_rcti_size_x(&ar->v2d.mask) / BLI_rctf_size_x(&ar->v2d.cur);
+  float stepy = BLI_rcti_size_y(&ar->v2d.mask) / BLI_rctf_size_y(&ar->v2d.cur);
+
+  GPUVertFormat *format = immVertexFormat();
+  unsigned int pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+  unsigned color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
+
+  immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
+  immBegin(GPU_PRIM_LINES, 8 * num_tiles);
+
+  float theme_color[3], selected_color[3];
+  UI_GetThemeColorShade3fv(TH_BACK, 60.0f, theme_color);
+  UI_GetThemeColor3fv(TH_FACE_SELECT, selected_color);
+
+  if (ima != NULL) {
+    ImageTile *cur_tile = BLI_findlink(&ima->tiles, ima->active_tile_index);
+
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      if (tile != cur_tile) {
+        int x = (tile->tile_number - 1001) % 10;
+        int y = (tile->tile_number - 1001) / 10;
+        draw_udim_tile_grid(pos, color, ar, x, y, stepx, stepy, theme_color);
+      }
+    }
+
+    if (cur_tile != NULL) {
+      int cur_x = (cur_tile->tile_number - 1001) % 10;
+      int cur_y = (cur_tile->tile_number - 1001) / 10;
+      draw_udim_tile_grid(pos, color, ar, cur_x, cur_y, stepx, stepy, selected_color);
+    }
+  }
+  else {
+    for (int y = 0; y < sima->tile_grid_shape[1]; y++) {
+      for (int x = 0; x < sima->tile_grid_shape[0]; x++) {
+        draw_udim_tile_grid(pos, color, ar, x, y, stepx, stepy, theme_color);
+      }
+    }
+  }
+
+  immEnd();
+  immUnbindProgram();
+}
+
 /* draw main image region */
 
 void draw_image_main(const bContext *C, ARegion *ar)
@@ -827,18 +935,43 @@ void draw_image_main(const bContext *C, ARegion *ar)
     }
   }
 
-  ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
+
+  int main_w = 0;
+  int main_h = 0;
 
   /* draw the image or grid */
   if (ibuf == NULL) {
-    ED_region_grid_draw(ar, zoomx, zoomy);
+    if (ima != NULL) {
+      LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+        int x = (tile->tile_number - 1001) % 10;
+        int y = (tile->tile_number - 1001) / 10;
+        ED_region_grid_draw(ar, zoomx, zoomy, x, y);
+      }
+    }
+    else {
+      for (int y = 0; y < sima->tile_grid_shape[1]; y++) {
+        for (int x = 0; x < sima->tile_grid_shape[0]; x++) {
+          ED_region_grid_draw(ar, zoomx, zoomy, x, y);
+        }
+      }
+    }
   }
   else {
     if (sima->flag & SI_DRAW_TILE) {
       draw_image_buffer_repeated(C, sima, ar, scene, ibuf, zoomx, zoomy);
     }
     else {
+      main_w = ibuf->x;
+      main_h = ibuf->y;
+
       draw_image_buffer(C, sima, ar, scene, ibuf, 0.0f, 0.0f, zoomx, zoomy);
+      if (ima->source == IMA_SRC_TILED) {
+        ImageTile *tile = BKE_image_get_tile(ima, 0);
+        char label[sizeof(tile->label)];
+        BKE_image_get_tile_label(ima, tile, label, sizeof(label));
+        draw_udim_label(ar, 0.0f, 0.0f, label);
+      }
     }
 
     if (sima->flag & SI_DRAW_METADATA) {
@@ -854,6 +987,30 @@ void draw_image_main(const bContext *C, ARegion *ar)
 
   ED_space_image_release_buffer(sima, ibuf, lock);
 
+  if (ima != NULL && ima->source == IMA_SRC_TILED) {
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      if (tile->tile_number == 1001) {
+        continue;
+      }
+
+      ibuf = ED_space_image_acquire_buffer(sima, &lock, tile->tile_number);
+      if (ibuf != NULL) {
+        int x_pos = (tile->tile_number - 1001) % 10;
+        int y_pos = (tile->tile_number - 1001) / 10;
+        char label[sizeof(tile->label)];
+        BKE_image_get_tile_label(ima, tile, label, sizeof(label));
+
+        float tile_zoomx = (zoomx * main_w) / ibuf->x;
+        float tile_zoomy = (zoomy * main_h) / ibuf->y;
+        draw_image_buffer(C, sima, ar, scene, ibuf, x_pos, y_pos, tile_zoomx, tile_zoomy);
+        draw_udim_label(ar, x_pos, y_pos, label);
+      }
+      ED_space_image_release_buffer(sima, ibuf, lock);
+    }
+  }
+
+  draw_udim_tile_grids(ar, sima, ima);
+
   /* paint helpers */
   if (show_paint) {
     draw_image_paint_helpers(C, ar, scene, zoomx, zoomy);
index ec2b1cc7fbe58590bce517e9827767726932b785..c1ed049130e76eca0a7ce23f9b06b7153d8616e7 100644 (file)
@@ -136,7 +136,7 @@ void ED_space_image_set_mask(bContext *C, SpaceImage *sima, Mask *mask)
   }
 }
 
-ImBuf *ED_space_image_acquire_buffer(SpaceImage *sima, void **r_lock)
+ImBuf *ED_space_image_acquire_buffer(SpaceImage *sima, void **r_lock, int tile)
 {
   ImBuf *ibuf;
 
@@ -148,7 +148,9 @@ ImBuf *ED_space_image_acquire_buffer(SpaceImage *sima, void **r_lock)
     else
 #endif
     {
+      sima->iuser.tile = tile;
       ibuf = BKE_image_acquire_ibuf(sima->image, &sima->iuser, r_lock);
+      sima->iuser.tile = 0;
     }
 
     if (ibuf) {
@@ -179,7 +181,7 @@ bool ED_space_image_has_buffer(SpaceImage *sima)
   void *lock;
   bool has_buffer;
 
-  ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
   has_buffer = (ibuf != NULL);
   ED_space_image_release_buffer(sima, ibuf, lock);
 
@@ -192,7 +194,8 @@ void ED_space_image_get_size(SpaceImage *sima, int *width, int *height)
   ImBuf *ibuf;
   void *lock;
 
-  ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  /* TODO(lukas): Support tiled images with different sizes */
+  ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
 
   if (ibuf && ibuf->x > 0 && ibuf->y > 0) {
     *width = ibuf->x;
index f8ce065d46c6030ef70a2cdb913dd55c949fa51d..f3ec68db56212c046b47642ce66731809189884e 100644 (file)
@@ -89,6 +89,10 @@ void IMAGE_OT_read_viewlayers(struct wmOperatorType *ot);
 void IMAGE_OT_render_border(struct wmOperatorType *ot);
 void IMAGE_OT_clear_render_border(struct wmOperatorType *ot);
 
+void IMAGE_OT_tile_add(struct wmOperatorType *ot);
+void IMAGE_OT_tile_remove(struct wmOperatorType *ot);
+void IMAGE_OT_tile_fill(struct wmOperatorType *ot);
+
 /* image_panels.c */
 struct ImageUser *ntree_get_active_iuser(struct bNodeTree *ntree);
 void image_buttons_register(struct ARegionType *art);
index 8d17b703449cdae59d6b72a0467c9bd37a6f5dd2..4404f904891c736d1ee7a210d95f47eaad3599f2 100644 (file)
 #include "MEM_guardedalloc.h"
 
 #include "BLI_blenlib.h"
+#include "BLI_fileops.h"
+#include "BLI_fileops_types.h"
 #include "BLI_ghash.h"
+#include "BLI_linklist.h"
 #include "BLI_math.h"
 #include "BLI_string.h"
 #include "BLI_utildefines.h"
@@ -1264,6 +1267,51 @@ static int image_cmp_frame(const void *a, const void *b)
   return 0;
 }
 
+static int image_get_udim(const char *filepath, LinkNodePair *udim_tiles)
+{
+  char filename[FILE_MAX], dirname[FILE_MAXDIR];
+  BLI_split_dirfile(filepath, dirname, filename, sizeof(dirname), sizeof(filename));
+
+  if (strstr(filename, "1001") == NULL) {
+    return 0;
+  }
+
+  bool is_udim = true;
+  int max_udim = 0;
+
+  unsigned short digits;
+  char base_head[FILE_MAX], base_tail[FILE_MAX];
+  int id = BLI_stringdec(filename, base_head, base_tail, &digits);
+  if (id == 1001) {
+    struct direntry *dir;
+    uint totfile = BLI_filelist_dir_contents(dirname, &dir);
+    for (int i = 0; i < totfile; i++) {
+      if (!(dir[i].type & S_IFREG)) {
+        continue;
+      }
+      char head[FILE_MAX], tail[FILE_MAX];
+      id = BLI_stringdec(dir[i].relname, head, tail, &digits);
+
+      if (digits > 4 || !(STREQLEN(base_head, head, FILE_MAX)) ||
+          !(STREQLEN(base_tail, tail, FILE_MAX))) {
+        continue;
+      }
+
+      if (id < 1001 || id >= 2000) {
+        is_udim = false;
+        break;
+      }
+
+      BLI_linklist_append(udim_tiles, POINTER_FROM_INT(id));
+      max_udim = max_ii(max_udim, id);
+    }
+
+    BLI_filelist_free(dir, totfile);
+  }
+
+  return is_udim ? (max_udim - 1001) : 0;
+}
+
 /**
  * Return the start (offset) and the length of the sequence of
  * continuous frames in the list of frames.
@@ -1272,21 +1320,27 @@ static int image_cmp_frame(const void *a, const void *b)
  * \param ofs: [out] offset the first frame number in the sequence.
  * \return the number of contiguous frames in the sequence
  */
-static int image_sequence_get_len(ListBase *frames, int *ofs)
+static int image_sequence_get_len(ImageFrameRange *frame_range, int *ofs, LinkNodePair *udim_tiles)
 {
   ImageFrame *frame;
 
-  BLI_listbase_sort(frames, image_cmp_frame);
+  BLI_listbase_sort(&frame_range->frames, image_cmp_frame);
 
-  frame = frames->first;
-  if (frame) {
+  frame = frame_range->frames.first;
+  if (frame != NULL) {
     int frame_curr = frame->framenr;
     (*ofs) = frame_curr;
-    while (frame && (frame->framenr == frame_curr)) {
-      frame_curr++;
-      frame = frame->next;
+
+    if (udim_tiles != NULL && (frame_curr == 1001)) {
+      return 1 + image_get_udim(frame_range->filepath, udim_tiles);
+    }
+    else {
+      while (frame != NULL && (frame->framenr == frame_curr)) {
+        frame_curr++;
+        frame = frame->next;
+      }
+      return frame_curr - (*ofs);
     }
-    return frame_curr - (*ofs);
   }
   *ofs = 0;
   return 0;
@@ -1298,7 +1352,9 @@ static Image *image_open_single(Main *bmain,
                                 const char *relbase,
                                 bool is_relative_path,
                                 bool use_multiview,
-                                int frame_seq_len)
+                                int frame_seq_len,
+                                int frame_seq_ofs,
+                                LinkNodePair *udim_tiles)
 {
   bool exists = false;
   Image *ima = NULL;
@@ -1339,7 +1395,15 @@ static Image *image_open_single(Main *bmain,
     }
 
     if ((frame_seq_len > 1) && (ima->source == IMA_SRC_FILE)) {
-      ima->source = IMA_SRC_SEQUENCE;
+      if (udim_tiles && frame_seq_ofs == 1001) {
+        ima->source = IMA_SRC_TILED;
+        for (LinkNode *node = udim_tiles->list; node; node = node->next) {
+          BKE_image_add_tile(ima, POINTER_AS_INT(node->link), NULL);
+        }
+      }
+      else {
+        ima->source = IMA_SRC_SEQUENCE;
+      }
     }
   }
 
@@ -1358,9 +1422,11 @@ static int image_open_exec(bContext *C, wmOperator *op)
   char filepath[FILE_MAX];
   int frame_seq_len = 0;
   int frame_ofs = 1;
+  LinkNodePair udim_tiles = {NULL};
 
   const bool is_relative_path = RNA_boolean_get(op->ptr, "relative_path");
   const bool use_multiview = RNA_boolean_get(op->ptr, "use_multiview");
+  const bool use_udim = RNA_boolean_get(op->ptr, "use_udim_detecting");
 
   if (!op->customdata) {
     image_open_init(C, op);
@@ -1378,7 +1444,10 @@ static int image_open_exec(bContext *C, wmOperator *op)
     for (ImageFrameRange *frame_range = frame_ranges_all.first; frame_range;
          frame_range = frame_range->next) {
       int frame_range_ofs;
-      int frame_range_seq_len = image_sequence_get_len(&frame_range->frames, &frame_range_ofs);
+
+      LinkNodePair *udim_tiles_ptr = use_udim ? (&udim_tiles) : NULL;
+      int frame_range_seq_len = image_sequence_get_len(
+          frame_range, &frame_range_ofs, udim_tiles_ptr);
       BLI_freelistN(&frame_range->frames);
 
       char filepath_range[FILE_MAX];
@@ -1394,7 +1463,9 @@ static int image_open_exec(bContext *C, wmOperator *op)
                                            BKE_main_blendfile_path(bmain),
                                            is_relative_path,
                                            use_multiview,
-                                           frame_range_seq_len);
+                                           frame_range_seq_len,
+                                           frame_range_ofs,
+                                           udim_tiles_ptr);
 
       /* take the first image */
       if ((ima == NULL) && ima_range) {
@@ -1407,10 +1478,31 @@ static int image_open_exec(bContext *C, wmOperator *op)
   }
   else {
     /* for drag & drop etc. */
-    ima = image_open_single(
-        bmain, op, filepath, BKE_main_blendfile_path(bmain), is_relative_path, use_multiview, 1);
+    frame_seq_len = 1;
+
+    if (use_udim) {
+      /* Try to find UDIM tiles corresponding to the image */
+      frame_seq_len = 1 + image_get_udim(filepath, &udim_tiles);
+
+      /* If we found something, mark the image as tiled. */
+      if (frame_seq_len > 1) {
+        frame_ofs = 1001;
+      }
+    }
+
+    ima = image_open_single(bmain,
+                            op,
+                            filepath,
+                            BKE_main_blendfile_path(bmain),
+                            is_relative_path,
+                            use_multiview,
+                            frame_seq_len,
+                            frame_ofs,
+                            &udim_tiles);
   }
 
+  BLI_linklist_free(udim_tiles.list, NULL);
+
   if (ima == NULL) {
     return OPERATOR_CANCELLED;
   }
@@ -1458,7 +1550,8 @@ static int image_open_exec(bContext *C, wmOperator *op)
 
   /* initialize because of new image */
   if (iuser) {
-    iuser->frames = frame_seq_len;
+    /* If the sequence was a tiled image, we only have one frame. */
+    iuser->frames = (ima->source == IMA_SRC_SEQUENCE) ? frame_seq_len : 1;
     iuser->sfra = 1;
     iuser->framenr = 1;
     if (ima->source == IMA_SRC_MOVIE) {
@@ -1604,6 +1697,11 @@ void IMAGE_OT_open(wmOperatorType *ot)
       true,
       "Detect Sequences",
       "Automatically detect animated sequences in selected images (based on file names)");
+  RNA_def_boolean(ot->srna,
+                  "use_udim_detecting",
+                  true,
+                  "Detect UDIMs",
+                  "Detect selected UDIM files and load all matching tiles");
 }
 
 /** \} */
@@ -1870,6 +1968,12 @@ static int image_save_options_init(Main *bmain,
         BLI_path_make_safe(opts->filepath);
         BLI_path_abs(opts->filepath, is_prev_save ? G.ima : BKE_main_blendfile_path(bmain));
       }
+
+      /* append UDIM numbering if not present */
+      if (ima->source == IMA_SRC_TILED && (BLI_stringdec(ima->name, NULL, NULL, NULL) != 1001)) {
+        int len = strlen(opts->filepath);
+        STR_CONCAT(opts->filepath, len, ".1001");
+      }
     }
 
     /* color management */
@@ -2608,13 +2712,23 @@ static int image_new_exec(bContext *C, wmOperator *op)
   RNA_float_get_array(op->ptr, "color", color);
   alpha = RNA_boolean_get(op->ptr, "alpha");
   stereo3d = RNA_boolean_get(op->ptr, "use_stereo_3d");
+  bool tiled = RNA_boolean_get(op->ptr, "tiled");
 
   if (!alpha) {
     color[3] = 1.0f;
   }
 
-  ima = BKE_image_add_generated(
-      bmain, width, height, name, alpha ? 32 : 24, floatbuf, gen_type, color, stereo3d, false);
+  ima = BKE_image_add_generated(bmain,
+                                width,
+                                height,
+                                name,
+                                alpha ? 32 : 24,
+                                floatbuf,
+                                gen_type,
+                                color,
+                                stereo3d,
+                                false,
+                                tiled);
 
   if (!ima) {
     image_new_free(op);
@@ -2698,6 +2812,9 @@ static void image_new_draw(bContext *UNUSED(C), wmOperator *op)
   uiItemL(col[0], "", ICON_NONE);
   uiItemR(col[1], &ptr, "float", 0, NULL, ICON_NONE);
 
+  uiItemL(col[0], "", ICON_NONE);
+  uiItemR(col[1], &ptr, "tiled", 0, NULL, ICON_NONE);
+
 #if 0
   if (is_multiview) {
     uiItemL(col[0], "", ICON_NONE);
@@ -2753,6 +2870,8 @@ void IMAGE_OT_new(wmOperatorType *ot)
   prop = RNA_def_boolean(
       ot->srna, "use_stereo_3d", 0, "Stereo 3D", "Create an image with left and right views");
   RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
+  prop = RNA_def_boolean(ot->srna, "tiled", 0, "Tiled", "Create a tiled image");
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
 #undef IMA_DEF_NAME
@@ -2783,7 +2902,7 @@ static int image_invert_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf);
+  ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, 0);
 
   if (is_paint) {
     ED_imapaint_clear_partial_redraw();
@@ -2927,7 +3046,7 @@ static int image_scale_exec(bContext *C, wmOperator *op)
     RNA_property_int_set_array(op->ptr, prop, size);
   }
 
-  ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf);
+  ED_image_undo_push_begin_with_image(op->type->name, ima, ibuf, 0);
 
   ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
   IMB_scaleImBuf(ibuf, size[0], size[1]);
@@ -2977,7 +3096,7 @@ static bool image_pack_test(bContext *C, wmOperator *op)
     return 0;
   }
 
-  if (ima->source == IMA_SRC_SEQUENCE || ima->source == IMA_SRC_MOVIE) {
+  if (ELEM(ima->source, IMA_SRC_SEQUENCE, IMA_SRC_MOVIE, IMA_SRC_TILED)) {
     BKE_report(op->reports, RPT_ERROR, "Packing movies or image sequences not supported");
     return 0;
   }
@@ -3045,7 +3164,7 @@ static int image_unpack_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  if (ima->source == IMA_SRC_SEQUENCE || ima->source == IMA_SRC_MOVIE) {
+  if (ELEM(ima->source, IMA_SRC_SEQUENCE, IMA_SRC_MOVIE, IMA_SRC_TILED)) {
     BKE_report(op->reports, RPT_ERROR, "Unpacking movies or image sequences not supported");
     return OPERATOR_CANCELLED;
   }
@@ -3078,7 +3197,7 @@ static int image_unpack_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSE
     return OPERATOR_CANCELLED;
   }
 
-  if (ima->source == IMA_SRC_SEQUENCE || ima->source == IMA_SRC_MOVIE) {
+  if (ELEM(ima->source, IMA_SRC_SEQUENCE, IMA_SRC_MOVIE, IMA_SRC_TILED)) {
     BKE_report(op->reports, RPT_ERROR, "Unpacking movies or image sequences not supported");
     return OPERATOR_CANCELLED;
   }
@@ -3210,9 +3329,12 @@ static void image_sample_draw(const bContext *C, ARegion *ar, void *arg_info)
 /* Returns color in linear space, matching ED_space_node_color_sample(). */
 bool ED_space_image_color_sample(SpaceImage *sima, ARegion *ar, int mval[2], float r_col[3])
 {
+  float uv[2];
+  UI_view2d_region_to_view(&ar->v2d, mval[0], mval[1], &uv[0], &uv[1]);
+  int tile = BKE_image_get_tile_from_pos(sima->image, uv, uv, NULL);
+
   void *lock;
-  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock);
-  float fx, fy;
+  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, tile);
   bool ret = false;
 
   if (ibuf == NULL) {
@@ -3220,12 +3342,10 @@ bool ED_space_image_color_sample(SpaceImage *sima, ARegion *ar, int mval[2], flo
     return false;
   }
 
-  UI_view2d_region_to_view(&ar->v2d, mval[0], mval[1], &fx, &fy);
-
-  if (fx >= 0.0f && fy >= 0.0f && fx < 1.0f && fy < 1.0f) {
+  if (uv[0] >= 0.0f && uv[1] >= 0.0f && uv[0] < 1.0f && uv[1] < 1.0f) {
     const float *fp;
     unsigned char *cp;
-    int x = (int)(fx * ibuf->x), y = (int)(fy * ibuf->y);
+    int x = (int)(uv[0] * ibuf->x), y = (int)(uv[1] * ibuf->y);
 
     CLAMP(x, 0, ibuf->x - 1);
     CLAMP(y, 0, ibuf->y - 1);
@@ -3326,10 +3446,15 @@ static void image_sample_apply(bContext *C, wmOperator *op, const wmEvent *event
 {
   SpaceImage *sima = CTX_wm_space_image(C);
   ARegion *ar = CTX_wm_region(C);
+  Image *image = ED_space_image(sima);
+
+  float uv[2];
+  UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &uv[0], &uv[1]);
+  int tile = BKE_image_get_tile_from_pos(sima->image, uv, uv, NULL);
+
   void *lock;
-  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, tile);
   ImageSampleInfo *info = op->customdata;
-  float fx, fy;
   Scene *scene = CTX_data_scene(C);
   CurveMapping *curve_mapping = scene->view_settings.curve_mapping;
 
@@ -3339,11 +3464,8 @@ static void image_sample_apply(bContext *C, wmOperator *op, const wmEvent *event
     return;
   }
 
-  UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &fx, &fy);
-
-  if (fx >= 0.0f && fy >= 0.0f && fx < 1.0f && fy < 1.0f) {
-    int x = (int)(fx * ibuf->x), y = (int)(fy * ibuf->y);
-    Image *image = ED_space_image(sima);
+  if (uv[0] >= 0.0f && uv[1] >= 0.0f && uv[0] < 1.0f && uv[1] < 1.0f) {
+    int x = (int)(uv[0] * ibuf->x), y = (int)(uv[1] * ibuf->y);
 
     CLAMP(x, 0, ibuf->x - 1);
     CLAMP(y, 0, ibuf->y - 1);
@@ -3551,18 +3673,25 @@ static int image_sample_line_exec(bContext *C, wmOperator *op)
   SpaceImage *sima = CTX_wm_space_image(C);
   ARegion *ar = CTX_wm_region(C);
   Scene *scene = CTX_data_scene(C);
+  Image *ima = ED_space_image(sima);
 
   int x_start = RNA_int_get(op->ptr, "xstart");
   int y_start = RNA_int_get(op->ptr, "ystart");
   int x_end = RNA_int_get(op->ptr, "xend");
   int y_end = RNA_int_get(op->ptr, "yend");
 
+  float uv1[2], uv2[2], ofs[2];
+  UI_view2d_region_to_view(&ar->v2d, x_start, y_start, &uv1[0], &uv1[1]);
+  UI_view2d_region_to_view(&ar->v2d, x_end, y_end, &uv2[0], &uv2[1]);
+
+  /* If the image has tiles, shift the positions accordingly. */
+  int tile = BKE_image_get_tile_from_pos(ima, uv1, uv1, ofs);
+  sub_v2_v2(uv2, ofs);
+
   void *lock;
-  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, tile);
   Histogram *hist = &sima->sample_line_hist;
 
-  float x1f, y1f, x2f, y2f;
-
   if (ibuf == NULL) {
     ED_space_image_release_buffer(sima, ibuf, lock);
     return OPERATOR_CANCELLED;
@@ -3573,13 +3702,8 @@ static int image_sample_line_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
-  UI_view2d_region_to_view(&ar->v2d, x_start, y_start, &x1f, &y1f);
-  UI_view2d_region_to_view(&ar->v2d, x_end, y_end, &x2f, &y2f);
-
-  hist->co[0][0] = x1f;
-  hist->co[0][1] = y1f;
-  hist->co[1][0] = x2f;
-  hist->co[1][1] = y2f;
+  copy_v2_v2(hist->co[0], uv1);
+  copy_v2_v2(hist->co[1], uv2);
 
   /* enable line drawing */
   hist->flag |= HISTO_FLAG_SAMPLELINE;
@@ -4085,3 +4209,248 @@ void IMAGE_OT_clear_render_border(wmOperatorType *ot)
 }
 
 /** \} */
+
+/* ********************* Add tile operator ****************** */
+
+static bool tile_poll(bContext *C)
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  return (ima != NULL && ima->source == IMA_SRC_TILED);
+}
+
+static int tile_add_exec(bContext *C, wmOperator *op)
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  int tile_number = RNA_int_get(op->ptr, "number");
+
+  char *label = RNA_string_get_alloc(op->ptr, "label", NULL, 0);
+
+  ImageTile *tile = BKE_image_add_tile(ima, tile_number, label);
+  MEM_freeN(label);
+
+  if (tile == NULL) {
+    return OPERATOR_CANCELLED;
+  }
+
+  ima->active_tile_index = BLI_findindex(&ima->tiles, tile);
+
+  WM_event_add_notifier(C, NC_IMAGE | ND_DRAW, NULL);
+
+  return OPERATOR_FINISHED;
+}
+
+static int tile_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  /* Find the first gap in tile numbers or the number after the last if
+   * no gap exists. */
+  int next_number = 0;
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    next_number = tile->tile_number + 1;
+    if (tile->next == NULL || tile->next->tile_number > next_number) {
+      break;
+    }
+  }
+
+  RNA_int_set(op->ptr, "number", next_number);
+  RNA_string_set(op->ptr, "label", "");
+
+  return WM_operator_props_dialog_popup(C, op, 5 * UI_UNIT_X, 5 * UI_UNIT_Y);
+}
+
+static void tile_add_draw(bContext *UNUSED(C), wmOperator *op)
+{
+  uiLayout *split, *col[2];
+  uiLayout *layout = op->layout;
+  PointerRNA ptr;
+
+  RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+  split = uiLayoutSplit(layout, 0.5f, false);
+  col[0] = uiLayoutColumn(split, false);
+  col[1] = uiLayoutColumn(split, false);
+
+  uiItemL(col[0], IFACE_("Number"), ICON_NONE);
+  uiItemR(col[1], &ptr, "number", 0, "", ICON_NONE);
+
+  uiItemL(col[0], IFACE_("Label"), ICON_NONE);
+  uiItemR(col[1], &ptr, "label", 0, "", ICON_NONE);
+}
+
+void IMAGE_OT_tile_add(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Add tile";
+  ot->description = "Adds a tile to the image";
+  ot->idname = "IMAGE_OT_tile_add";
+
+  /* api callbacks */
+  ot->poll = tile_poll;
+  ot->exec = tile_add_exec;
+  ot->invoke = tile_add_invoke;
+  ot->ui = tile_add_draw;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  RNA_def_int(
+      ot->srna, "number", 1002, 1001, INT_MAX, "Number", "UDIM number of the tile", 1001, 1099);
+  RNA_def_string(ot->srna, "label", NULL, 0, "Label", "Optional tile label");
+}
+
+/* ********************* Remove tile operator ****************** */
+
+static bool tile_remove_poll(bContext *C)
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  return (ima != NULL && ima->source == IMA_SRC_TILED && ima->active_tile_index != 0);
+}
+
+static int tile_remove_exec(bContext *C, wmOperator *UNUSED(op))
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  ImageTile *tile = BLI_findlink(&ima->tiles, ima->active_tile_index);
+  if (!BKE_image_remove_tile(ima, tile)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  /* Ensure that the active index is valid. */
+  ima->active_tile_index = min_ii(ima->active_tile_index, BLI_listbase_count(&ima->tiles) - 1);
+
+  WM_event_add_notifier(C, NC_IMAGE | ND_DRAW, NULL);
+
+  return OPERATOR_FINISHED;
+}
+
+void IMAGE_OT_tile_remove(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Remove tile";
+  ot->description = "Removes a tile from the image";
+  ot->idname = "IMAGE_OT_tile_remove";
+
+  /* api callbacks */
+  ot->poll = tile_remove_poll;
+  ot->exec = tile_remove_exec;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* ********************* Fill tile operator ****************** */
+
+static int tile_fill_exec(bContext *C, wmOperator *op)
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  float color[4];
+  RNA_float_get_array(op->ptr, "color", color);
+  int gen_type = RNA_enum_get(op->ptr, "generated_type");
+  int width = RNA_int_get(op->ptr, "width");
+  int height = RNA_int_get(op->ptr, "height");
+  bool is_float = RNA_boolean_get(op->ptr, "float");
+  int planes = RNA_boolean_get(op->ptr, "alpha") ? 32 : 24;
+
+  ImageTile *tile = BLI_findlink(&ima->tiles, ima->active_tile_index);
+  if (!BKE_image_fill_tile(ima, tile, width, height, color, gen_type, planes, is_float)) {
+    return OPERATOR_CANCELLED;
+  }
+
+  WM_event_add_notifier(C, NC_IMAGE | ND_DRAW, NULL);
+
+  return OPERATOR_FINISHED;
+}
+
+static int tile_fill_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+  Image *ima = CTX_data_edit_image(C);
+
+  /* Acquire first tile to get the defaults. */
+  ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
+  if (ibuf != NULL) {
+    RNA_int_set(op->ptr, "width", ibuf->x);
+    RNA_int_set(op->ptr, "height", ibuf->y);
+    RNA_boolean_set(op->ptr, "float", ibuf->rect_float != NULL);
+    RNA_boolean_set(op->ptr, "alpha", ibuf->planes > 24);
+    BKE_image_release_ibuf(ima, ibuf, NULL);
+  }
+
+  return WM_operator_props_dialog_popup(C, op, 15 * UI_UNIT_X, 5 * UI_UNIT_Y);
+}
+
+static void tile_fill_draw(bContext *UNUSED(C), wmOperator *op)
+{
+  uiLayout *split, *col[2];
+  uiLayout *layout = op->layout;
+  PointerRNA ptr;
+
+  RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+  /* copy of WM_operator_props_dialog_popup() layout */
+
+  split = uiLayoutSplit(layout, 0.5f, false);
+  col[0] = uiLayoutColumn(split, false);
+  col[1] = uiLayoutColumn(split, false);
+
+  uiItemL(col[0], IFACE_("Color"), ICON_NONE);
+  uiItemR(col[1], &ptr, "color", 0, "", ICON_NONE);
+
+  uiItemL(col[0], IFACE_("Width"), ICON_NONE);
+  uiItemR(col[1], &ptr, "width", 0, "", ICON_NONE);
+
+  uiItemL(col[0], IFACE_("Height"), ICON_NONE);
+  uiItemR(col[1], &ptr, "height", 0, "", ICON_NONE);
+
+  uiItemL(col[0], "", ICON_NONE);
+  uiItemR(col[1], &ptr, "alpha", 0, NULL, ICON_NONE);
+
+  uiItemL(col[0], IFACE_("Generated Type"), ICON_NONE);
+  uiItemR(col[1], &ptr, "generated_type", 0, "", ICON_NONE);
+
+  uiItemL(col[0], "", ICON_NONE);
+  uiItemR(col[1], &ptr, "float", 0, NULL, ICON_NONE);
+}
+
+void IMAGE_OT_tile_fill(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Fill tile";
+  ot->description = "Fill the current tile with a generated image";
+  ot->idname = "IMAGE_OT_tile_fill";
+
+  /* api callbacks */
+  ot->poll = tile_poll;
+  ot->exec = tile_fill_exec;
+  ot->invoke = tile_fill_invoke;
+  ot->ui = tile_fill_draw;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+  PropertyRNA *prop;
+  static float default_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+  prop = RNA_def_float_color(
+      ot->srna, "color", 4, NULL, 0.0f, FLT_MAX, "Color", "Default fill color", 0.0f, 1.0f);
+  RNA_def_property_subtype(prop, PROP_COLOR_GAMMA);
+  RNA_def_property_float_array_default(prop, default_color);
+  RNA_def_enum(ot->srna,
+               "generated_type",
+               rna_enum_image_generated_type_items,
+               IMA_GENTYPE_BLANK,
+               "Generated Type",
+               "Fill the image with a grid for UV map testing");
+  prop = RNA_def_int(ot->srna, "width", 1024, 1, INT_MAX, "Width", "Image width", 1, 16384);
+  RNA_def_property_subtype(prop, PROP_PIXEL);
+  prop = RNA_def_int(ot->srna, "height", 1024, 1, INT_MAX, "Height", "Image height", 1, 16384);
+  RNA_def_property_subtype(prop, PROP_PIXEL);
+
+  /* Only needed when filling the first tile. */
+  RNA_def_boolean(
+      ot->srna, "float", 0, "32 bit Float", "Create image with 32 bit floating point bit depth");
+  RNA_def_boolean(ot->srna, "alpha", 1, "Alpha", "Create an image with an alpha channel");
+}
index b6b32293cee2b677bd6a815f15772d7eea32b151..79aa4d2ed7f4e1cae582843785a22f067f15d425 100644 (file)
@@ -107,6 +107,7 @@ typedef struct PaintTile {
   struct PaintTile *next, *prev;
   Image *image;
   ImBuf *ibuf;
+  int tile_number;
   union {
     float *fp;
     uint *uint;
@@ -148,6 +149,7 @@ static void ptile_invalidate_list(ListBase *paint_tiles)
 void *ED_image_paint_tile_find(ListBase *paint_tiles,
                                Image *image,
                                ImBuf *ibuf,
+                               int tile_number,
                                int x_tile,
                                int y_tile,
                                ushort **r_mask,
@@ -155,7 +157,7 @@ void *ED_image_paint_tile_find(ListBase *paint_tiles,
 {
   for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) {
     if (ptile->x_tile == x_tile && ptile->y_tile == y_tile) {
-      if (ptile->image == image && ptile->ibuf == ibuf) {
+      if (ptile->image == image && ptile->ibuf == ibuf && ptile->tile_number == tile_number) {
         if (r_mask) {
           /* allocate mask if requested. */
           if (!ptile->mask) {
@@ -178,6 +180,7 @@ void *ED_image_paint_tile_push(ListBase *paint_tiles,
                                Image *image,
                                ImBuf *ibuf,
                                ImBuf **tmpibuf,
+                               int tile_number,
                                int x_tile,
                                int y_tile,
                                ushort **r_mask,
@@ -191,7 +194,8 @@ void *ED_image_paint_tile_push(ListBase *paint_tiles,
 
   /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */
   if (find_prev) {
-    void *data = ED_image_paint_tile_find(paint_tiles, image, ibuf, x_tile, y_tile, r_mask, true);
+    void *data = ED_image_paint_tile_find(
+        paint_tiles, image, ibuf, tile_number, x_tile, y_tile, r_mask, true);
     if (data) {
       return data;
     }
@@ -205,6 +209,7 @@ void *ED_image_paint_tile_push(ListBase *paint_tiles,
 
   ptile->image = image;
   ptile->ibuf = ibuf;
+  ptile->tile_number = tile_number;
 
   ptile->x_tile = x_tile;
   ptile->y_tile = y_tile;
@@ -259,7 +264,10 @@ static void ptile_restore_runtime_list(ListBase *paint_tiles)
 
   for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) {
     Image *image = ptile->image;
-    ImBuf *ibuf = BKE_image_acquire_ibuf(image, NULL, NULL);
+    ImageUser iuser;
+    BKE_imageuser_default(&iuser);
+    iuser.tile = ptile->tile_number;
+    ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, NULL);
     const bool has_float = (ibuf->rect_float != NULL);
 
     if (has_float) {
@@ -460,6 +468,8 @@ static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
     }
   }
 
+  BLI_assert(i == ubuf->tiles_len);
+
   IMB_freeImBuf(tmpibuf);
 }
 
@@ -514,13 +524,13 @@ typedef struct UndoImageHandle {
   /** Each undo handle refers to a single image which may have multiple buffers. */
   UndoRefID_Image image_ref;
 
+  /** Each tile of a tiled image has its own UndoImageHandle.
+   * The tile number of this IUser is used to distinguish them.
+   */
+  ImageUser iuser;
+
   /**
    * List of #UndoImageBuf's to support multiple buffers per image.
-   *
-   * \note To properly support multiple buffers per image
-   * we would need to store an #ImageUser for each #UndoImageBuf.
-   * since when restoring the image we use:
-   * `BKE_image_acquire_ibuf(image, NULL, NULL)`.
    */
   ListBase buffers;
 
@@ -533,7 +543,8 @@ static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
   for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
     /* Tiles only added to second set of tiles. */
     Image *image = uh->image_ref.ptr;
-    ImBuf *ibuf = BKE_image_acquire_ibuf(image, NULL, NULL);
+
+    ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, NULL);
     if (UNLIKELY(ibuf == NULL)) {
       CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2);
       continue;
@@ -626,40 +637,44 @@ static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBu
   return ubuf;
 }
 
-static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, const Image *image)
+static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles,
+                                               const Image *image,
+                                               int tile_number)
 {
   for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
-    if (STREQ(image->id.name + 2, uh->image_ref.name + 2)) {
+    if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) {
       return uh;
     }
   }
   return NULL;
 }
 
-static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image)
+static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
 {
   for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
-    if (image == uh->image_ref.ptr) {
+    if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) {
       return uh;
     }
   }
   return NULL;
 }
 
-static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image)
+static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, int tile_number)
 {
-  BLI_assert(uhandle_lookup(undo_handles, image) == NULL);
+  BLI_assert(uhandle_lookup(undo_handles, image, tile_number) == NULL);
   UndoImageHandle *uh = MEM_callocN(sizeof(*uh), __func__);
   uh->image_ref.ptr = image;
+  uh->iuser.ok = 1;
+  uh->iuser.tile = tile_number;
   BLI_addtail(undo_handles, uh);
   return uh;
 }
 
-static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image)
+static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, int tile_number)
 {
-  UndoImageHandle *uh = uhandle_lookup(undo_handles, image);
+  UndoImageHandle *uh = uhandle_lookup(undo_handles, image, tile_number);
   if (uh == NULL) {
-    uh = uhandle_add(undo_handles, image);
+    uh = uhandle_add(undo_handles, image, tile_number);
   }
   return uh;
 }
@@ -693,10 +708,11 @@ typedef struct ImageUndoStep {
  */
 static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev,
                                                 const Image *image,
+                                                int tile_number,
                                                 const UndoImageBuf *ubuf)
 {
   /* Use name lookup because because the pointer is cleared for previous steps. */
-  UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image);
+  UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number);
   if (uh_prev != NULL) {
     UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name);
     if (ubuf_reference) {
@@ -763,7 +779,7 @@ static bool image_undosys_step_encode(struct bContext *C,
     /* Initialize undo tiles from ptiles (if they exist). */
     for (PaintTile *ptile = us->paint_tiles.first, *ptile_next; ptile; ptile = ptile_next) {
       if (ptile->valid) {
-        UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image);
+        UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, ptile->tile_number);
         UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf);
 
         UndoImageTile *utile = MEM_callocN(sizeof(*utile), "UndoImageTile");
@@ -783,7 +799,7 @@ static bool image_undosys_step_encode(struct bContext *C,
     for (UndoImageHandle *uh = us->handles.first; uh; uh = uh->next) {
       for (UndoImageBuf *ubuf_pre = uh->buffers.first; ubuf_pre; ubuf_pre = ubuf_pre->next) {
 
-        ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, NULL, NULL);
+        ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, NULL);
 
         const bool has_float = ibuf->rect_float;
 
@@ -797,10 +813,10 @@ static bool image_undosys_step_encode(struct bContext *C,
         }
         else {
           /* Search for the previous buffer. */
-          UndoImageBuf *ubuf_reference = (us_reference ?
-                                              ubuf_lookup_from_reference(
-                                                  us_reference, uh->image_ref.ptr, ubuf_post) :
-                                              NULL);
+          UndoImageBuf *ubuf_reference =
+              (us_reference ? ubuf_lookup_from_reference(
+                                  us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) :
+                              NULL);
 
           int i = 0;
           for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) {
@@ -850,6 +866,8 @@ static bool image_undosys_step_encode(struct bContext *C,
               i += 1;
             }
           }
+          BLI_assert(i == ubuf_pre->tiles_len);
+          BLI_assert(i == ubuf_post->tiles_len);
         }
         BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, NULL);
       }
@@ -1026,11 +1044,15 @@ void ED_image_undo_push_begin(const char *name, int paint_mode)
   image_undo_push_begin(name, paint_mode);
 }
 
-void ED_image_undo_push_begin_with_image(const char *name, Image *image, ImBuf *ibuf)
+void ED_image_undo_push_begin_with_image(const char *name,
+                                         Image *image,
+                                         ImBuf *ibuf,
+                                         int tile_number)
 {
   ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D);
 
-  UndoImageHandle *uh = uhandle_ensure(&us->handles, image);
+  BLI_assert(BKE_image_get_tile(image, tile_number));
+  UndoImageHandle *uh = uhandle_ensure(&us->handles, image, tile_number);
   UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf);
   BLI_assert(ubuf_pre->post == NULL);
 
@@ -1038,9 +1060,9 @@ void ED_image_undo_push_begin_with_image(const char *name, Image *image, ImBuf *
   while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
     us_reference = (ImageUndoStep *)us_reference->step.prev;
   }
-  UndoImageBuf *ubuf_reference = (us_reference ?
-                                      ubuf_lookup_from_reference(us_reference, image, ubuf_pre) :
-                                      NULL);
+  UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference(
+                                                     us_reference, image, tile_number, ubuf_pre) :
+                                                 NULL);
 
   if (ubuf_reference) {
     memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len);
index a88ecc918681d7f2b52805028f9f7fcdd6d528da..f30c0e97cab269ad8d7d83912c29d4fcd7a34002 100644 (file)
@@ -134,6 +134,9 @@ static SpaceLink *image_new(const ScrArea *UNUSED(area), const Scene *UNUSED(sce
   BKE_scopes_new(&simage->scopes);
   simage->sample_line_hist.height = 100;
 
+  simage->tile_grid_shape[0] = 1;
+  simage->tile_grid_shape[1] = 1;
+
   /* tool header */
   ar = MEM_callocN(sizeof(ARegion), "tool header for image");
 
@@ -246,6 +249,10 @@ static void image_operatortypes(void)
   WM_operatortype_append(IMAGE_OT_read_viewlayers);
   WM_operatortype_append(IMAGE_OT_render_border);
   WM_operatortype_append(IMAGE_OT_clear_render_border);
+
+  WM_operatortype_append(IMAGE_OT_tile_add);
+  WM_operatortype_append(IMAGE_OT_tile_remove);
+  WM_operatortype_append(IMAGE_OT_tile_fill);
 }
 
 static void image_keymap(struct wmKeyConfig *keyconf)
@@ -783,7 +790,8 @@ static void image_buttons_region_draw(const bContext *C, ARegion *ar)
   SpaceImage *sima = CTX_wm_space_image(C);
   Scene *scene = CTX_data_scene(C);
   void *lock;
-  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  /* TODO(lukas): Support tiles in scopes? */
+  ImBuf *ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
   /* XXX performance regression if name of scopes category changes! */
   PanelCategoryStack *category = UI_panel_category_active_find(ar, "Scopes");
 
index 4dc0019978a4295227b320c5bd519e89d36b5983..a5363c7a42c0b2c70ea4cd542d66dfeea1641b5e 100644 (file)
@@ -142,7 +142,7 @@ typedef enum eGPUMaterialStatus {
 GPUNodeLink *GPU_attribute(CustomDataType type, const char *name);
 GPUNodeLink *GPU_constant(float *num);
 GPUNodeLink *GPU_uniform(float *num);
-GPUNodeLink *GPU_image(struct Image *ima, struct ImageUser *iuser);
+GPUNodeLink *GPU_image(struct Image *ima, struct ImageUser *iuser, int tile);
 GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *layer);
 GPUNodeLink *GPU_builtin(eGPUBuiltin builtin);
 
index 410e23c95764a621f245ad0924db67edade61083..23ea9a62ef8b36afce2103ba0d83cf6dcf80f706 100644 (file)
@@ -581,17 +581,19 @@ const char *GPU_builtin_name(eGPUBuiltin builtin)
 }
 
 /* assign only one texid per buffer to avoid sampling the same texture twice */
-static void codegen_set_texid(GHash *bindhash, GPUInput *input, int *texid, void *key)
+static void codegen_set_texid(GHash *bindhash, GPUInput *input, int *texid, void *key1, int key2)
 {
-  if (BLI_ghash_haskey(bindhash, key)) {
+  GHashPair pair = {key1, POINTER_FROM_INT(key2)};
+  if (BLI_ghash_haskey(bindhash, &pair)) {
     /* Reuse existing texid */
-    input->texid = POINTER_AS_INT(BLI_ghash_lookup(bindhash, key));
+    input->texid = POINTER_AS_INT(BLI_ghash_lookup(bindhash, &pair));
   }
   else {
     /* Allocate new texid */
     input->texid = *texid;
     (*texid)++;
     input->bindtex = true;
+    void *key = BLI_ghashutil_pairalloc(key1, POINTER_FROM_INT(key2));
     BLI_ghash_insert(bindhash, key, POINTER_FROM_INT(input->texid));
   }
 }
@@ -604,7 +606,7 @@ static void codegen_set_unique_ids(ListBase *nodes)
   GPUOutput *output;
   int id = 1, texid = 0;
 
-  bindhash = BLI_ghash_ptr_new("codegen_set_unique_ids1 gh");
+  bindhash = BLI_ghash_pair_new("codegen_set_unique_ids1 gh");
 
   for (node = nodes->first; node; node = node->next) {
     for (input = node->inputs.first; input; input = input->next) {
@@ -616,11 +618,11 @@ static void codegen_set_unique_ids(ListBase *nodes)
         input->bindtex = false;
         if (input->ima) {
           /* input is texture from image */
-          codegen_set_texid(bindhash, input, &texid, input->ima);
+          codegen_set_texid(bindhash, input, &texid, input->ima, input->image_tile);
         }
         else if (input->coba) {
           /* input is color band texture, check coba pointer */
-          codegen_set_texid(bindhash, input, &texid, input->coba);
+          codegen_set_texid(bindhash, input, &texid, input->coba, 0);
         }
         else {
           /* Either input->ima or input->coba should be non-NULL. */
@@ -635,7 +637,7 @@ static void codegen_set_unique_ids(ListBase *nodes)
     }
   }
 
-  BLI_ghash_free(bindhash, NULL, NULL);
+  BLI_ghash_free(bindhash, BLI_ghashutil_pairfree, NULL);
 }
 
 /**
@@ -1545,6 +1547,7 @@ static void gpu_node_input_link(GPUNode *node, GPUNodeLink *link, const eGPUType
       input->source = GPU_SOURCE_TEX;
       input->ima = link->ima;
       input->iuser = link->iuser;
+      input->image_tile = link->image_tile;
       break;
     case GPU_NODE_LINK_ATTR:
       input->source = GPU_SOURCE_ATTR;
@@ -1789,12 +1792,13 @@ GPUNodeLink *GPU_uniform(float *num)
   return link;
 }
 
-GPUNodeLink *GPU_image(Image *ima, ImageUser *iuser)
+GPUNodeLink *GPU_image(Image *ima, ImageUser *iuser, int tile)
 {
   GPUNodeLink *link = GPU_node_link_create();
   link->link_type = GPU_NODE_LINK_IMAGE_BLENDER;
   link->ima = ima;
   link->iuser = iuser;
+  link->image_tile = tile;
   return link;
 }
 
index 4e09f16ebf8bbb1d8e582070a0fd8c4c0d678d8a..0e6982c603edb46f5b4d1552b524f1c83cb38a64 100644 (file)
@@ -99,6 +99,7 @@ struct GPUNodeLink {
     struct {
       struct Image *ima;
       struct ImageUser *iuser;
+      int image_tile;
     };
   };
 };
@@ -138,6 +139,7 @@ typedef struct GPUInput {
       struct ImageUser *iuser;  /* image user */
       bool bindtex;             /* input is responsible for binding the texture? */
       int texid;                /* number for multitexture, starting from zero */
+      int image_tile;           /* image tile */
       eGPUType textype;         /* texture type (2D, 1D Array ...) */
     };
     /* GPU_SOURCE_ATTR */
index 7fa2eb6424c2bb26e7d9f04c40b5efe3e9018387..2d70ce009e2c7fa9089a6f86f553a8a170142735 100644 (file)
@@ -195,13 +195,13 @@ float GPU_get_anisotropic(void)
 
 /* Set OpenGL state for an MTFace */
 
-static GPUTexture **gpu_get_image_gputexture(Image *ima, GLenum textarget)
+static GPUTexture **gpu_get_tile_gputexture(ImageTile *tile, GLenum textarget)
 {
   if (textarget == GL_TEXTURE_2D) {
-    return &ima->gputexture[TEXTARGET_TEXTURE_2D];
+    return &tile->gputexture[TEXTARGET_TEXTURE_2D];
   }
   else if (textarget == GL_TEXTURE_CUBE_MAP) {
-    return &ima->gputexture[TEXTARGET_TEXTURE_CUBE_MAP];
+    return &tile->gputexture[TEXTARGET_TEXTURE_CUBE_MAP];
   }
 
   return NULL;
@@ -476,8 +476,19 @@ GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget
   /* Tag as in active use for garbage collector. */
   BKE_image_tag_time(ima);
 
+  ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+
+  if (tile == NULL) {
+    /* TODO(lukas): When a tile gets deleted, the materials using the image
+     * aren't rebuilt and therefore continue to use it.
+     * This workaround isn't ideal, the result should be a pink color
+     * (for a missing tile). With the current behaviour, new tiles also won't
+     * be detected. */
+    tile = BKE_image_get_tile(ima, 0);
+  }
+
   /* Test if we already have a texture. */
-  GPUTexture **tex = gpu_get_image_gputexture(ima, textarget);
+  GPUTexture **tex = gpu_get_tile_gputexture(tile, textarget);
   if (*tex) {
     return *tex;
   }
@@ -485,7 +496,7 @@ GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget
   /* Check if we have a valid image. If not, we return a dummy
    * texture with zero bindcode so we don't keep trying. */
   uint bindcode = 0;
-  if (ima->ok == 0) {
+  if (tile->ok == 0) {
     *tex = GPU_texture_from_bindcode(textarget, bindcode);
     return *tex;
   }
@@ -861,11 +872,14 @@ void GPU_paint_set_mipmap(Main *bmain, bool mipmap)
     for (Image *ima = bmain->images.first; ima; ima = ima->id.next) {
       if (BKE_image_has_opengl_texture(ima)) {
         if (ima->gpuflag & IMA_GPU_MIPMAP_COMPLETE) {
-          if (ima->gputexture[TEXTARGET_TEXTURE_2D]) {
-            GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gpu_get_mipmap_filter(0));
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
-            GPU_texture_unbind(ima->gputexture[TEXTARGET_TEXTURE_2D]);
+          LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+            GPUTexture *tex = tile->gputexture[TEXTARGET_TEXTURE_2D];
+            if (tex != NULL) {
+              GPU_texture_bind(tex, 0);
+              glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gpu_get_mipmap_filter(0));
+              glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
+              GPU_texture_unbind(tex);
+            }
           }
         }
         else {
@@ -880,11 +894,14 @@ void GPU_paint_set_mipmap(Main *bmain, bool mipmap)
   else {
     for (Image *ima = bmain->images.first; ima; ima = ima->id.next) {
       if (BKE_image_has_opengl_texture(ima)) {
-        if (ima->gputexture[TEXTARGET_TEXTURE_2D]) {
-          GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
-          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
-          GPU_texture_unbind(ima->gputexture[TEXTARGET_TEXTURE_2D]);
+        LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+          GPUTexture *tex = tile->gputexture[TEXTARGET_TEXTURE_2D];
+          if (tex != NULL) {
+            GPU_texture_bind(tex, 0);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
+            GPU_texture_unbind(tex);
+          }
         }
       }
       else {
@@ -897,14 +914,16 @@ void GPU_paint_set_mipmap(Main *bmain, bool mipmap)
 void GPU_paint_update_image(Image *ima, ImageUser *iuser, int x, int y, int w, int h)
 {
   ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
+  ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser);
+  GPUTexture *tex = tile->gputexture[TEXTARGET_TEXTURE_2D];
 
-  if ((ima->gputexture[TEXTARGET_TEXTURE_2D] == NULL) || (ibuf == NULL) || (w == 0) || (h == 0)) {
+  if ((tex == NULL) || (ibuf == NULL) || (w == 0) || (h == 0)) {
     /* Full reload of texture. */
     GPU_free_image(ima);
   }
   else {
     /* Partial update of texture. */
-    GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
+    GPU_texture_bind(tex, 0);
 
     gpu_texture_update_from_ibuf(ima, ibuf, x, y, w, h);
 
@@ -915,7 +934,7 @@ void GPU_paint_update_image(Image *ima, ImageUser *iuser, int x, int y, int w, i
       ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
     }
 
-    GPU_texture_unbind(ima->gputexture[TEXTARGET_TEXTURE_2D]);
+    GPU_texture_unbind(tex);
   }
 
   BKE_image_release_ibuf(ima, ibuf, NULL);
@@ -1323,11 +1342,13 @@ void GPU_free_unused_buffers(Main *bmain)
 
 static void gpu_free_image_immediate(Image *ima)
 {
-  for (int i = 0; i < TEXTARGET_COUNT; i++) {
-    /* free glsl image binding */
-    if (ima->gputexture[i]) {
-      GPU_texture_free(ima->gputexture[i]);
-      ima->gputexture[i] = NULL;
+  LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+    for (int i = 0; i < TEXTARGET_COUNT; i++) {
+      /* free glsl image binding */
+      if (tile->gputexture[i] != NULL) {
+        GPU_texture_free(tile->gputexture[i]);
+        tile->gputexture[i] = NULL;
+      }
     }
   }
 
index bc2bf998145320d36c977f0303150f24839159e3..fadb3b92df488216993ab31778164f9d12e8d579 100644 (file)
@@ -353,3 +353,68 @@ void node_tex_image_empty(vec3 co, out vec4 color, out float alpha)
   color = vec4(0.0);
   alpha = 0.0;
 }
+
+void node_tex_tile_map(vec3 co, out vec4 color, out vec3 map)
+{
+  float tx = floor(co.x);
+  float ty = floor(co.y);
+
+  if (tx < 0 || ty < 0 || tx >= 10)
+    map = vec3(0, 0, -1);
+  else
+    map = vec3(co.x - tx, co.y - ty, 1001 + 10 * ty + tx);
+
+  color = vec4(1.0, 0.0, 1.0, 1.0);
+}
+
+void node_tex_tile_linear(
+    vec3 map, float tile_id, sampler2D ima, vec4 in_color, out vec4 color, out float alpha)
+{
+  if (map.z == tile_id) {
+    vec3 co = map.xyy;
+    node_tex_image_linear(co, ima, color, alpha);
+  }
+  else {
+    color = in_color;
+    alpha = color.a;
+  }
+}
+
+void node_tex_tile_nearest(
+    vec3 map, float tile_id, sampler2D ima, vec4 in_color, out vec4 color, out float alpha)
+{
+  if (map.z == tile_id) {
+    vec3 co = map.xyy;
+    node_tex_image_nearest(co, ima, color, alpha);
+  }
+  else {
+    color = in_color;
+    alpha = color.a;
+  }
+}
+
+void node_tex_tile_cubic(
+    vec3 map, float tile_id, sampler2D ima, vec4 in_color, out vec4 color, out float alpha)
+{
+  if (map.z == tile_id) {
+    vec3 co = map.xyy;
+    node_tex_image_cubic(co, ima, color, alpha);
+  }
+  else {
+    color = in_color;
+    alpha = color.a;
+  }
+}
+
+void node_tex_tile_smart(
+    vec3 map, float tile_id, sampler2D ima, vec4 in_color, out vec4 color, out float alpha)
+{
+  if (map.z == tile_id) {
+    vec3 co = map.xyy;
+    node_tex_image_smart(co, ima, color, alpha);
+  }
+  else {
+    color = in_color;
+    alpha = color.a;
+  }
+}
index 25494df9c0009ed6a6b9689ad5dacdb341a55d38..84ad0724b1a34435c5f96e6acbefd49495567bd4 100644 (file)
@@ -57,6 +57,7 @@ void IMB_moviecache_set_priority_callback(struct MovieCache *cache,
 void IMB_moviecache_put(struct MovieCache *cache, void *userkey, struct ImBuf *ibuf);
 bool IMB_moviecache_put_if_possible(struct MovieCache *cache, void *userkey, struct ImBuf *ibuf);
 struct ImBuf *IMB_moviecache_get(struct MovieCache *cache, void *userkey);
+void IMB_moviecache_remove(struct MovieCache *cache, void *userkey);
 bool IMB_moviecache_has_frame(struct MovieCache *cache, void *userkey);
 void IMB_moviecache_free(struct MovieCache *cache);
 
index 3cb976a6d9fbd80f3d3645c80693635aaf1c098d..fbe074d0fd5841a9e014f5edafc5e77ba5d27ec2 100644 (file)
@@ -388,6 +388,14 @@ bool IMB_moviecache_put_if_possible(MovieCache *cache, void *userkey, ImBuf *ibu
   return result;
 }
 
+void IMB_moviecache_remove(MovieCache *cache, void *userkey)
+{
+  MovieCacheKey key;
+  key.cache_owner = cache;
+  key.userkey = userkey;
+  BLI_ghash_remove(cache->hash, &key, moviecache_keyfree, moviecache_valfree);
+}
+
 ImBuf *IMB_moviecache_get(MovieCache *cache, void *userkey)
 {
   MovieCacheKey key;
index 5e4ed16d28ee0aaefd40e19554d284e1353ee7e3..e975d7acd742951dee8cee30fd5ab0049d00b95f 100644 (file)
@@ -56,6 +56,9 @@ typedef struct ImageUser {
   short pass;
   char _pad1[2];
 
+  int tile;
+  int _pad2;
+
   /** Listbase indices, for menu browsing or retrieve buffer. */
   short multi_index, view, layer;
   short flag;
@@ -88,6 +91,19 @@ typedef struct RenderSlot {
   struct RenderResult *render;
 } RenderSlot;
 
+typedef struct ImageTile {
+  struct ImageTile *next, *prev;
+
+  /** Not written in file 2 = TEXTARGET_COUNT. */
+  struct GPUTexture *gputexture[2];
+
+  char ok;
+  char _pad[3];
+
+  int tile_number;
+  char label[64];
+} ImageTile;
+
 /* iuser->flag */
 #define IMA_ANIM_ALWAYS (1 << 0)
 /* #define IMA_UNUSED_1         (1 << 1) */
@@ -109,8 +125,6 @@ typedef struct Image {
 
   /** Not written in file. */
   struct MovieCache *cache;
-  /** Not written in file 2 = TEXTARGET_COUNT. */
-  struct GPUTexture *gputexture[2];
 
   /* sources from: */
   ListBase anims;
@@ -134,8 +148,6 @@ typedef struct Image {
   struct PreviewImage *preview;
 
   int lastused;
-  short ok;
-  char _pad4[6];
 
   /* for generated images */
   int gen_x, gen_y;
@@ -150,12 +162,17 @@ typedef struct Image {
   ColorManagedColorspaceSettings colorspace_settings;
   char alpha_mode;
 
-  char _pad[5];
+  char _pad;
 
   /* Multiview */
   /** For viewer node stereoscopy. */
   char eye;
   char views_format;
+
+  /* ImageTile list for UDIMs. */
+  int active_tile_index;
+  ListBase tiles;
+
   /** ImageView. */
   ListBase views;
   struct Stereo3dFormat *stereo3d_format;
@@ -202,6 +219,7 @@ enum {
   IMA_SRC_MOVIE = 3,
   IMA_SRC_GENERATED = 4,
   IMA_SRC_VIEWER = 5,
+  IMA_SRC_TILED = 6,
 };
 
 /* Image.type, how to handle or generate the image */
index 6277dec64831e451bc42a9f2c59e423a43209a9b..2fb439c80741216370ce1a7d07828da35c1cd854 100644 (file)
@@ -1070,6 +1070,8 @@ typedef struct SpaceImage {
   char pixel_snap_mode;
   char _pad2[3];
 
+  int tile_grid_shape[2];
+
   MaskSpaceInfo mask_info;
 } SpaceImage;
 
index 517bd2b727637a78a976b4dc3ff3ebdbd51b8051..7a03bdb952b36a0e5640473d61f6845d86872de7 100644 (file)
@@ -57,6 +57,7 @@ static const EnumPropertyItem image_source_items[] = {
     {IMA_SRC_MOVIE, "MOVIE", 0, "Movie", "Movie file"},
     {IMA_SRC_GENERATED, "GENERATED", 0, "Generated", "Generated image"},
     {IMA_SRC_VIEWER, "VIEWER", 0, "Viewer", "Compositing node viewer"},
+    {IMA_SRC_TILED, "TILED", 0, "Tiled", "Tiled image texture"},
     {0, NULL, 0, NULL, NULL},
 };
 
@@ -209,6 +210,7 @@ static const EnumPropertyItem *rna_Image_source_itemf(bContext *UNUSED(C),
     RNA_enum_items_add_value(&item, &totitem, image_source_items, IMA_SRC_SEQUENCE);
     RNA_enum_items_add_value(&item, &totitem, image_source_items, IMA_SRC_MOVIE);
     RNA_enum_items_add_value(&item, &totitem, image_source_items, IMA_SRC_GENERATED);
+    RNA_enum_items_add_value(&item, &totitem, image_source_items, IMA_SRC_TILED);
   }
 
   RNA_enum_item_end(&item, &totitem);
@@ -238,6 +240,87 @@ static void rna_Image_file_format_set(PointerRNA *ptr, int value)
   }
 }
 
+static void rna_UDIMTile_label_get(PointerRNA *ptr, char *value)
+{
+  ImageTile *tile = (ImageTile *)ptr->data;
+  Image *image = (Image *)ptr->owner_id;
+
+  /* We don't know the length of the target string here, so we assume
+   * that it has been allocated according to what rna_UDIMTile_label_length returned. */
+  BKE_image_get_tile_label(image, tile, value, sizeof(tile->label));
+}
+
+static int rna_UDIMTile_label_length(PointerRNA *ptr)
+{
+  ImageTile *tile = (ImageTile *)ptr->data;
+  Image *image = (Image *)ptr->owner_id;
+
+  char label[sizeof(tile->label)];
+  BKE_image_get_tile_label(image, tile, label, sizeof(label));
+
+  return strlen(label);
+}
+
+static void rna_UDIMTile_tile_number_set(PointerRNA *ptr, int value)
+{
+  ImageTile *tile = (ImageTile *)ptr->data;
+  Image *image = (Image *)ptr->owner_id;
+
+  /* The index of the first tile can't be changed. */
+  if (tile->tile_number == 1001) {
+    return;
+  }
+
+  /* Check that no other tile already has that number. */
+  ImageTile *cur_tile = BKE_image_get_tile(image, value);
+  if (cur_tile == NULL || cur_tile == tile) {
+    tile->tile_number = value;
+  }
+}
+
+static int rna_Image_active_tile_index_get(PointerRNA *ptr)
+{
+  Image *image = (Image *)ptr->data;
+  return image->active_tile_index;
+}
+
+static void rna_Image_active_tile_index_set(PointerRNA *ptr, int value)
+{
+  Image *image = (Image *)ptr->data;
+  int num_tiles = BLI_listbase_count(&image->tiles);
+
+  image->active_tile_index = min_ii(value, num_tiles - 1);
+}
+
+static void rna_Image_active_tile_index_range(
+    PointerRNA *ptr, int *min, int *max, int *UNUSED(softmin), int *UNUSED(softmax))
+{
+  Image *image = (Image *)ptr->data;
+  int num_tiles = BLI_listbase_count(&image->tiles);
+
+  *min = 0;
+  *max = max_ii(0, num_tiles - 1);
+}
+
+static PointerRNA rna_Image_active_tile_get(PointerRNA *ptr)
+{
+  Image *image = (Image *)ptr->data;
+  ImageTile *tile = BLI_findlink(&image->tiles, image->active_tile_index);
+
+  return rna_pointer_inherit_refine(ptr, &RNA_UDIMTile, tile);
+}
+
+static void rna_Image_active_tile_set(PointerRNA *ptr,
+                                      PointerRNA value,
+                                      struct ReportList *UNUSED(reports))
+{
+  Image *image = (Image *)ptr->data;
+  ImageTile *tile = (ImageTile *)value.data;
+  const int index = BLI_findindex(&image->tiles, tile);
+  if (index != -1)
+    image->active_tile_index = index;
+}
+
 static bool rna_Image_has_data_get(PointerRNA *ptr)
 {
   Image *image = (Image *)ptr->data;
@@ -301,7 +384,8 @@ static void rna_Image_resolution_set(PointerRNA *ptr, const float *values)
 static int rna_Image_bindcode_get(PointerRNA *ptr)
 {
   Image *ima = (Image *)ptr->data;
-  GPUTexture *tex = ima->gputexture[TEXTARGET_TEXTURE_2D];
+  ImageTile *tile = BKE_image_get_tile(ima, 0);
+  GPUTexture *tex = tile->gputexture[TEXTARGET_TEXTURE_2D];
   return (tex) ? GPU_texture_opengl_bindcode(tex) : 0;
 }
 
@@ -527,6 +611,23 @@ static void rna_render_slots_active_index_range(
   *max = max_ii(0, BLI_listbase_count(&image->renderslots) - 1);
 }
 
+static ImageTile *rna_UDIMTile_new(Image *image, int tile_number, const char *label)
+{
+  ImageTile *tile = BKE_image_add_tile(image, tile_number, label);
+
+  WM_main_add_notifier(NC_IMAGE | ND_DRAW, NULL);
+
+  return tile;
+}
+
+static void rna_UDIMTile_remove(Image *image, PointerRNA *ptr)
+{
+  ImageTile *tile = (ImageTile *)ptr->data;
+  BKE_image_remove_tile(image, tile);
+
+  WM_main_add_notifier(NC_IMAGE | ND_DRAW, NULL);
+}
+
 #else
 
 static void rna_def_imageuser(BlenderRNA *brna)
@@ -597,6 +698,11 @@ static void rna_def_imageuser(BlenderRNA *brna)
   RNA_def_property_int_sdna(prop, NULL, "view");
   RNA_def_property_clear_flag(prop, PROP_EDITABLE); /* image_multi_cb */
   RNA_def_property_ui_text(prop, "View", "View in multilayer image");
+
+  prop = RNA_def_property(srna, "tile", PROP_INT, PROP_UNSIGNED);
+  RNA_def_property_int_sdna(prop, NULL, "tile");
+  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+  RNA_def_property_ui_text(prop, "Tile", "Tile in tiled image");
 }
 
 /* image.packed_files */
@@ -676,6 +782,79 @@ static void rna_def_render_slots(BlenderRNA *brna, PropertyRNA *cprop)
   RNA_def_function_return(func, parm);
 }
 
+static void rna_def_udim_tile(BlenderRNA *brna)
+{
+  StructRNA *srna;
+  PropertyRNA *prop;
+
+  srna = RNA_def_struct(brna, "UDIMTile", NULL);
+  RNA_def_struct_sdna(srna, "ImageTile");
+  RNA_def_struct_ui_text(srna, "UDIM Tile", "Properties of the UDIM tile");
+
+  prop = RNA_def_property(srna, "label", PROP_STRING, PROP_NONE);
+  RNA_def_property_string_sdna(prop, NULL, "label");
+  RNA_def_property_ui_text(prop, "Label", "Tile label");
+  RNA_def_property_string_funcs(prop, "rna_UDIMTile_label_get", "rna_UDIMTile_label_length", NULL);
+  RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, NULL);
+
+  prop = RNA_def_property(srna, "number", PROP_INT, PROP_NONE);
+  RNA_def_property_int_sdna(prop, NULL, "tile_number");
+  RNA_def_property_ui_text(prop, "Number", "Number of the position that this tile covers");
+  RNA_def_property_int_funcs(prop, NULL, "rna_UDIMTile_tile_number_set", NULL);
+  RNA_def_property_update(prop, NC_IMAGE | ND_DISPLAY, NULL);
+}
+
+static void rna_def_udim_tiles(BlenderRNA *brna, PropertyRNA *cprop)
+{
+  StructRNA *srna;
+  PropertyRNA *prop;
+
+  FunctionRNA *func;
+  PropertyRNA *parm;
+
+  RNA_def_property_srna(cprop, "UDIMTiles");
+  srna = RNA_def_struct(brna, "UDIMTiles", NULL);
+  RNA_def_struct_sdna(srna, "Image");
+  RNA_def_struct_ui_text(srna, "UDIM Tiles", "Collection of UDIM tiles");
+
+  prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
+  RNA_def_property_int_sdna(prop, NULL, "active_tile_index");
+  RNA_def_property_int_funcs(prop,
+                             "rna_Image_active_tile_index_get",
+                             "rna_Image_active_tile_index_set",
+                             "rna_Image_active_tile_index_range");
+  RNA_def_property_ui_text(prop, "Active Tile Index", "Active index in tiles array");
+
+  prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
+  RNA_def_property_struct_type(prop, "UDIMTile");
+  RNA_def_property_pointer_funcs(
+      prop, "rna_Image_active_tile_get", "rna_Image_active_tile_set", NULL, NULL);
+  RNA_def_property_flag(prop, PROP_EDITABLE | PROP_NEVER_NULL);
+  RNA_def_property_ui_text(prop, "Active Image Tile", "Active Image Tile");
+
+  func = RNA_def_function(srna, "new", "rna_UDIMTile_new");
+  RNA_def_function_ui_description(func, "Add a tile to the image");
+  parm = RNA_def_int(
+      func, "tile_number", 1, 1, INT_MAX, "", "Number of the newly created tile", 1, 100);
+  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+  parm = RNA_def_string(func, "label", NULL, 0, "", "Optional label for the tile");
+  parm = RNA_def_pointer(func, "result", "UDIMTile", "", "Newly created image tile");
+  RNA_def_function_return(func, parm);
+
+  func = RNA_def_function(srna, "get", "BKE_image_get_tile");
+  RNA_def_function_ui_description(func, "Get a tile based on its tile number");
+  parm = RNA_def_int(func, "tile_number", 0, 0, INT_MAX, "", "Number of the tile", 0, 100);
+  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+  parm = RNA_def_pointer(func, "result", "UDIMTile", "", "The tile");
+  RNA_def_function_return(func, parm);
+
+  func = RNA_def_function(srna, "remove", "rna_UDIMTile_remove");
+  RNA_def_function_ui_description(func, "Remove an image tile");
+  parm = RNA_def_pointer(func, "tile", "UDIMTile", "", "Image tile to remove");
+  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
+  RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0);
+}
+
 static void rna_def_image(BlenderRNA *brna)
 {
   StructRNA *srna;
@@ -860,6 +1039,12 @@ static void rna_def_image(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Render Slots", "Render slots of the image");
   rna_def_render_slots(brna, prop);
 
+  prop = RNA_def_property(srna, "tiles", PROP_COLLECTION, PROP_NONE);
+  RNA_def_property_struct_type(prop, "UDIMTile");
+  RNA_def_property_collection_sdna(prop, NULL, "tiles", NULL);
+  RNA_def_property_ui_text(prop, "Image Tiles", "Tiles of the image");
+  rna_def_udim_tiles(brna, prop);
+
   /*
    * Image.has_data and Image.depth are temporary,
    * Update import_obj.py when they are replaced (Arystan)
@@ -954,6 +1139,7 @@ static void rna_def_image(BlenderRNA *brna)
 void RNA_def_image(BlenderRNA *brna)
 {
   rna_def_render_slot(brna);
+  rna_def_udim_tile(brna);
   rna_def_image(brna);
   rna_def_imageuser(brna);
   rna_def_image_packed_files(brna);
index 997a5f5ca453af7eab04b9b092b71f82acce83c3..c4ec0a84a2c5e46c3b93572c32e2789c04e5d9e7 100644 (file)
@@ -174,8 +174,9 @@ static void rna_Image_unpack(Image *image, Main *bmain, ReportList *reports, int
   if (!BKE_image_has_packedfile(image)) {
     BKE_report(reports, RPT_ERROR, "Image not packed");
   }
-  else if (BKE_image_is_animated(image)) {
-    BKE_report(reports, RPT_ERROR, "Unpacking movies or image sequences not supported");
+  else if (BKE_image_has_multiple_ibufs(image)) {
+    BKE_report(
+        reports, RPT_ERROR, "Unpacking movies, image sequences or tiled images not supported");
     return;
   }
   else {
@@ -215,11 +216,12 @@ static void rna_Image_scale(Image *image, ReportList *reports, int width, int he
   }
 }
 
-static int rna_Image_gl_load(Image *image, ReportList *reports, int frame)
+static int rna_Image_gl_load(Image *image, ReportList *reports, int frame, int tile_number)
 {
-  ImageUser iuser = {NULL};
+  ImageUser iuser;
+  BKE_imageuser_default(&iuser);
   iuser.framenr = frame;
-  iuser.ok = true;
+  iuser.tile = tile_number;
 
   GPUTexture *tex = GPU_texture_from_blender(image, &iuser, GL_TEXTURE_2D);
 
@@ -231,14 +233,15 @@ static int rna_Image_gl_load(Image *image, ReportList *reports, int frame)
   return GL_NO_ERROR;
 }
 
-static int rna_Image_gl_touch(Image *image, ReportList *reports, int frame)
+static int rna_Image_gl_touch(Image *image, ReportList *reports, int frame, int tile_number)
 {
   int error = GL_NO_ERROR;
 
   BKE_image_tag_time(image);
 
-  if (image->gputexture[TEXTARGET_TEXTURE_2D] == NULL) {
-    error = rna_Image_gl_load(image, reports, frame);
+  ImageTile *tile = BKE_image_get_tile(image, tile_number);
+  if (tile->gputexture[TEXTARGET_TEXTURE_2D] == NULL) {
+    error = rna_Image_gl_load(image, reports, frame, tile_number);
   }
 
   return error;
@@ -333,6 +336,7 @@ void RNA_api_image(StructRNA *srna)
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   RNA_def_int(
       func, "frame", 0, 0, INT_MAX, "Frame", "Frame of image sequence or movie", 0, INT_MAX);
+  RNA_def_int(func, "tile_number", 0, 0, INT_MAX, "Tile", "Tile of a tiled image", 0, INT_MAX);
   /* return value */
   parm = RNA_def_int(
       func, "error", 0, -INT_MAX, INT_MAX, "Error", "OpenGL error value", -INT_MAX, INT_MAX);
@@ -347,6 +351,7 @@ void RNA_api_image(StructRNA *srna)
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   RNA_def_int(
       func, "frame", 0, 0, INT_MAX, "Frame", "Frame of image sequence or movie", 0, INT_MAX);
+  RNA_def_int(func, "tile_number", 0, 0, INT_MAX, "Tile", "Tile of a tiled image", 0, INT_MAX);
   /* return value */
   parm = RNA_def_int(
       func, "error", 0, -INT_MAX, INT_MAX, "Error", "OpenGL error value", -INT_MAX, INT_MAX);
index 2c42dba91310740ec8e15753f7fa956e1be272e5..d85c5c5f24908cec3f06e59832b50a98bde84a46 100644 (file)
@@ -372,14 +372,24 @@ static Image *rna_Main_images_new(Main *bmain,
                                   bool alpha,
                                   bool float_buffer,
                                   bool stereo3d,
-                                  bool is_data)
+                                  bool is_data,
+                                  bool tiled)
 {
   char safe_name[MAX_ID_NAME - 2];
   rna_idname_validate(name, safe_name);
 
   float color[4] = {0.0, 0.0, 0.0, 1.0};
-  Image *image = BKE_image_add_generated(
-      bmain, width, height, safe_name, alpha ? 32 : 24, float_buffer, 0, color, stereo3d, is_data);
+  Image *image = BKE_image_add_generated(bmain,
+                                         width,
+                                         height,
+                                         safe_name,
+                                         alpha ? 32 : 24,
+                                         float_buffer,
+                                         0,
+                                         color,
+                                         stereo3d,
+                                         is_data,
+                                         tiled);
   id_us_min(&image->id);
   return image;
 }
@@ -1146,6 +1156,7 @@ void RNA_def_main_images(BlenderRNA *brna, PropertyRNA *cprop)
       func, "float_buffer", 0, "Float Buffer", "Create an image with floating point color");
   RNA_def_boolean(func, "stereo3d", 0, "Stereo 3D", "Create left and right views");
   RNA_def_boolean(func, "is_data", 0, "Is Data", "Create image with non-color data color space");
+  RNA_def_boolean(func, "tiled", 0, "Tiled", "Create a tiled image");
   /* return type */
   parm = RNA_def_pointer(func, "image", "Image", "", "New image data-block");
   RNA_def_function_return(func, parm);
index 3e6d7352d02aaf6282e79bafee66b74e03c8602d..4da6b8214fa33b7978d1368c94f4f17fdb236fbc 100644 (file)
@@ -1404,7 +1404,7 @@ static const EnumPropertyItem *rna_SpaceImageEditor_display_channels_itemf(
   void *lock;
   int zbuf, alpha, totitem = 0;
 
-  ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
 
   alpha = ibuf && (ibuf->channels == 4);
   zbuf = ibuf && (ibuf->zbuf || ibuf->zbuf_float || (ibuf->channels == 1));
@@ -1512,7 +1512,8 @@ static void rna_SpaceImageEditor_scopes_update(struct bContext *C, struct Pointe
   ImBuf *ibuf;
   void *lock;
 
-  ibuf = ED_space_image_acquire_buffer(sima, &lock);
+  /* TODO(lukas): Support tiles in scopes? */
+  ibuf = ED_space_image_acquire_buffer(sima, &lock, 0);
   if (ibuf) {
     ED_space_image_scopes_update(C, sima, ibuf, true);
     WM_main_add_notifier(NC_IMAGE, sima->image);
@@ -2803,6 +2804,15 @@ static void rna_def_space_image_uv(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Display Faces", "Display faces over the image");
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL);
 
+  prop = RNA_def_property(srna, "tile_grid_shape", PROP_INT, PROP_NONE);
+  RNA_def_property_int_sdna(prop, NULL, "tile_grid_shape");
+  RNA_def_property_array(prop, 2);
+  RNA_def_property_int_default(prop, 1);
+  RNA_def_property_range(prop, 1, 10);
+  RNA_def_property_ui_text(
+      prop, "Tile Grid Shape", "How many tiles will be shown in the background");
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, NULL);
+
   /* todo: move edge and face drawing options here from G.f */
 
   prop = RNA_def_property(srna, "pixel_snap_mode", PROP_ENUM, PROP_NONE);
index 6c380efe0b2ad222090568643bb265743c1af4c5..72ad5581050c8fc976229f7969e8b25793fa89af 100644 (file)
@@ -88,7 +88,7 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
              "node_tex_environment_equirectangular",
              in[0].link,
              GPU_constant(&clamp_size),
-             GPU_image(ima, iuser),
+             GPU_image(ima, iuser, 0),
              &in[0].link);
   }
   else {
@@ -103,7 +103,7 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
       GPU_link(mat,
                "node_tex_image_linear_no_mip",
                in[0].link,
-               GPU_image(ima, iuser),
+               GPU_image(ima, iuser, 0),
                &out[0].link,
                &outalpha);
       break;
@@ -111,13 +111,17 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
       GPU_link(mat,
                "node_tex_image_nearest",
                in[0].link,
-               GPU_image(ima, iuser),
+               GPU_image(ima, iuser, 0),
                &out[0].link,
                &outalpha);
       break;
     default:
-      GPU_link(
-          mat, "node_tex_image_cubic", in[0].link, GPU_image(ima, iuser), &out[0].link, &outalpha);
+      GPU_link(mat,
+               "node_tex_image_cubic",
+               in[0].link,
+               GPU_image(ima, iuser, 0),
+               &out[0].link,
+               &outalpha);
       break;
   }
 
index c81f4f9853f8ee3289e45ca66f206074199377f2..34a5e32349026b6cde23cd15442a96bbbf89e8a6 100644 (file)
@@ -74,6 +74,12 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
       "node_tex_image_cubic",
       "node_tex_image_smart",
   };
+  static const char *names_tiled[] = {
+      "node_tex_tile_linear",
+      "node_tex_tile_nearest",
+      "node_tex_tile_cubic",
+      "node_tex_tile_smart",
+  };
   static const char *names_box[] = {
       "tex_box_sample_linear",
       "tex_box_sample_nearest",
@@ -123,70 +129,89 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
 
   node_shader_gpu_tex_mapping(mat, node, in, out);
 
-  switch (tex->projection) {
-    case SHD_PROJ_FLAT:
-      if (do_texco_clip) {
-        /* This seems redundant, but is required to ensure the texco link
-         * is not freed by GPU_link, as it is still needed for GPU_stack_link.
-         * Intermediate links like this can only be used once and are then
-         * freed immediately, but if we make it the output link of a set_rgb
-         * node it will be kept and can be used multiple times. */
-        GPU_link(mat, "set_rgb", *texco, texco);
-        GPU_link(mat, "set_rgb", *texco, &input_coords);
-      }
-      if (do_texco_extend) {
-        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
-      }
-      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
-      break;
-
-    case SHD_PROJ_BOX:
-      vnor = GPU_builtin(GPU_WORLD_NORMAL);
-      ob_mat = GPU_builtin(GPU_OBJECT_MATRIX);
-      blend = GPU_uniform(&tex->projection_blend);
-      gpu_image = GPU_image(ima, iuser);
-
-      /* equivalent to normal_world_to_object */
-      GPU_link(mat, "normal_transform_transposed_m4v3", vnor, ob_mat, &norm);
-      GPU_link(mat, gpu_node_name, *texco, norm, GPU_image(ima, iuser), &col1, &col2, &col3);
-      GPU_stack_link(
-          mat, node, "node_tex_image_box", in, out, norm, col1, col2, col3, gpu_image, blend);
-      break;
-
-    case SHD_PROJ_SPHERE:
-      GPU_link(mat, "point_texco_remap_square", *texco, texco);
-      GPU_link(mat, "point_map_to_sphere", *texco, texco);
-      if (do_texco_clip) {
-        /* See SHD_PROJ_FLAT for explanation. */
-        GPU_link(mat, "set_rgb", *texco, texco);
-        GPU_link(mat, "set_rgb", *texco, &input_coords);
-      }
-      if (do_texco_extend) {
-        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
-      }
-      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
-      break;
+  if (ima->source == IMA_SRC_TILED) {
+    GPUNodeLink *map;
+    GPU_link(mat, "node_tex_tile_map", in[0].link, &out[0].link, &map);
+    /* This is not exactly great, but if we want to support different sizes per
+     * tile and older hardware, which rules out better methods like texture arrays. */
+    LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) {
+      float tile_number = tile->tile_number;
+      GPU_link(mat,
+               names_tiled[tex->interpolation],
+               map,
+               GPU_uniform(&tile_number),
+               GPU_image(ima, iuser, tile->tile_number),
+               out[0].link,
+               &out[0].link,
+               &out[1].link);
+    }
+  }
+  else {
+    switch (tex->projection) {
+      case SHD_PROJ_FLAT:
+        if (do_texco_clip) {
+          /* This seems redundant, but is required to ensure the texco link
+           * is not freed by GPU_link, as it is still needed for GPU_stack_link.
+           * Intermediate links like this can only be used once and are then
+           * freed immediately, but if we make it the output link of a set_rgb
+           * node it will be kept and can be used multiple times. */
+          GPU_link(mat, "set_rgb", *texco, texco);
+          GPU_link(mat, "set_rgb", *texco, &input_coords);
+        }
+        if (do_texco_extend) {
+          GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser, 0), texco);
+        }
+        GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, 0));
+        break;
+
+      case SHD_PROJ_BOX:
+        vnor = GPU_builtin(GPU_WORLD_NORMAL);
+        ob_mat = GPU_builtin(GPU_OBJECT_MATRIX);
+        blend = GPU_uniform(&tex->projection_blend);
+        gpu_image = GPU_image(ima, iuser, 0);
 
-    case SHD_PROJ_TUBE:
-      GPU_link(mat, "point_texco_remap_square", *texco, texco);
-      GPU_link(mat, "point_map_to_tube", *texco, texco);
+        /* equivalent to normal_world_to_object */
+        GPU_link(mat, "normal_transform_transposed_m4v3", vnor, ob_mat, &norm);
+        GPU_link(mat, gpu_node_name, *texco, norm, GPU_image(ima, iuser, 0), &col1, &col2, &col3);
+        GPU_stack_link(
+            mat, node, "node_tex_image_box", in, out, norm, col1, col2, col3, gpu_image, blend);
+        break;
+
+      case SHD_PROJ_SPHERE:
+        GPU_link(mat, "point_texco_remap_square", *texco, texco);
+        GPU_link(mat, "point_map_to_sphere", *texco, texco);