Files
workinf_Blender_Wasm/blender-5.2.0/intern/cycles/scene/image.cpp
2026-08-12 04:47:48 -04:00

870 lines
26 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "device/device.h"
#include "device/queue.h"
#include "scene/devicescene.h"
#include "scene/image.h"
#include "scene/image_loader.h"
#include "scene/image_oiio.h"
#include "scene/image_vdb.h"
#include "scene/scene.h"
#include "scene/stats.h"
#include "util/colorspace.h"
#include "util/debug.h"
#include "util/log.h"
#include "util/progress.h"
#include "util/task.h"
#include "util/types_image.h"
CCL_NAMESPACE_BEGIN
/* Image Handle */
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(other.image_texture), manager(other.manager)
{
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;
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();
}
void ImageHandle::clear()
{
/* 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;
}
manager = nullptr;
}
bool ImageHandle::empty() const
{
return image_texture == nullptr;
}
int ImageHandle::num_tiles() const
{
if (image_texture && image_texture->type == ImageTexture::UDIM) {
ImageUDIM *udim = static_cast<ImageUDIM *>(image_texture);
return udim->tiles.size();
}
return 0;
}
ImageMetaData ImageHandle::metadata(Progress &progress)
{
if (image_texture) {
if (image_texture->type == ImageTexture::SINGLE) {
ImageSingle *img = static_cast<ImageSingle *>(image_texture);
manager->load_image_metadata(img, progress);
return img->metadata;
}
if (image_texture->type == ImageTexture::UDIM) {
ImageUDIM *udim = static_cast<ImageUDIM *>(image_texture);
return udim->tiles[0].second.metadata(progress);
}
}
return ImageMetaData();
}
bool ImageHandle::all_udim_tiled(Progress &progress)
{
if (image_texture && image_texture->type == ImageTexture::UDIM) {
ImageUDIM *udim = static_cast<ImageUDIM *>(image_texture);
for (auto &tile : udim->tiles) {
if (!tile.second.metadata(progress).has_tiles_and_mipmaps) {
return false;
}
}
return true;
}
return metadata(progress).has_tiles_and_mipmaps;
}
int ImageHandle::kernel_id() const
{
if (!image_texture) {
return KERNEL_IMAGE_NONE;
}
if (image_texture->type == ImageTexture::SINGLE) {
return static_cast<const ImageSingle *>(image_texture)->image_texture_id;
}
return static_cast<const ImageUDIM *>(image_texture)->id;
}
device_image *ImageHandle::vdb_image_memory() const
{
if (image_texture == nullptr || image_texture->type != ImageTexture::SINGLE) {
return nullptr;
}
ImageSingle *img = static_cast<ImageSingle *>(image_texture);
return img->vdb_memory;
}
VDBImageLoader *ImageHandle::vdb_loader() const
{
if (image_texture == nullptr || image_texture->type != ImageTexture::SINGLE) {
return nullptr;
}
ImageSingle *img = static_cast<ImageSingle *>(image_texture);
ImageLoader *loader = img->loader.get();
if (loader == nullptr) {
return nullptr;
}
if (loader->is_vdb_loader()) {
return dynamic_cast<VDBImageLoader *>(loader);
}
return nullptr;
}
ImageManager *ImageHandle::get_manager() const
{
return manager;
}
bool ImageHandle::operator==(const ImageHandle &other) const
{
return image_texture == other.image_texture && manager == other.manager;
}
void ImageHandle::add_to_set(set<const ImageSingle *> &images) const
{
if (empty()) {
return;
}
if (image_texture->type == ImageTexture::SINGLE) {
images.insert(static_cast<const ImageSingle *>(image_texture));
}
else {
for (const auto &tile : static_cast<const ImageUDIM *>(image_texture)->tiles) {
images.insert(static_cast<const ImageSingle *>(tile.second.image_texture));
}
}
}
/* Image Single */
ImageSingle::~ImageSingle() = default;
/* Image Manager */
ImageManager::ImageManager(const DeviceInfo & /*info*/, const SceneParams &params)
{
use_texture_cache = params.use_texture_cache;
auto_texture_cache = params.auto_texture_cache;
texture_cache_path = params.texture_cache_path;
}
ImageManager::~ImageManager()
{
for (ImageSingle *img : images) {
assert(!img);
(void)img;
}
}
bool ImageManager::set_animation_frame_update(const int frame)
{
if (frame != animation_frame) {
const thread_scoped_lock device_lock(images_mutex);
animation_frame = frame;
for (ImageSingle *img : images) {
if (img && img->params.animated) {
return true;
}
}
}
return false;
}
void ImageManager::load_image_metadata(ImageSingle *img, Progress &progress)
{
if (!img->need_metadata) {
return;
}
const thread_scoped_lock image_lock(img->mutex);
if (!img->need_metadata) {
return;
}
/* Isolate threading since we are holding a mutex lock and metadata loading
* may involve multi-threading from e.g. the texture cache generation or host
* application processing. */
isolate_task([&]() {
/* Change image to use tx file if supported. */
const ImageLoaderParams params = {.use_texture_cache = use_texture_cache,
.auto_texture_cache = auto_texture_cache,
.texture_cache_path = texture_cache_path,
.colorspace = img->params.colorspace,
.alpha_type = img->params.alpha_type,
.load_failure_num = load_failure_num,
.tx_failure_num = tx_failure_num};
ImageMetaData &metadata = img->metadata;
metadata = ImageMetaData();
metadata.colorspace = img->params.colorspace;
if (img->loader->load_metadata(metadata, params, progress)) {
assert(metadata.type != IMAGE_DATA_NUM_TYPES);
}
else {
metadata.type = IMAGE_DATA_TYPE_BYTE4;
}
metadata.finalize(img->params.alpha_type);
img->need_metadata = false;
});
}
ImageHandle ImageManager::add_image(const string &filename, const ImageParams &params)
{
ImageSingle *image = add_image_texture(make_unique<OIIOImageLoader>(filename), params, false);
return ImageHandle(image, this);
}
ImageHandle ImageManager::add_image(const string &filename,
const ImageParams &params,
const array<int> &tiles)
{
if (tiles.empty()) {
return add_image(filename, params);
}
vector<std::pair<int, ImageHandle>> udim_tiles;
for (const int tile : tiles) {
string tile_filename = filename;
/* Since we don't have information about the exact tile format used in this code location,
* just attempt all replacement patterns that Blender supports. */
string_replace(tile_filename, "<UDIM>", string_printf("%04d", tile));
const int u = ((tile - 1001) % 10);
const int v = ((tile - 1001) / 10);
string_replace(tile_filename, "<UVTILE>", string_printf("u%d_v%d", u + 1, v + 1));
ImageSingle *image = add_image_texture(
make_unique<OIIOImageLoader>(tile_filename), params, false);
udim_tiles.emplace_back(tile, ImageHandle(image, this));
}
ImageUDIM *udim = add_image_texture(std::move(udim_tiles));
return ImageHandle(udim, this);
}
ImageHandle ImageManager::add_image(unique_ptr<ImageLoader> &&loader,
const ImageParams &params,
const bool builtin)
{
ImageSingle *image = add_image_texture(std::move(loader), params, builtin);
return ImageHandle(image, this);
}
ImageHandle ImageManager::add_image(vector<unique_ptr<ImageLoader>> &&loaders,
const ImageParams &params)
{
vector<std::pair<int, ImageHandle>> udim_tiles;
for (unique_ptr<ImageLoader> &loader : loaders) {
unique_ptr<ImageLoader> local_loader;
std::swap(loader, local_loader);
ImageSingle *image = add_image_texture(std::move(local_loader), params, true);
udim_tiles.emplace_back(image->loader->get_tile_number(), ImageHandle(image, this));
}
ImageUDIM *udim = add_image_texture(std::move(udim_tiles));
return ImageHandle(udim, this);
}
/* ImageManager */
ImageSingle *ImageManager::add_image_texture(unique_ptr<ImageLoader> &&loader,
const ImageParams &params,
const bool builtin)
{
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++) {
ImageSingle *img = images[image_texture_id];
if (img && ImageLoader::equals(img->loader.get(), loader.get()) && img->params == params) {
return img;
}
}
/* Find free image_texture_id. */
for (image_texture_id = 0; image_texture_id < images.size(); image_texture_id++) {
if (!images[image_texture_id]) {
break;
}
}
if (image_texture_id == images.size()) {
images.resize(images.size() + 1);
}
/* Add new image. */
unique_ptr<ImageSingle> img = make_unique<ImageSingle>();
img->type = ImageTexture::SINGLE;
img->image_texture_id = image_texture_id;
img->params = params;
img->loader = std::move(loader);
img->builtin = builtin;
images.replace(image_texture_id, std::move(img));
tag_update();
return images[image_texture_id];
}
ImageUDIM *ImageManager::add_image_texture(vector<std::pair<int, ImageHandle>> &&tiles)
{
const thread_scoped_lock device_lock(images_mutex);
/* 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;
}
}
/* 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<ImageUDIM> img = make_unique<ImageUDIM>();
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)
{
const thread_scoped_lock device_lock(device_mutex);
DeviceScene &dscene = scene->dscene;
if (dscene.image_textures.size() < images.size()) {
dscene.image_textures.resize(images.size());
}
}
void ImageManager::device_copy_image_textures(Device *device, Scene *scene)
{
image_cache.copy_to_device(scene->dscene);
const thread_scoped_lock device_lock(device_mutex);
DeviceScene &dscene = scene->dscene;
dscene.image_textures.copy_to_device_if_modified();
dscene.image_texture_udims.copy_to_device_if_modified();
dscene.image_textures.clear_modified();
dscene.image_texture_udims.clear_modified();
device->set_image_cache_func(
[this, device, scene](size_t image_texture_id,
int miplevel,
int x,
int y,
KernelTileDescriptor &tile_descriptor) {
this->device_cpu_load_requested(
device, scene, image_texture_id, miplevel, x, y, tile_descriptor);
},
[this, device, scene](DeviceQueue &queue) {
this->device_gpu_load_requested(device, queue, scene);
});
}
void ImageManager::device_load_image(Device *device,
Scene *scene,
const size_t image_texture_id,
Progress &progress)
{
if (progress.get_cancel()) {
return;
}
ImageSingle *img = images[image_texture_id];
progress.set_status("Updating Images", "Loading " + img->loader->name());
load_image_metadata(img, progress);
KernelImageTexture tex;
tex.width = img->metadata.width;
tex.height = img->metadata.height;
tex.interpolation = img->params.interpolation;
tex.extension = img->params.extension;
tex.use_transform_3d = img->metadata.use_transform_3d;
tex.transform_3d = img->metadata.transform_3d;
tex.average_color = img->metadata.average_color;
int max_dim = std::max(img->metadata.width, img->metadata.height);
if (use_texture_cache && img->metadata.has_tiles_and_mipmaps && img->metadata.tile_size) {
/* Apply texture size limit by skipping the highest mip levels. */
const int texture_limit = scene->params.texture_limit;
img->miplevel_offset = 0;
while (texture_limit > 0 && max_dim > texture_limit) {
img->miplevel_offset++;
tex.width = std::max(1, tex.width / 2);
tex.height = std::max(1, tex.height / 2);
max_dim /= 2;
}
image_cache.load_image_tiled(scene->dscene, img->metadata, tex);
}
else {
/* Compute texture resolution scale factor from texture size limit. */
float texture_resolution = scene->params.texture_resolution;
const int texture_limit = scene->params.texture_limit;
if (texture_limit > 0 && max_dim > texture_limit) {
texture_resolution = std::min(texture_resolution, float(texture_limit) / float(max_dim));
}
img->vdb_memory = image_cache.load_image_full(
*device, *img->loader, img->metadata, texture_resolution, tex);
}
/* Update image texture device data. */
scene->dscene.image_textures[image_texture_id] = tex;
scene->dscene.image_textures.tag_modified();
/* Cleanup memory in image loader. */
img->loader->cleanup();
img->need_load = false;
}
void ImageManager::device_free_image(Scene *scene, size_t image_texture_id)
{
ImageSingle *img = images[image_texture_id];
if (img == nullptr) {
return;
}
if (!img->need_load) {
const KernelImageTexture &tex = scene->dscene.image_textures[image_texture_id];
image_cache.free_image(scene->dscene, tex);
}
images.steal(image_texture_id);
}
void ImageManager::device_cpu_load_requested(Device *device,
Scene *scene,
size_t image_texture_id,
int miplevel,
int x,
int y,
KernelTileDescriptor &tile_descriptor)
{
/* Apply any deferred updates from GPU devices that loaded tiles. */
const bool for_cpu_cache_miss = true;
image_cache.copy_images_to_device(for_cpu_cache_miss);
/* Load the tile. */
const ImageSingle *img = images[image_texture_id];
const KernelImageTexture &tex = scene->dscene.image_textures[image_texture_id];
image_cache.load_requested_tile(*device,
scene->dscene,
tex,
tile_descriptor,
miplevel,
x,
y,
*img->loader,
img->metadata,
img->miplevel_offset);
}
void ImageManager::device_gpu_load_requested(Device *device, DeviceQueue &queue, Scene *scene)
{
/* TODO: Check if this works correctly if access state or tile descriptors get moved to host
* memory, or prevent it from happening. */
DeviceScene &dscene = scene->dscene;
/* Copy tile access state from the device, using either the storage or just a pointer to
* existing allocation for unified memory. */
vector<uint8_t> local_storage;
const uint8_t *access_state = reinterpret_cast<const uint8_t *>(
queue.copy_from_device_synchronized(dscene.image_texture_tile_access_state, local_storage));
/* Load tiles requested by this device in parallel. */
parallel_for(blocked_range<size_t>(0, images.size(), 1), [&](const blocked_range<size_t> &r) {
for (size_t i = r.begin(); i != r.end(); i++) {
if (images[i] && dscene.image_textures[i].tile_descriptor_offset != KERNEL_TILE_LOAD_NONE) {
ImageSingle *img = images[i];
image_cache.load_requested_tiles(*device,
dscene,
dscene.image_textures[i],
*img->loader,
img->metadata,
img->miplevel_offset,
access_state);
}
}
});
/* Copy data to just this GPU device, using the queue so it happens before kernel execution
* without the need for another synchronize call. */
image_cache.copy_to_device(dscene, queue);
}
void ImageManager::device_update_udims(Device * /*device*/, Scene *scene)
{
const thread_scoped_lock device_lock(device_mutex);
device_vector<KernelImageUDIM> &device_udims = scene->dscene.image_texture_udims;
if (device_udims.size() == num_udim_tiles) {
return;
}
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)
{
if (!need_update()) {
return;
}
const scoped_callback_timer timer([scene](double time) {
if (scene->update_stats) {
scene->update_stats->image.times.add_entry({"device_update", time});
}
});
/* Set mip bias for tiled images based on texture resolution. */
KernelImage *kimage = &scene->dscene.data.image;
kimage->mip_bias = (scene->params.texture_resolution < 1.0f) ?
-log2f(scene->params.texture_resolution) :
0.0f;
/* 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 (auto [image_texture_id, img] : images.enumerate()) {
if (img && img->users == 0) {
device_free_image(scene, image_texture_id);
}
else if (img && img->need_load) {
pool.push([this, device, scene, image_texture_id, &progress] {
device_load_image(device, scene, image_texture_id, progress);
});
}
}
pool.wait_work();
report_failures();
/* Copy device arrays. */
device_copy_image_textures(device, scene);
need_update_ = false;
}
void ImageManager::device_load_images(Device *device,
Scene *scene,
Progress &progress,
const set<const ImageSingle *> &images)
{
/* Set mip bias for tiled images based on texture resolution. */
KernelImage *kimage = &scene->dscene.data.image;
kimage->mip_bias = (scene->params.texture_resolution < 1.0f) ?
-log2f(scene->params.texture_resolution) :
0.0f;
/* Update UDIM ids. */
device_update_udims(device, scene);
/* Resize devices arrays to match number of images. */
device_resize_image_textures(scene);
/* 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();
report_failures();
/* Copy device arrays. */
device_copy_image_textures(device, scene);
}
void ImageManager::device_load_builtin(Device *device, Scene *scene, Progress &progress)
{
/* Load only builtin images, Blender needs this to load evaluated
* scene data from depsgraph before it is freed. */
if (!need_update()) {
return;
}
device_resize_image_textures(scene);
TaskPool pool;
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);
});
}
}
pool.wait_work();
report_failures();
}
void ImageManager::device_free_builtin(Scene *scene)
{
image_udims.clear();
for (auto [image_texture_id, img] : images.enumerate()) {
if (img && img->builtin) {
device_free_image(scene, image_texture_id);
}
}
}
void ImageManager::device_free(Scene *scene)
{
image_udims.clear();
for (auto [image_texture_id, img] : images.enumerate()) {
device_free_image(scene, image_texture_id);
}
images.clear();
image_cache.device_free(scene->dscene);
scene->dscene.image_textures.free();
scene->dscene.image_texture_udims.free();
}
void ImageManager::evict_unused(Device *device, Scene *scene)
{
if (!DebugFlags().texture_cache.use_eviction) {
return;
}
DeviceScene &dscene = scene->dscene;
device_vector<uint8_t> &tile_access = dscene.image_texture_tile_access_state;
if (tile_access.size() == 0) {
return;
}
/* Read back tile access state from all devices and OR together. */
device->mem_or_from_device(tile_access);
image_cache.evict_unused(*device,
dscene,
{dscene.image_textures.data(), dscene.image_textures.size()},
tile_access.data());
/* Reset access state on both host and device, so no more tiles are marked as used.
* Any tile not marked as used before the next eviction cycle will be evicted. */
memset(tile_access.data(), KERNEL_TILE_ACCESS_NONE, tile_access.size() * sizeof(uint8_t));
tile_access.zero_to_device();
tile_access.clear_modified();
device_copy_image_textures(device, scene);
}
void ImageManager::collect_statistics(RenderStats *stats, Scene *scene)
{
DeviceScene &dscene = scene->dscene;
for (auto [image_texture_id, image] : images.enumerate()) {
if (!image) {
continue;
}
const KernelImageTexture &tex = dscene.image_textures[image_texture_id];
if (tex.tile_descriptor_offset != KERNEL_TILE_LOAD_NONE) {
/* Tiled image. */
ImageTileStats tile_stats;
tile_stats.name = image->loader->name();
tile_stats.size = 0;
image_cache.collect_statistics(dscene, tex, image->metadata, tile_stats);
stats->image.tiled_images.push_back(tile_stats);
stats->image.tiled_images_size += tile_stats.size;
}
else {
/* Non-tiled image. */
stats->image.full_images.add_entry(
NamedSizeEntry(image->loader->name(), image->metadata.memory_size()));
}
}
/* Add global overhead from device vectors. */
stats->image.overhead_size = dscene.image_textures.memory_size() +
dscene.image_texture_udims.memory_size() +
image_cache.memory_size(dscene);
/* Map image cache stats to eviction statistics. */
const ImageCacheStats &cache_stats = image_cache.get_stats();
stats->image.eviction.tiles_loaded = cache_stats.total_loaded;
stats->image.eviction.tiles_evicted = cache_stats.total_evicted;
stats->image.eviction.tiles_reloaded = cache_stats.total_reloaded;
stats->image.eviction.peak_loaded = cache_stats.peak_loaded;
stats->image.tiled_images_peak_size = size_t(cache_stats.peak_tiled_bytes);
}
void ImageManager::tag_update()
{
need_update_ = true;
}
bool ImageManager::need_update() const
{
return need_update_;
}
bool ImageManager::get_use_texture_cache() const
{
return use_texture_cache;
}
bool ImageManager::get_auto_texture_cache() const
{
return auto_texture_cache;
}
void ImageManager::report_failures()
{
/* Report failure once after the full update. If we report an error immediately then
* exit-on-error will abort the process without waiting for other threads to cleanly finish
* generating their tx files. */
const int load_num = load_failure_num.exchange(0);
if (load_num > 0) {
LOG_ERROR << "Failed to load " << load_num << " image files";
}
const int tx_num = tx_failure_num.exchange(0);
if (tx_num > 0) {
LOG_ERROR << "Failed to generate " << tx_num << " tx files";
}
}
CCL_NAMESPACE_END