From 1ffb43fc69a8f22ac6dbe154fa889921d2535c11 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 15 Feb 2026 02:37:52 +0100 Subject: [PATCH] Refactor: Cycles: Move UDIM code out of SVM, refactor ImageHandle Refactor ImageHandle to used pointers to a new ImageSlot istead of slot indices. ImageSlot has subclasses ImageSingle and ImageUDIM, for individual images and UDIMs. ImageUDIM has a list of handles for ImageSingle. UDIM tile lookup was moved out of SVM and OSL code, and is now shared. Early loading of images for displacement and volumes was refactored. Now a set of ImageSingle pointers is collected, which are then loaded by the image manager. Pull Request: https://projects.blender.org/blender/blender/pulls/154668 --- intern/cycles/kernel/CMakeLists.txt | 1 + intern/cycles/kernel/data_arrays.h | 3 +- intern/cycles/kernel/device/cpu/image.h | 14 + intern/cycles/kernel/device/gpu/image.h | 14 + intern/cycles/kernel/osl/services.cpp | 48 +-- intern/cycles/kernel/osl/services.h | 15 +- intern/cycles/kernel/osl/services_gpu.h | 6 +- intern/cycles/kernel/svm/image.h | 68 +-- intern/cycles/kernel/util/image_2d.h | 48 +++ intern/cycles/kernel/util/image_3d.h | 2 +- intern/cycles/scene/devicescene.cpp | 3 +- intern/cycles/scene/devicescene.h | 1 + intern/cycles/scene/geometry.cpp | 33 +- intern/cycles/scene/geometry_attributes.cpp | 2 +- intern/cycles/scene/image.cpp | 434 +++++++++++--------- intern/cycles/scene/image.h | 120 ++++-- intern/cycles/scene/light.cpp | 2 +- intern/cycles/scene/object.cpp | 4 +- intern/cycles/scene/object.h | 2 +- intern/cycles/scene/osl.cpp | 26 +- intern/cycles/scene/osl.h | 9 +- intern/cycles/scene/scene.cpp | 2 +- intern/cycles/scene/shader_nodes.cpp | 75 ++-- intern/cycles/scene/shader_nodes.h | 6 + intern/cycles/scene/svm.cpp | 4 +- intern/cycles/scene/svm.h | 3 +- intern/cycles/scene/volume.cpp | 8 +- intern/cycles/scene/volume.h | 2 +- intern/cycles/util/types_image.h | 8 +- 29 files changed, 511 insertions(+), 452 deletions(-) create mode 100644 intern/cycles/kernel/util/image_2d.h diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 02b806e9871..be98f3ed81b 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -238,6 +238,7 @@ set(SRC_KERNEL_UTIL_HEADERS util/colorspace.h util/differential.h util/ies.h + util/image_2d.h util/image_3d.h util/lookup_table.h util/nanovdb.h diff --git a/intern/cycles/kernel/data_arrays.h b/intern/cycles/kernel/data_arrays.h index 52f9fd60625..08f4c94e515 100644 --- a/intern/cycles/kernel/data_arrays.h +++ b/intern/cycles/kernel/data_arrays.h @@ -88,7 +88,8 @@ KERNEL_DATA_ARRAY(int, volume_tree_root_ids) KERNEL_DATA_ARRAY(float, volume_step_size) /* image textures */ -KERNEL_DATA_ARRAY(KernelImageInfo, image_info) KERNEL_DATA_ARRAY(KernelImageTexture, image_textures) +KERNEL_DATA_ARRAY(KernelImageUDIM, image_texture_udims) +KERNEL_DATA_ARRAY(KernelImageInfo, image_info) #undef KERNEL_DATA_ARRAY diff --git a/intern/cycles/kernel/device/cpu/image.h b/intern/cycles/kernel/device/cpu/image.h index 0cedc90610b..db9a177368c 100644 --- a/intern/cycles/kernel/device/cpu/image.h +++ b/intern/cycles/kernel/device/cpu/image.h @@ -6,6 +6,7 @@ #include "kernel/device/cpu/compat.h" #include "kernel/device/cpu/globals.h" +#include "kernel/util/image_2d.h" #include "util/half.h" #include "util/types_image.h" @@ -372,6 +373,19 @@ ccl_device float4 kernel_image_interp(KernelGlobals kg, } } +ccl_device_forceinline float4 kernel_image_interp_with_udim(KernelGlobals kg, + ShaderData * /*sd*/, + const int udim_id, + float2 uv) +{ + const int image_texture_id = kernel_image_udim_map(kg, udim_id, uv); + if (image_texture_id == KERNEL_IMAGE_NONE) { + return IMAGE_MISSING_RGBA; + } + + return kernel_image_interp(kg, image_texture_id, uv.x, uv.y); +} + } /* Namespace. */ CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/device/gpu/image.h b/intern/cycles/kernel/device/gpu/image.h index 9083f50aff6..ea2c4c192bd 100644 --- a/intern/cycles/kernel/device/gpu/image.h +++ b/intern/cycles/kernel/device/gpu/image.h @@ -5,6 +5,7 @@ #pragma once #include "kernel/globals.h" +#include "kernel/util/image_2d.h" CCL_NAMESPACE_BEGIN @@ -126,4 +127,17 @@ ccl_device float4 kernel_image_interp(KernelGlobals kg, } } +ccl_device_forceinline float4 kernel_image_interp_with_udim(KernelGlobals kg, + ccl_private ShaderData * /*sd*/, + const int udim_id, + float2 uv) +{ + const int image_texture_id = kernel_image_udim_map(kg, udim_id, uv); + if (image_texture_id == KERNEL_IMAGE_NONE) { + return IMAGE_MISSING_RGBA; + } + + return kernel_image_interp(kg, image_texture_id, uv.x, uv.y); +} + CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 6c9202de418..229a98e862c 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -313,15 +313,15 @@ OSL::TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle( it.clear(); break; } - return reinterpret_cast( - OSL_TEXTURE_HANDLE_TYPE_SVM | it->second.svm_image_texture_ids[0].y); + return reinterpret_cast(OSL_TEXTURE_HANDLE_TYPE_SVM | + it->second.id); case OSLTextureHandle::IES: if (!it->second.handle.empty() && it->second.handle.get_manager() != image_manager) { it.clear(); break; } - return reinterpret_cast( - OSL_TEXTURE_HANDLE_TYPE_IES | it->second.svm_image_texture_ids[0].y); + return reinterpret_cast(OSL_TEXTURE_HANDLE_TYPE_IES | + it->second.id); case OSLTextureHandle::AO: return reinterpret_cast( OSL_TEXTURE_HANDLE_TYPE_AO_OR_BEVEL | 1); @@ -346,7 +346,7 @@ OSL::TextureSystem::TextureHandle *OSLRenderServices::get_texture_handle( } return reinterpret_cast(OSL_TEXTURE_HANDLE_TYPE_SVM | - handle.svm_image_texture_id()); + handle.kernel_id()); } bool OSLRenderServices::good(OSL::TextureSystem::TextureHandle *texture_handle) @@ -425,37 +425,8 @@ bool OSLRenderServices::texture(OSLUStringHash filename, break; } case OSLTextureHandle::SVM: { - int id = -1; - if (handle->svm_image_texture_ids[0].w == -1) { - /* Packed single texture. */ - id = handle->svm_image_texture_ids[0].y; - } - else { - /* Packed tiled texture. */ - const int tx = (int)s; - const int ty = (int)t; - const int tile = 1001 + 10 * ty + tx; - for (const int4 &tile_node : handle->svm_image_texture_ids) { - if (tile_node.x == tile) { - id = tile_node.y; - break; - } - if (tile_node.z == tile) { - id = tile_node.w; - break; - } - } - s -= tx; - t -= ty; - } - - float4 rgba; - if (id == -1) { - rgba = IMAGE_MISSING_RGBA; - } - else { - rgba = kernel_image_interp(kernel_globals, id, s, 1.0f - t); - } + const float4 rgba = kernel_image_interp_with_udim( + kernel_globals, sd, handle->id, make_float2(s, 1.0f - t)); result[0] = rgba[0]; if (nchannels > 1) { @@ -472,7 +443,7 @@ bool OSLRenderServices::texture(OSLUStringHash filename, } case OSLTextureHandle::IES: { /* IES light. */ - result[0] = kernel_ies_interp(kernel_globals, handle->svm_image_texture_ids[0].y, s, t); + result[0] = kernel_ies_interp(kernel_globals, handle->id, s, t); status = true; break; } @@ -558,10 +529,9 @@ bool OSLRenderServices::texture3d(OSLUStringHash filename, switch (texture_type) { case OSLTextureHandle::SVM: { /* Packed texture. */ - const int image_texture_id = handle->svm_image_texture_ids[0].y; const float3 P_float3 = make_float3(P.x, P.y, P.z); float4 rgba = kernel_image_interp_3d( - kernel_globals, globals->sd, image_texture_id, P_float3, INTERPOLATION_NONE, false); + kernel_globals, globals->sd, handle->id, P_float3, INTERPOLATION_NONE, false); result[0] = rgba[0]; if (nchannels > 1) { diff --git a/intern/cycles/kernel/osl/services.h b/intern/cycles/kernel/osl/services.h index 868b2c81a07..a1c07421fea 100644 --- a/intern/cycles/kernel/osl/services.h +++ b/intern/cycles/kernel/osl/services.h @@ -48,23 +48,14 @@ struct ThreadKernelGlobalsCPU; struct OSLTextureHandle { enum Type { OIIO, SVM, IES, BEVEL, AO }; - OSLTextureHandle(Type type, const vector &svm_image_texture_ids) - : type(type), svm_image_texture_ids(svm_image_texture_ids) - { - } + OSLTextureHandle(Type type, const int id = -1) : type(type), id(id) {} - OSLTextureHandle(Type type = OIIO, const int svm_image_texture_ids = -1) - : OSLTextureHandle(type, {make_int4(0, svm_image_texture_ids, -1, -1)}) - { - } - - OSLTextureHandle(const ImageHandle &handle) - : type(SVM), svm_image_texture_ids(handle.get_svm_image_texture_ids()), handle(handle) + OSLTextureHandle(const ImageHandle &handle) : type(SVM), id(handle.kernel_id()), handle(handle) { } Type type; - vector svm_image_texture_ids; + int id = -1; OSL::TextureSystem::TextureHandle *oiio_handle = nullptr; ColorSpaceProcessor *processor = nullptr; ImageHandle handle; diff --git a/intern/cycles/kernel/osl/services_gpu.h b/intern/cycles/kernel/osl/services_gpu.h index 1c69e9c073e..6cfa68999ac 100644 --- a/intern/cycles/kernel/osl/services_gpu.h +++ b/intern/cycles/kernel/osl/services_gpu.h @@ -283,7 +283,7 @@ ccl_device_extern bool rend_get_userdata(RSDeviceString name, return false; } -ccl_device_extern bool rs_texture(ccl_private ShaderGlobals * /*sg*/, +ccl_device_extern bool rs_texture(ccl_private ShaderGlobals *sg, RSDeviceString /*filename*/, ccl_private void *texture_handle, ccl_private void * /*texture_thread_info*/, @@ -305,7 +305,9 @@ ccl_device_extern bool rs_texture(ccl_private ShaderGlobals * /*sg*/, switch (type) { case OSL_TEXTURE_HANDLE_TYPE_SVM: { - const float4 rgba = kernel_image_interp(nullptr, image_texture_id, s, 1.0f - t); + ccl_private ShaderData *sd = sg->sd; + const float4 rgba = kernel_image_interp_with_udim( + nullptr, sd, image_texture_id, make_float2(s, 1.0f - t)); if (nchannels > 0) { result[0] = rgba.x; } diff --git a/intern/cycles/kernel/svm/image.h b/intern/cycles/kernel/svm/image.h index ac221c6be40..beab1614633 100644 --- a/intern/cycles/kernel/svm/image.h +++ b/intern/cycles/kernel/svm/image.h @@ -18,14 +18,10 @@ CCL_NAMESPACE_BEGIN -ccl_device float4 -svm_image_texture(KernelGlobals kg, const int id, const float x, float y, const uint flags) +ccl_device float4 svm_image_texture( + KernelGlobals kg, ccl_private ShaderData *sd, const int id, const float2 uv, const uint flags) { - if (id == -1) { - return IMAGE_MISSING_RGBA; - } - - float4 r = kernel_image_interp(kg, id, x, y); + float4 r = kernel_image_interp_with_udim(kg, sd, id, uv); const float alpha = r.w; if ((flags & NODE_IMAGE_ALPHA_UNASSOCIATE) && alpha != 1.0f && alpha != 0.0f) { @@ -47,7 +43,7 @@ ccl_device_inline float3 texco_remap_square(const float3 co) } ccl_device_noinline int svm_node_tex_image(KernelGlobals kg, - ccl_private ShaderData * /*sd*/, + ccl_private ShaderData *sd, ccl_private float *stack, const uint4 node, int offset) @@ -73,50 +69,8 @@ ccl_device_noinline int svm_node_tex_image(KernelGlobals kg, tex_co = make_float2(co.x, co.y); } - /* TODO(lukas): Consider moving tile information out of the SVM node. - * KernelImageInfo seems a reasonable candidate. */ - int id = -1; - const int num_nodes = (int)node.y; - if (num_nodes > 0) { - /* Remember the offset of the node following the tile nodes. */ - const int next_offset = offset + num_nodes; - - /* Find the tile that the UV lies in. */ - const int tx = (int)tex_co.x; - const int ty = (int)tex_co.y; - - /* Check that we're within a legitimate tile. */ - if (tx >= 0 && ty >= 0 && tx < 10) { - const int tile = 1001 + 10 * ty + tx; - - /* Find the index of the tile. */ - for (int i = 0; i < num_nodes; i++) { - const 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; - } - - const float4 f = svm_image_texture(kg, id, tex_co.x, tex_co.y, flags); + const int id = node.y; + const float4 f = svm_image_texture(kg, sd, id, tex_co, flags); if (stack_valid(out_offset)) { stack_store_float3(stack, out_offset, make_float3(f.x, f.y, f.z)); @@ -212,15 +166,15 @@ ccl_device_noinline void svm_node_tex_image_box(KernelGlobals kg, /* Map so that no textures are flipped, rotation is somewhat arbitrary. */ if (weight.x > 0.0f) { const float2 uv = make_float2((signed_N.x < 0.0f) ? 1.0f - co.y : co.y, co.z); - f += weight.x * svm_image_texture(kg, id, uv.x, uv.y, flags); + f += weight.x * svm_image_texture(kg, sd, id, uv, flags); } if (weight.y > 0.0f) { const float2 uv = make_float2((signed_N.y > 0.0f) ? 1.0f - co.x : co.x, co.z); - f += weight.y * svm_image_texture(kg, id, uv.x, uv.y, flags); + f += weight.y * svm_image_texture(kg, sd, id, uv, flags); } if (weight.z > 0.0f) { const float2 uv = make_float2((signed_N.z > 0.0f) ? 1.0f - co.y : co.y, co.x); - f += weight.z * svm_image_texture(kg, id, uv.x, uv.y, flags); + f += weight.z * svm_image_texture(kg, sd, id, uv, flags); } if (stack_valid(out_offset)) { @@ -232,7 +186,7 @@ ccl_device_noinline void svm_node_tex_image_box(KernelGlobals kg, } ccl_device_noinline void svm_node_tex_environment(KernelGlobals kg, - ccl_private ShaderData * /*sd*/, + ccl_private ShaderData *sd, ccl_private float *stack, const uint4 node) { @@ -257,7 +211,7 @@ ccl_device_noinline void svm_node_tex_environment(KernelGlobals kg, uv = direction_to_mirrorball(co); } - const float4 f = svm_image_texture(kg, id, uv.x, uv.y, flags); + const float4 f = svm_image_texture(kg, sd, id, uv, flags); if (stack_valid(out_offset)) { stack_store_float3(stack, out_offset, make_float3(f.x, f.y, f.z)); diff --git a/intern/cycles/kernel/util/image_2d.h b/intern/cycles/kernel/util/image_2d.h new file mode 100644 index 00000000000..c0cc962d1ab --- /dev/null +++ b/intern/cycles/kernel/util/image_2d.h @@ -0,0 +1,48 @@ +/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +#include "kernel/globals.h" +#include "kernel/sample/lcg.h" + +#include "util/atomic.h" +#include "util/defines.h" +#include "util/math_fast.h" +#include "util/types_image.h" + +CCL_NAMESPACE_BEGIN + +ccl_device_forceinline int kernel_image_udim_map(KernelGlobals kg, + const int id, + ccl_private float2 &uv) +{ + if (id >= 0) { + return id; + } + + const int tx = (int)uv.x; + const int ty = (int)uv.y; + if (tx < 0 || ty < 0 || tx >= 10) { + return KERNEL_IMAGE_NONE; + } + + int udim_id = -id - 1; + const int num_udims = kernel_data_fetch(image_texture_udims, udim_id++).tile; + const int tile = 1001 + 10 * ty + tx; + + for (int i = 0; i < num_udims; i++) { + const KernelImageUDIM udim = kernel_data_fetch(image_texture_udims, udim_id++); + if (udim.tile == tile) { + /* If we found the tile, offset the UVs to be relative to it. */ + uv.x -= tx; + uv.y -= ty; + return udim.image_texture_id; + } + } + + return KERNEL_IMAGE_NONE; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/util/image_3d.h b/intern/cycles/kernel/util/image_3d.h index 96d7f46c4c4..026c1170274 100644 --- a/intern/cycles/kernel/util/image_3d.h +++ b/intern/cycles/kernel/util/image_3d.h @@ -250,7 +250,7 @@ ccl_device float4 kernel_image_interp_3d(KernelGlobals kg, #else (void)kg; (void)sd; - (void)id; + (void)image_texture_id; (void)P; (void)interp; (void)stochastic; diff --git a/intern/cycles/scene/devicescene.cpp b/intern/cycles/scene/devicescene.cpp index d4be0430788..18ce94a41eb 100644 --- a/intern/cycles/scene/devicescene.cpp +++ b/intern/cycles/scene/devicescene.cpp @@ -58,7 +58,8 @@ DeviceScene::DeviceScene(Device *device) volume_tree_roots(device, "volume_tree_roots", MEM_GLOBAL), volume_tree_root_ids(device, "volume_tree_root_ids", MEM_GLOBAL), volume_step_size(device, "volume_step_size", MEM_GLOBAL), - image_textures(device, "image_textures", MEM_GLOBAL) + image_textures(device, "image_textures", MEM_GLOBAL), + image_texture_udims(device, "image_texture_udims", MEM_GLOBAL) { memset((void *)&data, 0, sizeof(data)); } diff --git a/intern/cycles/scene/devicescene.h b/intern/cycles/scene/devicescene.h index c8ac3d687ad..2bbf19b83a3 100644 --- a/intern/cycles/scene/devicescene.h +++ b/intern/cycles/scene/devicescene.h @@ -95,6 +95,7 @@ class DeviceScene { /* Image textures */ device_vector image_textures; + device_vector image_texture_udims; KernelData data; diff --git a/intern/cycles/scene/geometry.cpp b/intern/cycles/scene/geometry.cpp index d5f0e59fe6d..398288a2bc2 100644 --- a/intern/cycles/scene/geometry.cpp +++ b/intern/cycles/scene/geometry.cpp @@ -620,9 +620,8 @@ void GeometryManager::device_update_displacement_images(Device *device, Progress &progress) { progress.set_status("Updating Displacement Images"); - TaskPool pool; ImageManager *image_manager = scene->image_manager.get(); - set bump_images; + set bump_images; #ifdef WITH_OSL bool has_osl_node = false; #endif @@ -655,11 +654,8 @@ void GeometryManager::device_update_displacement_images(Device *device, } ImageSlotTextureNode *image_node = static_cast(node); - for (int i = 0; i < image_node->handle.num_svm_image_texture_ids(); i++) { - const int image_texture_id = image_node->handle.svm_image_texture_id(i); - if (image_texture_id != -1) { - bump_images.insert(image_texture_id); - } + if (!image_node->handle.empty()) { + image_node->handle.add_to_set(bump_images); } } } @@ -670,16 +666,11 @@ void GeometryManager::device_update_displacement_images(Device *device, /* If any OSL node is used for displacement, it may reference a texture. But it's * unknown which ones, so have to load them all. */ if (has_osl_node) { - OSLShaderManager::osl_image_slots(device, image_manager, bump_images); + OSLShaderManager::osl_image_handles(device, image_manager, bump_images); } #endif - for (const int image_texture_id : bump_images) { - pool.push([image_manager, device, scene, image_texture_id, &progress] { - image_manager->device_update_image_texture_id(device, scene, image_texture_id, progress); - }); - } - pool.wait_work(); + image_manager->device_load_images(device, scene, progress, bump_images); } void GeometryManager::device_update_volume_images(Device *device, Scene *scene, Progress &progress) @@ -687,7 +678,7 @@ void GeometryManager::device_update_volume_images(Device *device, Scene *scene, progress.set_status("Updating Volume Images"); TaskPool pool; ImageManager *image_manager = scene->image_manager.get(); - set volume_images; + set volume_images; for (Geometry *geom : scene->geometry) { if (!geom->is_modified()) { @@ -700,19 +691,13 @@ void GeometryManager::device_update_volume_images(Device *device, Scene *scene, } const ImageHandle &handle = attr.data_voxel(); - const int image_texture_id = handle.svm_image_texture_id(); - if (image_texture_id != -1) { - volume_images.insert(image_texture_id); + if (!handle.empty()) { + handle.add_to_set(volume_images); } } } - for (const int image_texture_id : volume_images) { - pool.push([image_manager, device, scene, image_texture_id, &progress] { - image_manager->device_update_image_texture_id(device, scene, image_texture_id, progress); - }); - } - pool.wait_work(); + image_manager->device_load_images(device, scene, progress, volume_images); } void GeometryManager::device_update(Device *device, diff --git a/intern/cycles/scene/geometry_attributes.cpp b/intern/cycles/scene/geometry_attributes.cpp index 29262979e33..ad9e7e92725 100644 --- a/intern/cycles/scene/geometry_attributes.cpp +++ b/intern/cycles/scene/geometry_attributes.cpp @@ -334,7 +334,7 @@ class AttributeTableBuilder { if (mattr->element & ATTR_ELEMENT_VOXEL) { /* store slot in offset value */ const ImageHandle &handle = mattr->data_voxel(); - offset = handle.svm_image_texture_id(); + offset = handle.kernel_id(); } else if (mattr->element & ATTR_ELEMENT_IS_BYTE) { offset = attr_uchar4.add(mattr->data_uchar4(), size, mattr->modified); diff --git a/intern/cycles/scene/image.cpp b/intern/cycles/scene/image.cpp index 4e5f4b918a5..b09280b22b7 100644 --- a/intern/cycles/scene/image.cpp +++ b/intern/cycles/scene/image.cpp @@ -11,9 +11,6 @@ #include "scene/stats.h" #include "util/colorspace.h" -#include "util/image.h" -#include "util/image_impl.h" -#include "util/log.h" #include "util/progress.h" #include "util/task.h" #include "util/types_image.h" @@ -26,31 +23,56 @@ CCL_NAMESPACE_BEGIN /* Image Handle */ -ImageHandle::ImageHandle() : manager(nullptr) {} +ImageHandle::ImageHandle() = default; + +ImageHandle::ImageHandle(ImageTexture *image_texture, ImageManager *manager) + : image_texture(image_texture), manager(manager) +{ + if (image_texture) { + image_texture->users++; + } +} ImageHandle::ImageHandle(const ImageHandle &other) - : image_texture_ids(other.image_texture_ids), is_tiled(other.is_tiled), manager(other.manager) + : image_texture(other.image_texture), manager(other.manager) { - /* Increase image user count. */ - for (const size_t image_texture_id : image_texture_ids) { - manager->add_image_user(image_texture_id); + if (image_texture) { + image_texture->users++; } } +ImageHandle::ImageHandle(ImageHandle &&other) noexcept + : image_texture(other.image_texture), manager(other.manager) +{ + other.image_texture = nullptr; + other.manager = nullptr; +} + ImageHandle &ImageHandle::operator=(const ImageHandle &other) { clear(); + image_texture = other.image_texture; manager = other.manager; - is_tiled = other.is_tiled; - image_texture_ids = other.image_texture_ids; - for (const size_t image_texture_id : image_texture_ids) { - manager->add_image_user(image_texture_id); + if (image_texture) { + image_texture->users++; } return *this; } +ImageHandle &ImageHandle::operator=(ImageHandle &&other) noexcept +{ + if (this != &other) { + clear(); + image_texture = other.image_texture; + manager = other.manager; + other.image_texture = nullptr; + other.manager = nullptr; + } + return *this; +} + ImageHandle::~ImageHandle() { clear(); @@ -58,110 +80,82 @@ ImageHandle::~ImageHandle() void ImageHandle::clear() { - for (const size_t image_texture_id : image_texture_ids) { - manager->remove_image_user(image_texture_id); + /* Don't remove immediately, rather do it all together later on. one of + * the reasons for this is that on shader changes we add and remove nodes + * that use them, but we do not want to reload the image all the time. */ + if (image_texture) { + assert(image_texture->users >= 1); + image_texture->users--; + if (image_texture->users == 0) { + manager->tag_update(); + } + image_texture = nullptr; } - image_texture_ids.clear(); manager = nullptr; } bool ImageHandle::empty() const { - return image_texture_ids.empty(); + return image_texture == nullptr; } int ImageHandle::num_tiles() const { - return (is_tiled) ? image_texture_ids.size() : 0; -} - -int ImageHandle::num_svm_image_texture_ids() const -{ - return image_texture_ids.size(); -} - -ImageMetaData ImageHandle::metadata() -{ - if (image_texture_ids.empty()) { - return ImageMetaData(); + if (image_texture && image_texture->type == ImageTexture::UDIM) { + ImageUDIM *udim = static_cast(image_texture); + return udim->tiles.size(); } - ImageManager::Image *img = manager->get_image_texture(image_texture_ids.front()); - manager->load_image_metadata(img); - return img->metadata; + return 0; } -int ImageHandle::svm_image_texture_id(const int image_texture_id_index) const +ImageMetaData ImageHandle::metadata(Progress &progress) { - if (image_texture_id_index >= image_texture_ids.size()) { - return -1; - } - - if (manager->osl_texture_system) { - ImageManager::Image *img = manager->get_image_texture( - image_texture_ids[image_texture_id_index]); - if (!img->loader->osl_filepath().empty()) { - return -1; + if (image_texture) { + if (image_texture->type == ImageTexture::SINGLE) { + ImageSingle *img = static_cast(image_texture); + manager->load_image_metadata(img, progress); + return img->metadata; + } + if (image_texture->type == ImageTexture::UDIM) { + ImageUDIM *udim = static_cast(image_texture); + return udim->tiles[0].second.metadata(progress); } } - return image_texture_ids[image_texture_id_index]; + return ImageMetaData(); } -vector ImageHandle::get_svm_image_texture_ids() const +int ImageHandle::kernel_id() const { - const size_t num_nodes = divide_up(image_texture_ids.size(), 2); - - vector svm_image_texture_ids; - svm_image_texture_ids.reserve(num_nodes); - for (size_t i = 0; i < num_nodes; i++) { - int4 node; - - size_t image_texture_id = image_texture_ids[2 * i]; - node.x = manager->get_image_texture(image_texture_id)->loader->get_tile_number(); - node.y = image_texture_id; - - if ((2 * i + 1) < image_texture_ids.size()) { - image_texture_id = image_texture_ids[2 * i + 1]; - node.z = manager->get_image_texture(image_texture_id)->loader->get_tile_number(); - node.w = image_texture_id; - } - else { - node.z = -1; - node.w = -1; - } - - svm_image_texture_ids.push_back(node); + if (!image_texture) { + return KERNEL_IMAGE_NONE; } - - return svm_image_texture_ids; + if (image_texture->type == ImageTexture::SINGLE) { + return static_cast(image_texture)->image_texture_id; + } + return static_cast(image_texture)->id; } -device_image *ImageHandle::image_memory() const +device_image *ImageHandle::vdb_image_memory() const { - if (image_texture_ids.empty()) { + if (image_texture == nullptr || image_texture->type != ImageTexture::SINGLE) { return nullptr; } - ImageManager::Image *img = manager->get_image_texture(image_texture_ids[0]); - return img ? img->mem : nullptr; + ImageSingle *img = static_cast(image_texture); + return img->vdb_memory; } VDBImageLoader *ImageHandle::vdb_loader() const { - if (image_texture_ids.empty()) { - return nullptr; - } - - ImageManager::Image *img = manager->get_image_texture(image_texture_ids[0]); - - if (img == nullptr) { + if (image_texture == nullptr || image_texture->type != ImageTexture::SINGLE) { return nullptr; } + ImageSingle *img = static_cast(image_texture); ImageLoader *loader = img->loader.get(); - if (loader == nullptr) { return nullptr; } @@ -180,10 +174,29 @@ ImageManager *ImageHandle::get_manager() const bool ImageHandle::operator==(const ImageHandle &other) const { - return manager == other.manager && is_tiled == other.is_tiled && - image_texture_ids == other.image_texture_ids; + return image_texture == other.image_texture && manager == other.manager; } +void ImageHandle::add_to_set(set &images) const +{ + if (empty()) { + return; + } + + if (image_texture->type == ImageTexture::SINGLE) { + images.insert(static_cast(image_texture)); + } + else { + for (const auto &tile : static_cast(image_texture)->tiles) { + images.insert(static_cast(tile.second.image_texture)); + } + } +} + +/* Image Single */ + +ImageSingle::~ImageSingle() = default; + /* Image Manager */ ImageManager::ImageManager(const DeviceInfo & /*info*/) @@ -195,8 +208,9 @@ ImageManager::ImageManager(const DeviceInfo & /*info*/) ImageManager::~ImageManager() { - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - assert(!images[image_texture_id]); + for (ImageSingle *img : images) { + assert(!img); + (void)img; } } @@ -211,8 +225,8 @@ bool ImageManager::set_animation_frame_update(const int frame) const thread_scoped_lock device_lock(images_mutex); animation_frame = frame; - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - if (images[image_texture_id] && images[image_texture_id]->params.animated) { + for (ImageSingle *img : images) { + if (img && img->params.animated) { return true; } } @@ -221,7 +235,7 @@ bool ImageManager::set_animation_frame_update(const int frame) return false; } -void ImageManager::load_image_metadata(Image *img) +void ImageManager::load_image_metadata(ImageSingle *img, Progress & /*progress*/) { if (!img->need_metadata) { return; @@ -255,30 +269,19 @@ void ImageManager::load_image_metadata(Image *img) ImageHandle ImageManager::add_image(const string &filename, const ImageParams ¶ms) { - const size_t image_texture_id = add_image_texture( - make_unique(filename), params, false); - - ImageHandle handle; - handle.image_texture_ids.push_back(image_texture_id); - handle.manager = this; - return handle; + ImageSingle *image = add_image_texture(make_unique(filename), params, false); + return ImageHandle(image, this); } ImageHandle ImageManager::add_image(const string &filename, const ImageParams ¶ms, const array &tiles) { - ImageHandle handle; - handle.manager = this; - handle.is_tiled = !tiles.empty(); - - if (!handle.is_tiled) { - const size_t image_texture_id = add_image_texture( - make_unique(filename), params, false); - handle.image_texture_ids.push_back(image_texture_id); - return handle; + if (tiles.empty()) { + return add_image(filename, params); } + vector> udim_tiles; for (const int tile : tiles) { string tile_filename = filename; @@ -290,57 +293,51 @@ ImageHandle ImageManager::add_image(const string &filename, const int v = ((tile - 1001) / 10); string_replace(tile_filename, "", string_printf("u%d_v%d", u + 1, v + 1)); - const size_t image_texture_id = add_image_texture( + ImageSingle *image = add_image_texture( make_unique(tile_filename), params, false); - handle.image_texture_ids.push_back(image_texture_id); + udim_tiles.emplace_back(tile, ImageHandle(image, this)); } - return handle; + ImageUDIM *udim = add_image_texture(std::move(udim_tiles)); + return ImageHandle(udim, this); } ImageHandle ImageManager::add_image(unique_ptr &&loader, const ImageParams ¶ms, const bool builtin) { - const size_t image_texture_id = add_image_texture(std::move(loader), params, builtin); - - ImageHandle handle; - handle.image_texture_ids.push_back(image_texture_id); - handle.manager = this; - return handle; + ImageSingle *image = add_image_texture(std::move(loader), params, builtin); + return ImageHandle(image, this); } ImageHandle ImageManager::add_image(vector> &&loaders, const ImageParams ¶ms) { - ImageHandle handle; - handle.is_tiled = true; + vector> udim_tiles; for (unique_ptr &loader : loaders) { unique_ptr local_loader; std::swap(loader, local_loader); - const size_t image_texture_id = add_image_texture(std::move(local_loader), params, true); - handle.image_texture_ids.push_back(image_texture_id); + ImageSingle *image = add_image_texture(std::move(local_loader), params, true); + udim_tiles.emplace_back(image->loader->get_tile_number(), ImageHandle(image, this)); } - handle.manager = this; - return handle; + ImageUDIM *udim = add_image_texture(std::move(udim_tiles)); + return ImageHandle(udim, this); } -size_t ImageManager::add_image_texture(unique_ptr &&loader, - const ImageParams ¶ms, - const bool builtin) +ImageSingle *ImageManager::add_image_texture(unique_ptr &&loader, + const ImageParams ¶ms, + const bool builtin) { - size_t image_texture_id; - const thread_scoped_lock device_lock(images_mutex); /* Find existing image. */ + size_t image_texture_id; for (image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - Image *img = images[image_texture_id].get(); + ImageSingle *img = images[image_texture_id]; if (img && ImageLoader::equals(img->loader.get(), loader.get()) && img->params == params) { - img->users++; - return image_texture_id; + return img; } } @@ -356,53 +353,58 @@ size_t ImageManager::add_image_texture(unique_ptr &&loader, } /* Add new image. */ - unique_ptr img = make_unique(); + unique_ptr img = make_unique(); + img->type = ImageTexture::SINGLE; img->params = params; img->loader = std::move(loader); - img->need_metadata = true; img->need_load = !(osl_texture_system && !img->loader->osl_filepath().empty()); + img->image_texture_id = (img->need_load) ? image_texture_id : KERNEL_IMAGE_NONE; img->builtin = builtin; - img->users = 1; - img->mem = nullptr; - images[image_texture_id] = std::move(img); + images.replace(image_texture_id, std::move(img)); - need_update_ = true; + tag_update(); - return image_texture_id; + return images[image_texture_id]; } -void ImageManager::add_image_user(const size_t image_texture_id) +ImageUDIM *ImageManager::add_image_texture(vector> &&tiles) { const thread_scoped_lock device_lock(images_mutex); - Image *image = images[image_texture_id].get(); - assert(image && image->users >= 1); - image->users++; -} - -void ImageManager::remove_image_user(const size_t image_texture_id) -{ - const thread_scoped_lock device_lock(images_mutex); - Image *image = images[image_texture_id].get(); - assert(image && image->users >= 1); - - /* decrement user count */ - image->users--; - - /* don't remove immediately, rather do it all together later on. one of - * the reasons for this is that on shader changes we add and remove nodes - * that use them, but we do not want to reload the image all the time. */ - if (image->users == 0) { - need_update_ = true; + /* Find existing UDIM. */ + size_t image_texture_id; + for (image_texture_id = 0; image_texture_id < image_udims.size(); image_texture_id++) { + ImageUDIM *udim = image_udims[image_texture_id]; + if (udim && udim->tiles == tiles) { + return udim; + } } -} -ImageManager::Image *ImageManager::get_image_texture(const size_t image_texture_id) -{ - /* Need mutex lock, images vector might get resized by another thread. */ - const thread_scoped_lock device_lock(images_mutex); - return images[image_texture_id].get(); + /* Find free image_texture_id. */ + for (image_texture_id = 0; image_texture_id < image_udims.size(); image_texture_id++) { + if (!image_udims[image_texture_id]) { + break; + } + } + + if (image_texture_id == image_udims.size()) { + image_udims.resize(image_udims.size() + 1); + } + + /* Add new image. */ + unique_ptr img = make_unique(); + img->type = ImageTexture::UDIM; + img->id = -num_udim_tiles - 1; + img->tiles = std::move(tiles); + + num_udim_tiles += img->tiles.size() + 1; + + image_udims.replace(image_texture_id, std::move(img)); + + tag_update(); + + return image_udims[image_texture_id]; } void ImageManager::device_resize_image_textures(Scene *scene) @@ -423,6 +425,7 @@ void ImageManager::device_copy_image_textures(Scene *scene) DeviceScene &dscene = scene->dscene; dscene.image_textures.copy_to_device_if_modified(); + dscene.image_texture_udims.copy_to_device_if_modified(); } void ImageManager::device_load_image(Device *device, @@ -434,15 +437,11 @@ void ImageManager::device_load_image(Device *device, return; } - Image *img = images[image_texture_id].get(); - - if (img->users == 0) { - return; - } + ImageSingle *img = images[image_texture_id]; progress.set_status("Updating Images", "Loading " + img->loader->name()); - load_image_metadata(img); + load_image_metadata(img, progress); KernelImageTexture tex; tex.width = img->metadata.width; @@ -452,7 +451,7 @@ void ImageManager::device_load_image(Device *device, tex.use_transform_3d = img->metadata.use_transform_3d; tex.transform_3d = img->metadata.transform_3d; - img->mem = image_cache.load_image_full( + img->vdb_memory = image_cache.load_image_full( *device, *img->loader, img->metadata, scene->params.texture_limit, tex); /* Update image texture device data. */ @@ -466,7 +465,7 @@ void ImageManager::device_load_image(Device *device, void ImageManager::device_free_image(Scene *scene, size_t image_texture_id) { - Image *img = images[image_texture_id].get(); + ImageSingle *img = images[image_texture_id]; if (img == nullptr) { return; } @@ -480,13 +479,44 @@ void ImageManager::device_free_image(Scene *scene, size_t image_texture_id) #endif } - if (img->mem) { - const KernelImageTexture &tex = scene->dscene.image_textures[image_texture_id]; - image_cache.free_image(scene->dscene, tex); - img->mem = nullptr; + const KernelImageTexture &tex = scene->dscene.image_textures[image_texture_id]; + image_cache.free_image(scene->dscene, tex); + img->vdb_memory = nullptr; + + images.steal(image_texture_id); +} + +void ImageManager::device_update_udims(Device * /*device*/, Scene *scene) +{ + const thread_scoped_lock device_lock(device_mutex); + device_vector &device_udims = scene->dscene.image_texture_udims; + if (device_udims.size() == num_udim_tiles) { + return; } - images[image_texture_id].reset(); + device_udims.resize(num_udim_tiles); + + for (auto [udim_id, udim] : image_udims.enumerate()) { + if (udim == nullptr) { + continue; + } + + if (udim->users == 0) { + image_udims.replace(udim_id, nullptr); + } + else if (udim->need_load) { + const uint udim_offset = -udim->id - 1; + KernelImageUDIM *udim_data = device_udims.data() + udim_offset; + + udim_data[0] = KernelImageUDIM{.tile = int(udim->tiles.size()), .image_texture_id = 0}; + for (int i = 0; i < udim->tiles.size(); i++) { + const auto &tile = udim->tiles[i]; + udim_data[i + 1] = KernelImageUDIM{.tile = tile.first, + .image_texture_id = tile.second.kernel_id()}; + } + udim->need_load = false; + } + } } void ImageManager::device_update(Device *device, Scene *scene, Progress &progress) @@ -501,13 +531,15 @@ void ImageManager::device_update(Device *device, Scene *scene, Progress &progres } }); + /* Update UDIM ids. */ + device_update_udims(device, scene); + /* Resize devices arrays to match. */ device_resize_image_textures(scene); /* Free and load images. */ TaskPool pool; - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - Image *img = images[image_texture_id].get(); + for (auto [image_texture_id, img] : images.enumerate()) { if (img && img->users == 0) { device_free_image(scene, image_texture_id); } @@ -526,22 +558,31 @@ void ImageManager::device_update(Device *device, Scene *scene, Progress &progres need_update_ = false; } -void ImageManager::device_update_image_texture_id(Device *device, - Scene *scene, - const size_t image_texture_id, - Progress &progress) +void ImageManager::device_load_images(Device *device, + Scene *scene, + Progress &progress, + const set &images) { - Image *img = images[image_texture_id].get(); - assert(img != nullptr); + /* Update UDIM ids. */ + device_update_udims(device, scene); - if (img->users == 0) { + /* Resize devices arrays to match number of images. */ + device_resize_image_textures(scene); - device_free_image(scene, image_texture_id); - } - - else if (img->need_load) { - device_load_image(device, scene, image_texture_id, progress); + /* Load handles. */ + TaskPool pool; + for (const ImageSingle *img : images) { + pool.push([this, device, scene, img, &progress] { + assert(img != nullptr); + if (img->users == 0) { + device_free_image(scene, img->image_texture_id); + } + else if (img->need_load) { + device_load_image(device, scene, img->image_texture_id, progress); + } + }); } + pool.wait_work(); /* Copy device arrays. */ device_copy_image_textures(scene); @@ -558,8 +599,7 @@ void ImageManager::device_load_builtin(Device *device, Scene *scene, Progress &p device_resize_image_textures(scene); TaskPool pool; - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - Image *img = images[image_texture_id].get(); + for (auto [image_texture_id, img] : images.enumerate()) { if (img && img->need_load && img->builtin) { pool.push([this, device, scene, image_texture_id, &progress] { device_load_image(device, scene, image_texture_id, progress); @@ -572,8 +612,8 @@ void ImageManager::device_load_builtin(Device *device, Scene *scene, Progress &p void ImageManager::device_free_builtin(Scene *scene) { - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - Image *img = images[image_texture_id].get(); + image_udims.clear(); + for (auto [image_texture_id, img] : images.enumerate()) { if (img && img->builtin) { device_free_image(scene, image_texture_id); } @@ -582,27 +622,25 @@ void ImageManager::device_free_builtin(Scene *scene) void ImageManager::device_free(Scene *scene) { - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { + image_udims.clear(); + for (auto [image_texture_id, img] : images.enumerate()) { device_free_image(scene, image_texture_id); } images.clear(); - - const thread_scoped_lock device_lock(device_mutex); image_cache.device_free(scene->dscene); scene->dscene.image_textures.free(); + scene->dscene.image_texture_udims.free(); } -void ImageManager::collect_statistics(RenderStats *stats) +void ImageManager::collect_statistics(RenderStats *stats, Scene * /*scene*/) { - for (size_t image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) { - Image *image = images[image_texture_id].get(); - if (!image || !image->mem) { - /* Image may have been freed due to lack of users. */ + for (auto [image_texture_id, image] : images.enumerate()) { + if (!image) { continue; } stats->image.textures.add_entry( - NamedSizeEntry(image->loader->name(), image->mem->memory_size())); + NamedSizeEntry(image->loader->name(), image->metadata.memory_size())); } } diff --git a/intern/cycles/scene/image.h b/intern/cycles/scene/image.h index 056bc83423d..fc82af2e02e 100644 --- a/intern/cycles/scene/image.h +++ b/intern/cycles/scene/image.h @@ -21,10 +21,13 @@ CCL_NAMESPACE_BEGIN class Device; class DeviceInfo; +class ImageLoader; +class ImageSingle; class ImageHandle; class ImageKey; -class ImageMetaData; class ImageManager; +class ImageUDIM; +class ImageTexture; class Progress; class RenderStats; class Scene; @@ -54,12 +57,17 @@ class ImageParams { /* Image Handle * * Access handle for image in the image manager. Multiple shader nodes may - * share the same image, and this class handles reference counting for that. */ + * share the same image, and this class handles reference counting for that. + * + * This may reference a single image, or a UDIM with multiple images. */ class ImageHandle { public: ImageHandle(); + ImageHandle(ImageTexture *image_texture, ImageManager *manager); ImageHandle(const ImageHandle &other); + ImageHandle(ImageHandle &&other) noexcept; ImageHandle &operator=(const ImageHandle &other); + ImageHandle &operator=(ImageHandle &&other) noexcept; ~ImageHandle(); bool operator==(const ImageHandle &other) const; @@ -68,25 +76,68 @@ class ImageHandle { bool empty() const; int num_tiles() const; - int num_svm_image_texture_ids() const; - ImageMetaData metadata(); - int svm_image_texture_id(const int image_texture_id_index = 0) const; - vector get_svm_image_texture_ids() const; - device_image *image_memory() const; + ImageMetaData metadata(Progress &progress); + int kernel_id() const; + device_image *vdb_image_memory() const; VDBImageLoader *vdb_loader() const; ImageManager *get_manager() const; + void add_to_set(set &images) const; + protected: - vector image_texture_ids; - bool is_tiled = false; - ImageManager *manager; + ImageTexture *image_texture = nullptr; + ImageManager *manager = nullptr; friend class ImageManager; }; +/* Image Texture + * + * Base class for an entry in the image manager, which can either be + * a single image or a UDIM. */ +class ImageTexture { + public: + std::atomic users = 0; + enum { SINGLE, UDIM } type = SINGLE; + bool need_load = true; +}; + +/* Image Single + * + * Representation of single image texture in the image manager. */ +class ImageSingle : public ImageTexture { + public: + ~ImageSingle(); + + /* Index into ImageManager::images and DeviceScene::image_textures. */ + int image_texture_id = KERNEL_IMAGE_NONE; + + ImageParams params; + ImageMetaData metadata; + unique_ptr loader; + + bool need_metadata = true; + bool builtin = false; + + thread_mutex mutex; + + device_image *vdb_memory = nullptr; +}; + +/* Image UDIM + * + * Representation of an UDIM image in the image manager. */ +class ImageUDIM : public ImageTexture { + public: + /* Negative kernel ID encoding offset into DeviceScene::image_texture_udims. */ + int id = KERNEL_IMAGE_NONE; + + vector> tiles; +}; + /* Image Manager * * Handles loading and storage of all images in the scene. This includes 2D @@ -106,41 +157,25 @@ class ImageManager { ImageHandle add_image(vector> &&loaders, const ImageParams ¶ms); void device_update(Device *device, Scene *scene, Progress &progress); - void device_update_image_texture_id(Device *device, - Scene *scene, - const size_t image_texture_id, - Progress &progress); void device_free(Scene *scene); void device_load_builtin(Device *device, Scene *scene, Progress &progress); void device_free_builtin(Scene *scene); + void device_load_images(Device *device, + Scene *scene, + Progress &progress, + const set &images); + void set_osl_texture_system(void *texture_system); bool set_animation_frame_update(const int frame); - void collect_statistics(RenderStats *stats); + void collect_statistics(RenderStats *stats, Scene *scene); void tag_update(); bool need_update() const; - ImageCache image_cache; - - struct Image { - ImageParams params; - ImageMetaData metadata; - unique_ptr loader; - - bool need_metadata; - bool need_load; - bool builtin; - - device_image *mem = nullptr; - - int users; - thread_mutex mutex; - }; - private: bool need_update_; @@ -148,17 +183,20 @@ class ImageManager { thread_mutex images_mutex; int animation_frame; - vector> images; + unique_ptr_vector images; + unique_ptr_vector image_udims; + int num_udim_tiles = 0; + + ImageCache image_cache; + void *osl_texture_system; - size_t add_image_texture(unique_ptr &&loader, - const ImageParams ¶ms, - const bool builtin); - void add_image_user(const size_t image_texture_id); - void remove_image_user(const size_t image_texture_id); - Image *get_image_texture(const size_t image_texture_id); + ImageSingle *add_image_texture(unique_ptr &&loader, + const ImageParams ¶ms, + const bool builtin); + ImageUDIM *add_image_texture(vector> &&tiles); - void load_image_metadata(Image *img); + void load_image_metadata(ImageSingle *img, Progress &progress); void device_load_image(Device *device, Scene *scene, @@ -166,6 +204,8 @@ class ImageManager { Progress &progress); void device_free_image(Scene *scene, const size_t image_texture_id); + void device_update_udims(Device *device, Scene *scene); + void device_resize_image_textures(Scene *scene); void device_copy_image_textures(Scene *scene); diff --git a/intern/cycles/scene/light.cpp b/intern/cycles/scene/light.cpp index 694a0bbcea9..0b02f69d3ee 100644 --- a/intern/cycles/scene/light.cpp +++ b/intern/cycles/scene/light.cpp @@ -1014,7 +1014,7 @@ void LightManager::device_update_background(Device *device, if (node->type == EnvironmentTextureNode::get_node_type()) { EnvironmentTextureNode *env = (EnvironmentTextureNode *)node; if (!env->handle.empty()) { - const ImageMetaData metadata = env->handle.metadata(); + const ImageMetaData metadata = env->handle.metadata(progress); environment_res.x = max(environment_res.x, (int)metadata.width); environment_res.y = max(environment_res.y, (int)metadata.height); } diff --git a/intern/cycles/scene/object.cpp b/intern/cycles/scene/object.cpp index ce01b701d92..034803bb689 100644 --- a/intern/cycles/scene/object.cpp +++ b/intern/cycles/scene/object.cpp @@ -293,7 +293,7 @@ uint Object::visibility_for_tracing() const return SHADOW_CATCHER_OBJECT_VISIBILITY(is_shadow_catcher, visibility & PATH_RAY_ALL_VISIBILITY); } -float Object::compute_volume_step_size() const +float Object::compute_volume_step_size(Progress &progress) const { if (geometry->is_light()) { /* World volume. */ @@ -343,7 +343,7 @@ float Object::compute_volume_step_size() const for (Attribute &attr : volume->attributes.attributes) { if (attr.element == ATTR_ELEMENT_VOXEL) { ImageHandle &handle = attr.data_voxel(); - const ImageMetaData &metadata = handle.metadata(); + const ImageMetaData &metadata = handle.metadata(progress); if (metadata.nanovdb_byte_size == 0) { continue; } diff --git a/intern/cycles/scene/object.h b/intern/cycles/scene/object.h index 0bdbb819855..faf6c9f6653 100644 --- a/intern/cycles/scene/object.h +++ b/intern/cycles/scene/object.h @@ -114,7 +114,7 @@ class Object : public Node { int get_device_index() const; /* Compute step size from attributes, shaders, transforms. */ - float compute_volume_step_size() const; + float compute_volume_step_size(Progress &progress) const; /* Check whether this object can be used as light-emissive. */ bool usable_as_light() const; diff --git a/intern/cycles/scene/osl.cpp b/intern/cycles/scene/osl.cpp index cc2cb05afa3..439075de173 100644 --- a/intern/cycles/scene/osl.cpp +++ b/intern/cycles/scene/osl.cpp @@ -669,10 +669,11 @@ void OSLShaderManager::device_update_specific(Device *device, for (Shader *shader : scene->shaders) { assert(shader->graph); - auto compile = [scene, shader, background_shader](Device *sub_device, OSLGlobals *) { + auto compile = [scene, &progress, shader, background_shader](Device *sub_device, + OSLGlobals *) { OSL::ShadingSystem *ss = scene->osl_manager->get_shading_system(sub_device); - OSLCompiler compiler(ss, scene, sub_device); + OSLCompiler compiler(ss, scene, progress, sub_device); compiler.background = (shader == background_shader); compiler.compile(shader); }; @@ -923,10 +924,9 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph, return node; } -/* Static function, so only this file needs to be compile with RTTT. */ -void OSLShaderManager::osl_image_slots(Device *device, - ImageManager *image_manager, - set &image_slots) +void OSLShaderManager::osl_image_handles(Device *device, + ImageManager *image_manager, + set &handles) { set services_shared; device->foreach_device([&services_shared](Device *sub_device) { @@ -936,9 +936,9 @@ void OSLShaderManager::osl_image_slots(Device *device, for (OSLRenderServices *services : services_shared) { for (auto it = services->textures.begin(); it != services->textures.end(); ++it) { - if (it->second.handle.get_manager() == image_manager) { - const int slot = it->second.handle.svm_image_texture_id(); - image_slots.insert(slot); + const ImageHandle &handle = it->second.handle; + if (handle.get_manager() == image_manager && !handle.empty()) { + handle.add_to_set(handles); } } } @@ -946,8 +946,9 @@ void OSLShaderManager::osl_image_slots(Device *device, /* Graph Compiler */ -OSLCompiler::OSLCompiler(OSL::ShadingSystem *ss, Scene *scene, Device *device) +OSLCompiler::OSLCompiler(OSL::ShadingSystem *ss, Scene *scene, Progress &progress, Device *device) : scene(scene), + progress(progress), services(static_cast(ss->renderer())), ss(ss), device(device) @@ -1590,9 +1591,8 @@ void OSLCompiler::parameter_texture(const char *name, const ImageHandle &handle) * to get handle again. Note that this name must be unique between multiple * render sessions as the render services are shared. */ const ustring filename(string_printf("@svm%d", texture_shared_unique_id++).c_str()); - services->textures.insert( - OSLUStringHash(filename), - OSLTextureHandle(OSLTextureHandle::SVM, handle.get_svm_image_texture_ids())); + services->textures.insert(OSLUStringHash(filename), + OSLTextureHandle(OSLTextureHandle::SVM, handle.kernel_id())); parameter(name, filename); } diff --git a/intern/cycles/scene/osl.h b/intern/cycles/scene/osl.h index db63b9bb114..391df843e4e 100644 --- a/intern/cycles/scene/osl.h +++ b/intern/cycles/scene/osl.h @@ -140,8 +140,10 @@ class OSLShaderManager : public ShaderManager { const std::string &bytecode_hash = "", const std::string &bytecode = ""); - /* Get image slots used by OSL services on device. */ - static void osl_image_slots(Device *device, ImageManager *image_manager, set &image_slots); + /* Get image handles used by OSL services on device. */ + static void osl_image_handles(Device *device, + ImageManager *image_manager, + set &image_handles); }; #endif @@ -151,7 +153,7 @@ class OSLShaderManager : public ShaderManager { class OSLCompiler { public: #ifdef WITH_OSL - OSLCompiler(OSL::ShadingSystem *ss, Scene *scene, Device *device); + OSLCompiler(OSL::ShadingSystem *ss, Scene *scene, Progress &progress, Device *device); #endif void compile(Shader *shader); @@ -185,6 +187,7 @@ class OSLCompiler { bool background; Scene *scene; + Progress &progress; private: #ifdef WITH_OSL diff --git a/intern/cycles/scene/scene.cpp b/intern/cycles/scene/scene.cpp index f84ca2e93cf..f866783947f 100644 --- a/intern/cycles/scene/scene.cpp +++ b/intern/cycles/scene/scene.cpp @@ -500,7 +500,7 @@ void Scene::device_free() void Scene::collect_statistics(RenderStats *stats) { geometry_manager->collect_statistics(this, stats); - image_manager->collect_statistics(stats); + image_manager->collect_statistics(stats, this); } void Scene::enable_update_stats() diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index 25304b27cc0..0766a8db5cc 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -368,20 +368,25 @@ void ImageTextureNode::attributes(Shader *shader, AttributeRequestSet *attribute ShaderNode::attributes(shader, attributes); } +void ImageTextureNode::update_images(const SVMCompiler &compiler) +{ + if (handle.empty()) { + cull_tiles(compiler.scene, compiler.current_graph); + ImageManager *image_manager = compiler.scene->image_manager.get(); + handle = image_manager->add_image(filename.string(), image_params(), tiles); + } +} + void ImageTextureNode::compile(SVMCompiler &compiler) { ShaderInput *vector_in = input("Vector"); ShaderOutput *color_out = output("Color"); ShaderOutput *alpha_out = output("Alpha"); - if (handle.empty()) { - cull_tiles(compiler.scene, compiler.current_graph); - ImageManager *image_manager = compiler.scene->image_manager.get(); - handle = image_manager->add_image(filename.string(), image_params(), tiles); - } + update_images(compiler); /* All tiles have the same metadata. */ - const ImageMetaData metadata = handle.metadata(); + const ImageMetaData metadata = handle.metadata(compiler.progress); const bool compress_as_srgb = metadata.is_compressible_as_srgb; const int vector_offset = tex_mapping.compile_begin(compiler, vector_in); @@ -401,44 +406,17 @@ void ImageTextureNode::compile(SVMCompiler &compiler) } if (projection != NODE_IMAGE_PROJ_BOX) { - /* If there only is one image (a very common case), we encode it as a negative value. */ - int num_nodes; - if (handle.num_tiles() == 0) { - num_nodes = -handle.svm_image_texture_id(); - } - else { - num_nodes = divide_up(handle.num_tiles(), 2); - } - compiler.add_node(NODE_TEX_IMAGE, - num_nodes, + handle.kernel_id(), compiler.encode_uchar4(vector_offset, compiler.stack_assign_if_linked(color_out), compiler.stack_assign_if_linked(alpha_out), flags), projection); - - if (num_nodes > 0) { - for (int i = 0; i < num_nodes; i++) { - int4 node; - node.x = tiles[2 * i]; - node.y = handle.svm_image_texture_id(2 * i); - if (2 * i + 1 < tiles.size()) { - node.z = tiles[2 * i + 1]; - node.w = handle.svm_image_texture_id(2 * i + 1); - } - else { - node.z = -1; - node.w = -1; - } - compiler.add_node(node.x, node.y, node.z, node.w); - } - } } else { - assert(handle.num_svm_image_texture_ids() == 1); compiler.add_node(NODE_TEX_IMAGE_BOX, - handle.svm_image_texture_id(), + handle.kernel_id(), compiler.encode_uchar4(vector_offset, compiler.stack_assign_if_linked(color_out), compiler.stack_assign_if_linked(alpha_out), @@ -460,12 +438,12 @@ void ImageTextureNode::compile(OSLCompiler &compiler) handle = image_manager->add_image(filename.string(), image_params()); } - const ImageMetaData metadata = handle.metadata(); + const ImageMetaData metadata = handle.metadata(compiler.progress); const bool is_float = metadata.is_float(); const bool compress_as_srgb = metadata.is_compressible_as_srgb; const ustring known_colorspace = metadata.colorspace; - if (handle.svm_image_texture_id() == -1) { + if (handle.kernel_id() == KERNEL_IMAGE_NONE) { compiler.parameter_texture( "filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace); } @@ -571,18 +549,23 @@ void EnvironmentTextureNode::attributes(Shader *shader, AttributeRequestSet *att ShaderNode::attributes(shader, attributes); } +void EnvironmentTextureNode::update_images(const SVMCompiler &compiler) +{ + if (handle.empty()) { + ImageManager *image_manager = compiler.scene->image_manager.get(); + handle = image_manager->add_image(filename.string(), image_params()); + } +} + void EnvironmentTextureNode::compile(SVMCompiler &compiler) { ShaderInput *vector_in = input("Vector"); ShaderOutput *color_out = output("Color"); ShaderOutput *alpha_out = output("Alpha"); - if (handle.empty()) { - ImageManager *image_manager = compiler.scene->image_manager.get(); - handle = image_manager->add_image(filename.string(), image_params()); - } + update_images(compiler); - const ImageMetaData metadata = handle.metadata(); + const ImageMetaData metadata = handle.metadata(compiler.progress); const bool compress_as_srgb = metadata.is_compressible_as_srgb; const int vector_offset = tex_mapping.compile_begin(compiler, vector_in); @@ -593,7 +576,7 @@ void EnvironmentTextureNode::compile(SVMCompiler &compiler) } compiler.add_node(NODE_TEX_ENVIRONMENT, - handle.svm_image_texture_id(), + handle.kernel_id(), compiler.encode_uchar4(vector_offset, compiler.stack_assign_if_linked(color_out), compiler.stack_assign_if_linked(alpha_out), @@ -612,12 +595,12 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler) tex_mapping.compile(compiler); - const ImageMetaData metadata = handle.metadata(); + const ImageMetaData metadata = handle.metadata(compiler.progress); const bool is_float = metadata.is_float(); const bool compress_as_srgb = metadata.is_compressible_as_srgb; const ustring known_colorspace = metadata.colorspace; - if (handle.svm_image_texture_id() == -1) { + if (handle.kernel_id() == KERNEL_IMAGE_NONE) { compiler.parameter_texture( "filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace); } @@ -1027,7 +1010,7 @@ void SkyTextureNode::compile(SVMCompiler &compiler) compiler.add_node(__float_as_uint(sunsky.nishita_data[8]), __float_as_uint(sunsky.nishita_data[9]), __float_as_uint(sunsky.nishita_data[10]), - handle.svm_image_texture_id()); + handle.kernel_id()); } tex_mapping.compile_end(compiler, vector_in, vector_offset); diff --git a/intern/cycles/scene/shader_nodes.h b/intern/cycles/scene/shader_nodes.h index ac8c1b05c0a..b631a906fb5 100644 --- a/intern/cycles/scene/shader_nodes.h +++ b/intern/cycles/scene/shader_nodes.h @@ -84,6 +84,8 @@ class ImageSlotTextureNode : public TextureNode { return TextureNode::equals(other) && handle == other_node.handle; } + virtual void update_images(const SVMCompiler &compiler) = 0; + ImageHandle handle; }; @@ -105,6 +107,8 @@ class ImageTextureNode : public ImageSlotTextureNode { ImageParams image_params() const; + void update_images(const SVMCompiler &compiler) override; + /* Parameters. */ NODE_SOCKET_API(ustring, filename) NODE_SOCKET_API(ustring, colorspace) @@ -139,6 +143,8 @@ class EnvironmentTextureNode : public ImageSlotTextureNode { ImageParams image_params() const; + void update_images(const SVMCompiler &compiler) override; + /* Parameters. */ NODE_SOCKET_API(ustring, filename) NODE_SOCKET_API(ustring, colorspace) diff --git a/intern/cycles/scene/svm.cpp b/intern/cycles/scene/svm.cpp index d450471b458..d485578920f 100644 --- a/intern/cycles/scene/svm.cpp +++ b/intern/cycles/scene/svm.cpp @@ -39,7 +39,7 @@ void SVMShaderManager::device_update_shader(Scene *scene, assert(shader->graph); SVMCompiler::Summary summary; - SVMCompiler compiler(scene); + SVMCompiler compiler(scene, progress); compiler.background = (shader == scene->background->get_shader(scene)); compiler.compile(shader, *svm_nodes, 0, &summary); @@ -149,7 +149,7 @@ void SVMShaderManager::device_free(Device *device, DeviceScene *dscene, Scene *s /* Graph Compiler */ -SVMCompiler::SVMCompiler(Scene *scene) : scene(scene) +SVMCompiler::SVMCompiler(Scene *scene, Progress &progress) : scene(scene), progress(progress) { max_stack_use = 0; current_type = SHADER_TYPE_SURFACE; diff --git a/intern/cycles/scene/svm.h b/intern/cycles/scene/svm.h index 6f79ff07ba1..db3b75a1c19 100644 --- a/intern/cycles/scene/svm.h +++ b/intern/cycles/scene/svm.h @@ -75,7 +75,7 @@ class SVMCompiler { string full_report() const; }; - SVMCompiler(Scene *scene); + SVMCompiler(Scene *scene, Progress &progress); void compile(Shader *shader, array &svm_nodes, const int index, @@ -116,6 +116,7 @@ class SVMCompiler { } Scene *scene; + Progress &progress; ShaderGraph *current_graph; bool background; diff --git a/intern/cycles/scene/volume.cpp b/intern/cycles/scene/volume.cpp index 02650930092..b5851e49990 100644 --- a/intern/cycles/scene/volume.cpp +++ b/intern/cycles/scene/volume.cpp @@ -645,7 +645,7 @@ void GeometryManager::create_volume_mesh(const Scene *scene, Volume *volume, Pro } /* Create NanoVDB grid handle from image memory. */ - device_image *image = handle.image_memory(); + device_image *image = handle.vdb_image_memory(); if (image == nullptr || image->host_pointer == nullptr || image->info.data_type == IMAGE_DATA_TYPE_NANOVDB_EMPTY || !is_nanovdb_type(image->info.data_type)) @@ -1181,7 +1181,7 @@ std::string VolumeManager::visualize_octree(const char *filename) const return filename_full; } -void VolumeManager::update_step_size(const Scene *scene, DeviceScene *dscene) +void VolumeManager::update_step_size(const Scene *scene, DeviceScene *dscene, Progress &progress) { assert(scene->integrator->get_volume_ray_marching()); @@ -1204,7 +1204,7 @@ void VolumeManager::update_step_size(const Scene *scene, DeviceScene *dscene) } volume_step_size[object->index] = scene->integrator->get_volume_step_rate() * - object->compute_volume_step_size(); + object->compute_volume_step_size(progress); } dscene->volume_step_size.copy_to_device(); @@ -1224,7 +1224,7 @@ void VolumeManager::device_update(Device *device, dscene->volume_tree_roots.free(); dscene->volume_tree_root_ids.free(); } - update_step_size(scene, dscene); + update_step_size(scene, dscene, progress); algorithm_modified_ = false; return; } diff --git a/intern/cycles/scene/volume.h b/intern/cycles/scene/volume.h index 6bea2968e32..e3e15e19f50 100644 --- a/intern/cycles/scene/volume.h +++ b/intern/cycles/scene/volume.h @@ -78,7 +78,7 @@ class VolumeManager { std::string visualize_octree(const char *filename) const; /* Step size for ray marching. */ - void update_step_size(const Scene *, DeviceScene *); + void update_step_size(const Scene *, DeviceScene *, Progress &progress); /* One octree per object per shader. */ std::map, std::shared_ptr> object_octrees_; diff --git a/intern/cycles/util/types_image.h b/intern/cycles/util/types_image.h index 4bf9c8e5be2..8b925543ea0 100644 --- a/intern/cycles/util/types_image.h +++ b/intern/cycles/util/types_image.h @@ -102,6 +102,12 @@ struct KernelImageInfo { uint height = 0; }; +/* KernelImageTexture index for UDIM tile. */ +struct KernelImageUDIM { + int tile; + int image_texture_id; +}; + /* Kernel data structure for image textures. * * This describes a logical image texture for the shading system, that may be stored @@ -109,7 +115,7 @@ struct KernelImageInfo { * support on demand loading of tiles. */ struct KernelImageTexture { /* Index into image object map. */ - uint image_info_id = 0; + uint image_info_id = KERNEL_IMAGE_NONE; /* Image dimensions */ uint width = 0; uint height = 0;