diff options
68 files changed, 2785 insertions, 819 deletions
diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp index 53f2fdb91b9..78fb49db6c8 100644 --- a/intern/cycles/blender/blender_session.cpp +++ b/intern/cycles/blender/blender_session.cpp @@ -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) { diff --git a/intern/cycles/blender/blender_session.h b/intern/cycles/blender/blender_session.h index 7445fb53458..2f25ec740f9 100644 --- a/intern/cycles/blender/blender_session.h +++ b/intern/cycles/blender/blender_session.h @@ -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, diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index c3564eac940..6bbc73f72ec 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -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 diff --git a/intern/cycles/blender/blender_sync.cpp b/intern/cycles/blender/blender_sync.cpp index bb52c740bfb..332ee3575c0 100644 --- a/intern/cycles/blender/blender_sync.cpp +++ b/intern/cycles/blender/blender_sync.cpp @@ -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; } diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h index cbe61e367fa..efed96ec9f5 100644 --- a/intern/cycles/blender/blender_util.h +++ b/intern/cycles/blender/blender_util.h @@ -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) diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h index c4d7164a4d8..fd2833ee687 100644 --- a/intern/cycles/kernel/svm/svm.h +++ b/intern/cycles/kernel/svm/svm.h @@ -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); diff --git a/intern/cycles/kernel/svm/svm_image.h b/intern/cycles/kernel/svm/svm_image.h index 64abdd2d8b3..90f1a7845c7 100644 --- a/intern/cycles/kernel/svm/svm_image.h +++ b/intern/cycles/kernel/svm/svm_image.h @@ -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)) diff --git a/intern/cycles/render/image.cpp b/intern/cycles/render/image.cpp index 03ecc9bce52..212a867f9cd 100644 --- a/intern/cycles/render/image.cpp +++ b/intern/cycles/render/image.cpp @@ -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), diff --git a/intern/cycles/render/image.h b/intern/cycles/render/image.h index 459cd8c056c..bc04a667953 100644 --- a/intern/cycles/render/image.h +++ b/intern/cycles/render/image.h @@ -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, diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp index dc3f7c8f8ac..06304205dc9 100644 --- a/intern/cycles/render/light.cpp +++ b/intern/cycles/render/light.cpp @@ -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); } diff --git a/intern/cycles/render/mesh.cpp b/intern/cycles/render/mesh.cpp index cffe2bfa70a..2bf1040455f 100644 --- a/intern/cycles/render/mesh.cpp +++ b/intern/cycles/render/mesh.cpp @@ -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); } } diff --git a/intern/cycles/render/mesh.h b/intern/cycles/render/mesh.h index 4a24a9c2656..c5be0ba60b9 100644 --- a/intern/cycles/render/mesh.h +++ b/intern/cycles/render/mesh.h @@ -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, diff --git a/intern/cycles/render/nodes.cpp b/intern/cycles/render/nodes.cpp index 5e12d79bc6b..b8847f92153 100644 --- a/intern/cycles/render/nodes.cpp +++ b/intern/cycles/render/nodes.cpp @@ -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(); diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h index 54124cd2175..a8fe7644957 100644 --- a/intern/cycles/render/nodes.h +++ b/intern/cycles/render/nodes.h @@ -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; }; diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp index 889552f49cd..91f02e42071 100644 --- a/intern/cycles/render/osl.cpp +++ b/intern/cycles/render/osl.cpp @@ -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; diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h index 17bf98a3433..62cbfebf7eb 100644 --- a/intern/cycles/render/osl.h +++ b/intern/cycles/render/osl.h @@ -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 diff --git a/intern/cycles/render/scene.h b/intern/cycles/render/scene.h index 45997bccf5d..f99510d2d42 100644 --- a/intern/cycles/render/scene.h +++ b/intern/cycles/render/scene.h @@ -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 ¶ms) diff --git a/intern/cycles/render/svm.cpp b/intern/cycles/render/svm.cpp index 8466742ae38..f42a2ea818d 100644 --- a/intern/cycles/render/svm.cpp +++ b/intern/cycles/render/svm.cpp @@ -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(); diff --git a/intern/cycles/render/svm.h b/intern/cycles/render/svm.h index d1534567bea..61923fc40ac 100644 --- a/intern/cycles/render/svm.h +++ b/intern/cycles/render/svm.h @@ -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; diff --git a/intern/cycles/test/render_graph_finalize_test.cpp b/intern/cycles/test/render_graph_finalize_test.cpp index ca93f8b02d0..ace4f29913b 100644 --- a/intern/cycles/test/render_graph_finalize_test.cpp +++ b/intern/cycles/test/render_graph_finalize_test.cpp @@ -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) { } diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index 8de12f759fc..5af9fed83f8 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -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, ) diff --git a/source/blender/blenfont/BLF_api.h b/source/blender/blenfont/BLF_api.h index bf0aa96df84..40abf93e944 100644 --- a/source/blender/blenfont/BLF_api.h +++ b/source/blender/blenfont/BLF_api.h @@ -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]); diff --git a/source/blender/blenfont/intern/blf.c b/source/blender/blenfont/intern/blf.c index 8e1ff77b1c7..10bb1bd3c9c 100644 --- a/source/blender/blenfont/intern/blf.c +++ b/source/blender/blenfont/intern/blf.c @@ -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); diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index 82c831ae8e0..e2bb1f988ca 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -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); diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c index fa7af53df2d..de7837cdd90 100644 --- a/source/blender/blenkernel/intern/bpath.c +++ b/source/blender/blenkernel/intern/bpath.c @@ -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) && diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 4c81bd4b019..fca81acf038 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -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. diff --git a/source/blender/blenkernel/intern/image_save.c b/source/blender/blenkernel/intern/image_save.c index cc621e8468c..4768957e2c8 100644 --- a/source/blender/blenkernel/intern/image_save.c +++ b/source/blender/blenkernel/intern/image_save.c @@ -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; +} diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c index 5fa3352d497..d69527e8626 100644 --- a/source/blender/blenkernel/intern/packedFile.c +++ b/source/blender/blenkernel/intern/packedFile.c @@ -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); } } diff --git a/source/blender/blenlib/BLI_math_vector.h b/source/blender/blenlib/BLI_math_vector.h index 9fbd0227b7e..b39e979ec47 100644 --- a/source/blender/blenlib/BLI_math_vector.h +++ b/source/blender/blenlib/BLI_math_vector.h @@ -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; diff --git a/source/blender/blenlib/intern/math_vector_inline.c b/source/blender/blenlib/intern/math_vector_inline.c index b4b53a1dd58..67bc5c2fa50 100644 --- a/source/blender/blenlib/intern/math_vector_inline.c +++ b/source/blender/blenlib/intern/math_vector_inline.c @@ -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)); diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c index e7a2390b2c4..2a51a57f887 100644 --- a/source/blender/blenloader/intern/readfile.c +++ b/source/blender/blenloader/intern/readfile.c @@ -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; + } } /** \} */ diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index 68ea821996f..5b063655f75 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -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; + } + } + } + } + } } } diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c index b3a16b1fb4d..f8ac4e4062c 100644 --- a/source/blender/blenloader/intern/writefile.c +++ b/source/blender/blenloader/intern/writefile.c @@ -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); diff --git a/source/blender/compositor/operations/COM_ViewerOperation.cpp b/source/blender/compositor/operations/COM_ViewerOperation.cpp index 3f7619523e3..b6caf52a9f7 100644 --- a/source/blender/compositor/operations/COM_ViewerOperation.cpp +++ b/source/blender/compositor/operations/COM_ViewerOperation.cpp @@ -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; } diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 4b3be3ab924..83b764317a9 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -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); } diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h index 69742af9f50..e6d8684a8b2 100644 --- a/source/blender/editors/include/ED_image.h +++ b/source/blender/editors/include/ED_image.h @@ -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); diff --git a/source/blender/editors/include/ED_paint.h b/source/blender/editors/include/ED_paint.h index fec4beea809..e15e4c45c4a 100644 --- a/source/blender/editors/include/ED_paint.h +++ b/source/blender/editors/include/ED_paint.h @@ -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, diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 4ca7c8f96ad..6a801fc9928 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -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); diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 2a9ebdfaea9..cc677f8f7cb 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -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); diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 9e9cfe1beed..acecbc47c28 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -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; + } } } } diff --git a/source/blender/editors/render/render_preview.c b/source/blender/editors/render/render_preview.c index 1fe299e58a9..0ed37bbc5af 100644 --- a/source/blender/editors/render/render_preview.c +++ b/source/blender/editors/render/render_preview.c @@ -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; } diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index 36a2b4c2893..e3070903ccc 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -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); diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c index 0b770f17314..de09a52258f 100644 --- a/source/blender/editors/sculpt_paint/paint_image.c +++ b/source/blender/editors/sculpt_paint/paint_image.c @@ -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); diff --git a/source/blender/editors/sculpt_paint/paint_image_2d.c b/source/blender/editors/sculpt_paint/paint_image_2d.c index 004caae8a00..06d79b8a49d 100644 --- a/source/blender/editors/sculpt_paint/paint_image_2d.c +++ b/source/blender/editors/sculpt_paint/paint_image_2d.c @@ -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(®ion[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( ®ion[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( ®ion[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(®ion[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, ®ion[a], curveb, texmaskb, frombuf, mask_max, blend, tilex, tiley, tilew, tileh); + s, tile, ®ion[a], frombuf, mask_max, blend, tilex, tiley, tilew, tileh); } else { Paint2DForeachData data; data.s = s; + data.tile = tile; data.region = ®ion[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); diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index c57490041bc..6a67c469955 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -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; } diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index 84665728e17..53beb981522 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -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, diff --git a/source/blender/editors/space_clip/clip_draw.c b/source/blender/editors/space_clip/clip_draw.c index 74cf28ce5d4..f9899135e2d 100644 --- a/source/blender/editors/space_clip/clip_draw.c +++ b/source/blender/editors/space_clip/clip_draw.c @@ -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) { diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c index 2d4ca6dc15a..9a633427d82 100644 --- a/source/blender/editors/space_image/image_draw.c +++ b/source/blender/editors/space_image/image_draw.c @@ -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); diff --git a/source/blender/editors/space_image/image_edit.c b/source/blender/editors/space_image/image_edit.c index ec2b1cc7fbe..c1ed049130e 100644 --- a/source/blender/editors/space_image/image_edit.c +++ b/source/blender/editors/space_image/image_edit.c @@ -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; diff --git a/source/blender/editors/space_image/image_intern.h b/source/blender/editors/space_image/image_intern.h index f8ce065d46c..f3ec68db562 100644 --- a/source/blender/editors/space_image/image_intern.h +++ b/source/blender/editors/space_image/image_intern.h @@ -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); diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index 8d17b703449..4404f904891 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -35,7 +35,10 @@ #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"); +} diff --git a/source/blender/editors/space_image/image_undo.c b/source/blender/editors/space_image/image_undo.c index b6b32293cee..79aa4d2ed7f 100644 --- a/source/blender/editors/space_image/image_undo.c +++ b/source/blender/editors/space_image/image_undo.c @@ -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); diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c index a88ecc91868..f30c0e97cab 100644 --- a/source/blender/editors/space_image/space_image.c +++ b/source/blender/editors/space_image/space_image.c @@ -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"); diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h index 4dc0019978a..a5363c7a42c 100644 --- a/source/blender/gpu/GPU_material.h +++ b/source/blender/gpu/GPU_material.h @@ -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); diff --git a/source/blender/gpu/intern/gpu_codegen.c b/source/blender/gpu/intern/gpu_codegen.c index 410e23c9576..23ea9a62ef8 100644 --- a/source/blender/gpu/intern/gpu_codegen.c +++ b/source/blender/gpu/intern/gpu_codegen.c @@ -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; } diff --git a/source/blender/gpu/intern/gpu_codegen.h b/source/blender/gpu/intern/gpu_codegen.h index 4e09f16ebf8..0e6982c603e 100644 --- a/source/blender/gpu/intern/gpu_codegen.h +++ b/source/blender/gpu/intern/gpu_codegen.h @@ -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 */ diff --git a/source/blender/gpu/intern/gpu_draw.c b/source/blender/gpu/intern/gpu_draw.c index 7fa2eb6424c..2d70ce009e2 100644 --- a/source/blender/gpu/intern/gpu_draw.c +++ b/source/blender/gpu/intern/gpu_draw.c @@ -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; + } } } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_tex_image.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_tex_image.glsl index bc2bf998145..fadb3b92df4 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_tex_image.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_tex_image.glsl @@ -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; + } +} diff --git a/source/blender/imbuf/IMB_moviecache.h b/source/blender/imbuf/IMB_moviecache.h index 25494df9c00..84ad0724b1a 100644 --- a/source/blender/imbuf/IMB_moviecache.h +++ b/source/blender/imbuf/IMB_moviecache.h @@ -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); diff --git a/source/blender/imbuf/intern/moviecache.c b/source/blender/imbuf/intern/moviecache.c index 3cb976a6d9f..fbe074d0fd5 100644 --- a/source/blender/imbuf/intern/moviecache.c +++ b/source/blender/imbuf/intern/moviecache.c @@ -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; diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h index 5e4ed16d28e..e975d7acd74 100644 --- a/source/blender/makesdna/DNA_image_types.h +++ b/source/blender/makesdna/DNA_image_types.h @@ -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 */ diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 6277dec6483..2fb439c8074 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -1070,6 +1070,8 @@ typedef struct SpaceImage { char pixel_snap_mode; char _pad2[3]; + int tile_grid_shape[2]; + MaskSpaceInfo mask_info; } SpaceImage; diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index 517bd2b7276..7a03bdb952b 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -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); diff --git a/source/blender/makesrna/intern/rna_image_api.c b/source/blender/makesrna/intern/rna_image_api.c index 997a5f5ca45..c4ec0a84a2c 100644 --- a/source/blender/makesrna/intern/rna_image_api.c +++ b/source/blender/makesrna/intern/rna_image_api.c @@ -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); diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c index 2c42dba9131..d85c5c5f249 100644 --- a/source/blender/makesrna/intern/rna_main_api.c +++ b/source/blender/makesrna/intern/rna_main_api.c @@ -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); diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 3e6d7352d02..4da6b8214fa 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -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); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_environment.c b/source/blender/nodes/shader/nodes/node_shader_tex_environment.c index 6c380efe0b2..72ad5581050 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_environment.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_environment.c @@ -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; } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_image.c b/source/blender/nodes/shader/nodes/node_shader_tex_image.c index c81f4f9853f..34a5e323490 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_image.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_image.c @@ -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); + 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, 0), texco); + } + GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, 0)); + break; + + case SHD_PROJ_TUBE: + GPU_link(mat, "point_texco_remap_square", *texco, texco); + GPU_link(mat, "point_map_to_tube", *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, 0), texco); + } + GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, 0)); + break; + } + + if (tex->projection != SHD_PROJ_BOX) { 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_node_name = names_clip[tex->interpolation]; + in[0].link = input_coords; + GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, 0), out[0].link); } - GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser)); - break; - } - - if (tex->projection != SHD_PROJ_BOX) { - if (do_texco_clip) { - gpu_node_name = names_clip[tex->interpolation]; - in[0].link = input_coords; - GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser), out[0].link); } } |