Files
test2/intern/cycles/blender/image.cpp
Brecht Van Lommel 963c638a5b Fix #131132: Cycles reading packed multilayer EXR wrong
Rewrite Blender image loading to avoid RNA API, which we are moving away
from. This makes it easier to support multilayer EXR.

Pull Request: https://projects.blender.org/blender/blender/pulls/133179
2025-01-17 10:21:44 +01:00

319 lines
10 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <algorithm>
#include "DNA_image_types.h"
#include "IMB_imbuf_types.hh"
#include "BKE_image.hh"
#include "blender/image.h"
#include "blender/session.h"
#include "util/half.h"
#include "util/types_float4.h"
CCL_NAMESPACE_BEGIN
/* Packed Images */
BlenderImageLoader::BlenderImageLoader(Image *b_image,
ImageUser *b_iuser,
const int frame,
const int tile_number,
const bool is_preview_render)
: b_image(b_image),
b_iuser(*b_iuser),
/* Don't free cache for preview render to avoid race condition from #93560, to be fixed
* properly later as we are close to release. */
free_cache(!is_preview_render && !BKE_image_has_loaded_ibuf(b_image))
{
this->b_iuser.framenr = frame;
if (b_image->source != IMA_SRC_TILED) {
/* Image sequences currently not supported by this image loader. */
assert(b_image->source != IMA_SRC_SEQUENCE);
}
else {
/* Set UDIM tile, each can have different resolution. */
this->b_iuser.tile = tile_number;
}
}
bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
ImageMetaData &metadata)
{
bool is_float = false;
{
void *lock;
ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
if (ibuf) {
is_float = ibuf->float_buffer.data != nullptr;
metadata.width = ibuf->x;
metadata.height = ibuf->y;
metadata.channels = (is_float) ? ibuf->channels : 4;
}
else {
metadata.width = 0;
metadata.height = 0;
metadata.channels = 0;
}
BKE_image_release_ibuf(b_image, ibuf, lock);
}
metadata.depth = 1;
if (is_float) {
if (metadata.channels == 1) {
metadata.type = IMAGE_DATA_TYPE_FLOAT;
}
else {
metadata.channels = 4;
metadata.type = IMAGE_DATA_TYPE_FLOAT4;
}
/* Float images are already converted on the Blender side,
* no need to do anything in Cycles. */
metadata.colorspace = u_colorspace_raw;
}
else {
/* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the
* metadata queries in this function, so update the colorspace setting here. */
metadata.colorspace = b_image->colorspace_settings.name;
metadata.type = IMAGE_DATA_TYPE_BYTE4;
}
return true;
}
static void load_float_pixels(const ImBuf *ibuf, const ImageMetaData &metadata, float *out_pixels)
{
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const int in_channels = ibuf->channels;
const float *in_pixels = ibuf->float_buffer.data;
if (in_pixels && out_channels == in_channels) {
/* Straight copy pixel data. */
memcpy(out_pixels, in_pixels, num_pixels * out_channels * sizeof(float));
}
else if (in_pixels && out_channels == 4) {
/* Fill channels to 4. */
float *out_pixel = out_pixels;
const float *in_pixel = in_pixels;
for (size_t i = 0; i < num_pixels; i++) {
out_pixel[0] = in_pixel[0];
out_pixel[1] = (in_channels >= 2) ? in_pixel[1] : 0.0f;
out_pixel[2] = (in_channels >= 3) ? in_pixel[2] : 0.0f;
out_pixel[3] = (in_channels >= 4) ? in_pixel[3] : 1.0f;
out_pixel += out_channels;
in_pixel += in_channels;
}
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, 0.0f);
}
else {
std::fill((float4 *)out_pixels,
(float4 *)out_pixels + num_pixels,
make_float4(1.0f, 0.0f, 1.0f, 1.0f));
}
}
}
static void load_half_pixels(const ImBuf *ibuf,
const ImageMetaData &metadata,
half *out_pixels,
const bool associate_alpha)
{
/* Half float. Blender does not have a half type, but in some cases
* we up-sample byte to half to avoid precision loss for colorspace
* conversion. */
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const int in_channels = 4;
const uchar *in_pixels = ibuf->byte_buffer.data;
if (in_pixels) {
/* Convert uchar to half. */
const uchar *in_pixel = in_pixels;
half *out_pixel = out_pixels;
if (associate_alpha && out_channels == in_channels) {
for (size_t i = 0; i < num_pixels; i++, in_pixel += in_channels, out_pixel += out_channels) {
const float alpha = util_image_cast_to_float(in_pixel[3]);
out_pixel[0] = float_to_half_image(util_image_cast_to_float(in_pixel[0]) * alpha);
out_pixel[1] = float_to_half_image(util_image_cast_to_float(in_pixel[1]) * alpha);
out_pixel[2] = float_to_half_image(util_image_cast_to_float(in_pixel[2]) * alpha);
out_pixel[3] = float_to_half_image(alpha);
}
}
else {
for (size_t i = 0; i < num_pixels; i++) {
for (int c = 0; c < out_channels; c++, in_pixel++, out_pixel++) {
*out_pixel = float_to_half_image(util_image_cast_to_float(*in_pixel));
}
}
}
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, float_to_half_image(0.0f));
}
else {
std::fill((half4 *)out_pixels,
(half4 *)out_pixels + num_pixels,
float4_to_half4_display(make_float4(1.0f, 0.0f, 1.0f, 1.0f)));
}
}
}
static void load_byte_pixels(const ImBuf *ibuf,
const ImageMetaData &metadata,
uchar *out_pixels,
const bool associate_alpha)
{
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const int in_channels = 4;
const uchar *in_pixels = ibuf->byte_buffer.data;
if (in_pixels) {
/* Straight copy pixel data. */
memcpy(out_pixels, in_pixels, num_pixels * in_channels * sizeof(unsigned char));
if (associate_alpha && out_channels == in_channels) {
/* Premultiply, byte images are always straight for Blender. */
unsigned char *out_pixel = (unsigned char *)out_pixels;
for (size_t i = 0; i < num_pixels; i++, out_pixel += 4) {
out_pixel[0] = (out_pixel[0] * out_pixel[3]) / 255;
out_pixel[1] = (out_pixel[1] * out_pixel[3]) / 255;
out_pixel[2] = (out_pixel[2] * out_pixel[3]) / 255;
}
}
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, 0.0f);
}
else {
std::fill(
(uchar4 *)out_pixels, (uchar4 *)out_pixels + num_pixels, make_uchar4(255, 0, 255, 255));
}
}
}
bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata,
void *out_pixels,
const size_t /*out_pixels_size*/,
const bool associate_alpha)
{
void *lock;
ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
/* Image changed since we requested metadata, assume we'll get a signal to reload it later. */
const bool mismatch = (ibuf == nullptr || ibuf->x != metadata.width ||
ibuf->y != metadata.height);
if (!mismatch) {
if (metadata.type == IMAGE_DATA_TYPE_FLOAT || metadata.type == IMAGE_DATA_TYPE_FLOAT4) {
load_float_pixels(ibuf, metadata, (float *)out_pixels);
}
else if (metadata.type == IMAGE_DATA_TYPE_HALF || metadata.type == IMAGE_DATA_TYPE_HALF4) {
load_half_pixels(ibuf, metadata, (half *)out_pixels, associate_alpha);
}
else {
load_byte_pixels(ibuf, metadata, (uchar *)out_pixels, associate_alpha);
}
}
BKE_image_release_ibuf(b_image, ibuf, lock);
/* Free image buffers to save memory during render. */
if (free_cache) {
BKE_image_free_buffers_ex(b_image, true);
}
return !mismatch;
}
string BlenderImageLoader::name() const
{
return b_image->id.name + 2;
}
bool BlenderImageLoader::equals(const ImageLoader &other) const
{
const BlenderImageLoader &other_loader = (const BlenderImageLoader &)other;
return b_image == other_loader.b_image && b_iuser.framenr == other_loader.b_iuser.framenr &&
b_iuser.tile == other_loader.b_iuser.tile;
}
int BlenderImageLoader::get_tile_number() const
{
return b_iuser.tile;
}
/* Point Density */
BlenderPointDensityLoader::BlenderPointDensityLoader(BL::Depsgraph b_depsgraph,
BL::ShaderNodeTexPointDensity b_node)
: b_depsgraph(b_depsgraph), b_node(b_node)
{
}
bool BlenderPointDensityLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
ImageMetaData &metadata)
{
metadata.channels = 4;
metadata.width = b_node.resolution();
metadata.height = metadata.width;
metadata.depth = metadata.width;
metadata.type = IMAGE_DATA_TYPE_FLOAT4;
return true;
}
bool BlenderPointDensityLoader::load_pixels(const ImageMetaData & /*metadata*/,
void *pixels,
const size_t /*pixels_size*/,
const bool /*associate_alpha*/)
{
int length;
b_node.calc_point_density(b_depsgraph, &length, (float **)&pixels);
return true;
}
void BlenderSession::builtin_images_load()
{
/* Force builtin images to be loaded along with Blender data sync. This
* is needed because we may be reading from depsgraph evaluated data which
* can be freed by Blender before Cycles reads it.
*
* TODO: the assumption that no further access to builtin image data will
* happen is really weak, and likely to break in the future. We should find
* a better solution to hand over the data directly to the image manager
* instead of through callbacks whose timing is difficult to control. */
ImageManager *manager = session->scene->image_manager.get();
Device *device = session->device.get();
manager->device_load_builtin(device, session->scene.get(), session->progress);
}
string BlenderPointDensityLoader::name() const
{
return BL::ShaderNodeTexPointDensity(b_node).name();
}
bool BlenderPointDensityLoader::equals(const ImageLoader &other) const
{
const BlenderPointDensityLoader &other_loader = (const BlenderPointDensityLoader &)other;
return b_node == other_loader.b_node && b_depsgraph == other_loader.b_depsgraph;
}
CCL_NAMESPACE_END