Eevee/Workbench: store 8 bit image textures as half float for some color spaces

Same as in Cycles, this is needed for some color space conversions that don't
compress well to byte sRGB, like for example Filmic sRGB.

Ref T68926
This commit is contained in:
Brecht Van Lommel 2022-06-03 14:13:10 +02:00
parent 4c4056579b
commit 9d8fb80f21
Notes: blender-bot 2023-02-14 01:07:48 +01:00
Referenced by issue #94421, JPG Marked as Filmic Log in Image Editor has Worse Quality than Staight Output in Compositor
Referenced by issue #68926, Color Management Improvements
4 changed files with 149 additions and 88 deletions

View File

@ -718,12 +718,31 @@ static void gpu_texture_update_from_ibuf(
int tex_offset = ibuf->channels * (y * ibuf->x + x);
const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf);
if (rect_float == nullptr) {
/* Byte pixels. */
if (!IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) {
const bool compress_as_srgb = !IMB_colormanagement_space_is_scene_linear(
ibuf->rect_colorspace);
if (rect_float) {
/* Float image is already in scene linear colorspace or non-color data by
* convention, no colorspace conversion needed. But we do require 4 channels
* currently. */
if (ibuf->channels != 4 || scaled || !store_premultiplied) {
rect_float = (float *)MEM_mallocN(sizeof(float[4]) * w * h, __func__);
if (rect_float == nullptr) {
return;
}
tex_stride = w;
tex_offset = 0;
IMB_colormanagement_imbuf_to_float_texture(
rect_float, x, y, w, h, ibuf, store_premultiplied);
}
}
else {
/* Byte image is in original colorspace from the file, and may need conversion. */
if (IMB_colormanagement_space_is_data(ibuf->rect_colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace)) {
/* Non-color data, just store buffer as is. */
}
else if (IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace)) {
/* sRGB or scene linear, store as byte texture that the GPU can decode directly. */
rect = (uchar *)MEM_mallocN(sizeof(uchar[4]) * w * h, __func__);
if (rect == nullptr) {
return;
@ -734,13 +753,10 @@ static void gpu_texture_update_from_ibuf(
/* Convert to scene linear with sRGB compression, and premultiplied for
* correct texture interpolation. */
IMB_colormanagement_imbuf_to_byte_texture(
rect, x, y, w, h, ibuf, compress_as_srgb, store_premultiplied);
IMB_colormanagement_imbuf_to_byte_texture(rect, x, y, w, h, ibuf, store_premultiplied);
}
}
else {
/* Float pixels. */
if (ibuf->channels != 4 || scaled || !store_premultiplied) {
else {
/* Other colorspace, store as float texture to avoid precision loss. */
rect_float = (float *)MEM_mallocN(sizeof(float[4]) * w * h, __func__);
if (rect_float == nullptr) {
return;

View File

@ -181,7 +181,6 @@ void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
int width,
int height,
const struct ImBuf *ibuf,
bool compress_as_srgb,
bool store_premultiplied);
void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
int offset_x,

View File

@ -2211,21 +2211,14 @@ void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
const int width,
const int height,
const struct ImBuf *ibuf,
const bool compress_as_srgb,
const bool store_premultiplied)
{
/* Convert byte buffer for texture storage on the GPU. These have builtin
* support for converting sRGB to linear, which allows us to store textures
* without precision or performance loss at minimal memory usage. */
/* Byte buffer storage, only for sRGB and data texture since other
* color space conversions can't be done on the GPU. */
BLI_assert(ibuf->rect && ibuf->rect_float == NULL);
BLI_assert(IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace) ||
IMB_colormanagement_space_is_data(ibuf->rect_colorspace));
OCIO_ConstCPUProcessorRcPtr *processor = NULL;
if (compress_as_srgb && ibuf->rect_colorspace &&
!IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace)) {
processor = colorspace_to_scene_linear_cpu_processor(ibuf->rect_colorspace);
}
/* TODO(brecht): make this multi-threaded, or at least process in batches. */
const unsigned char *in_buffer = (unsigned char *)ibuf->rect;
const bool use_premultiply = IMB_alpha_affects_rgb(ibuf) && store_premultiplied;
@ -2235,20 +2228,7 @@ void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
const unsigned char *in = in_buffer + in_offset * 4;
unsigned char *out = out_buffer + out_offset * 4;
if (processor != NULL) {
/* Convert to scene linear, to sRGB and premultiply. */
for (int x = 0; x < width; x++, in += 4, out += 4) {
float pixel[4];
rgba_uchar_to_float(pixel, in);
OCIO_cpuProcessorApplyRGB(processor, pixel);
linearrgb_to_srgb_v3_v3(pixel, pixel);
if (use_premultiply) {
mul_v3_fl(pixel, pixel[3]);
}
rgba_float_to_uchar(out, pixel);
}
}
else if (use_premultiply) {
if (use_premultiply) {
/* Premultiply only. */
for (int x = 0; x < width; x++, in += 4, out += 4) {
out[0] = (in[0] * in[3]) >> 8;
@ -2279,43 +2259,80 @@ void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
{
/* Float texture are stored in scene linear color space, with premultiplied
* alpha depending on the image alpha mode. */
const float *in_buffer = ibuf->rect_float;
const int in_channels = ibuf->channels;
const bool use_unpremultiply = IMB_alpha_affects_rgb(ibuf) && !store_premultiplied;
if (ibuf->rect_float) {
/* Float source buffer. */
const float *in_buffer = ibuf->rect_float;
const int in_channels = ibuf->channels;
const bool use_unpremultiply = IMB_alpha_affects_rgb(ibuf) && !store_premultiplied;
for (int y = 0; y < height; y++) {
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
const size_t out_offset = y * width;
const float *in = in_buffer + in_offset * in_channels;
float *out = out_buffer + out_offset * 4;
for (int y = 0; y < height; y++) {
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
const size_t out_offset = y * width;
const float *in = in_buffer + in_offset * in_channels;
float *out = out_buffer + out_offset * 4;
if (in_channels == 1) {
/* Copy single channel. */
for (int x = 0; x < width; x++, in += 1, out += 4) {
out[0] = in[0];
out[1] = in[0];
out[2] = in[0];
out[3] = in[0];
}
}
else if (in_channels == 3) {
/* Copy RGB. */
for (int x = 0; x < width; x++, in += 3, out += 4) {
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
out[3] = 1.0f;
}
}
else if (in_channels == 4) {
/* Copy or convert RGBA. */
if (use_unpremultiply) {
for (int x = 0; x < width; x++, in += 4, out += 4) {
premul_to_straight_v4_v4(out, in);
if (in_channels == 1) {
/* Copy single channel. */
for (int x = 0; x < width; x++, in += 1, out += 4) {
out[0] = in[0];
out[1] = in[0];
out[2] = in[0];
out[3] = in[0];
}
}
else {
memcpy(out, in, sizeof(float[4]) * width);
else if (in_channels == 3) {
/* Copy RGB. */
for (int x = 0; x < width; x++, in += 3, out += 4) {
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
out[3] = 1.0f;
}
}
else if (in_channels == 4) {
/* Copy or convert RGBA. */
if (use_unpremultiply) {
for (int x = 0; x < width; x++, in += 4, out += 4) {
premul_to_straight_v4_v4(out, in);
}
}
else {
memcpy(out, in, sizeof(float[4]) * width);
}
}
}
}
else {
/* Byte source buffer. */
const unsigned char *in_buffer = (unsigned char *)ibuf->rect;
const bool use_premultiply = IMB_alpha_affects_rgb(ibuf) && store_premultiplied;
/* TODO(brecht): make this multi-threaded, or at least process in batches. */
OCIO_ConstCPUProcessorRcPtr *processor = (ibuf->rect_colorspace) ?
colorspace_to_scene_linear_cpu_processor(
ibuf->rect_colorspace) :
NULL;
for (int y = 0; y < height; y++) {
const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
const size_t out_offset = y * width;
const unsigned char *in = in_buffer + in_offset * 4;
float *out = out_buffer + out_offset * 4;
/* Convert to scene linear, to sRGB and premultiply. */
for (int x = 0; x < width; x++, in += 4, out += 4) {
float pixel[4];
rgba_uchar_to_float(pixel, in);
if (processor) {
OCIO_cpuProcessorApplyRGB(processor, pixel);
}
else {
srgb_to_linearrgb_v3_v3(pixel, pixel);
}
if (use_premultiply) {
mul_v3_fl(pixel, pixel[3]);
}
copy_v4_v4(out, pixel);
}
}
}

View File

@ -28,17 +28,30 @@ static void imb_gpu_get_format(const ImBuf *ibuf,
eGPUTextureFormat *r_texture_format)
{
const bool float_rect = (ibuf->rect_float != NULL);
const bool use_srgb = (!IMB_colormanagement_space_is_data(ibuf->rect_colorspace) &&
!IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace));
high_bitdepth = (!(ibuf->flags & IB_halffloat) && high_bitdepth);
*r_data_format = (float_rect) ? GPU_DATA_FLOAT : GPU_DATA_UBYTE;
if (float_rect) {
*r_texture_format = high_bitdepth ? GPU_RGBA32F : GPU_RGBA16F;
/* Float. */
const bool use_high_bitdepth = (!(ibuf->flags & IB_halffloat) && high_bitdepth);
*r_data_format = GPU_DATA_FLOAT;
*r_texture_format = use_high_bitdepth ? GPU_RGBA32F : GPU_RGBA16F;
}
else {
*r_texture_format = use_srgb ? GPU_SRGB8_A8 : GPU_RGBA8;
if (IMB_colormanagement_space_is_data(ibuf->rect_colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace)) {
/* Non-color data or scene linear, just store buffer as is. */
*r_data_format = GPU_DATA_UBYTE;
*r_texture_format = GPU_RGBA8;
}
else if (IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace)) {
/* sRGB, store as byte texture that the GPU can decode directly. */
*r_data_format = GPU_DATA_UBYTE;
*r_texture_format = GPU_SRGB8_A8;
}
else {
/* Other colorspace, store as half float texture to avoid precision loss. */
*r_data_format = GPU_DATA_FLOAT;
*r_texture_format = GPU_RGBA16F;
}
}
}
@ -74,7 +87,6 @@ static bool IMB_gpu_get_compressed_format(const ImBuf *ibuf, eGPUTextureFormat *
static void *imb_gpu_get_data(const ImBuf *ibuf,
const bool do_rescale,
const int rescale_size[2],
const bool compress_as_srgb,
const bool store_premultiplied,
bool *r_freedata)
{
@ -99,14 +111,16 @@ static void *imb_gpu_get_data(const ImBuf *ibuf,
}
}
else {
/* Byte image is in original colorspace from the file. If the file is sRGB
* scene linear, or non-color data no conversion is needed. Otherwise we
* compress as scene linear + sRGB transfer function to avoid precision loss
* in common cases.
/* Byte image is in original colorspace from the file, and may need conversion.
*
* We must also convert to premultiplied for correct texture interpolation
* and consistency with float images. */
if (!IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) {
if (IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) {
/* Non-color data, just store buffer as is. */
}
else if (IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace) ||
IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace)) {
/* sRGB or scene linear, store as byte texture that the GPU can decode directly. */
data_rect = MEM_mallocN(sizeof(uchar[4]) * ibuf->x * ibuf->y, __func__);
*r_freedata = freedata = true;
@ -120,7 +134,24 @@ static void *imb_gpu_get_data(const ImBuf *ibuf,
* zero alpha areas, and appears generally closer to what game engines that we
* want to be compatible with do. */
IMB_colormanagement_imbuf_to_byte_texture(
(uchar *)data_rect, 0, 0, ibuf->x, ibuf->y, ibuf, compress_as_srgb, store_premultiplied);
(uchar *)data_rect, 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
}
else {
/* Other colorspace, store as float texture to avoid precision loss. */
data_rect = MEM_mallocN(sizeof(float[4]) * ibuf->x * ibuf->y, __func__);
*r_freedata = freedata = true;
if (data_rect == NULL) {
return NULL;
}
/* Texture storage of images is defined by the alpha mode of the image. The
* downside of this is that there can be artifacts near alpha edges. However,
* this allows us to use sRGB texture formats and preserves color values in
* zero alpha areas, and appears generally closer to what game engines that we
* want to be compatible with do. */
IMB_colormanagement_imbuf_to_float_texture(
(float *)data_rect, 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
}
}
@ -181,10 +212,9 @@ void IMB_update_gpu_texture_sub(GPUTexture *tex,
eGPUTextureFormat tex_format;
imb_gpu_get_format(ibuf, use_high_bitdepth, &data_format, &tex_format);
const bool compress_as_srgb = (tex_format == GPU_SRGB8_A8);
bool freebuf = false;
void *data = imb_gpu_get_data(ibuf, do_rescale, size, compress_as_srgb, use_premult, &freebuf);
void *data = imb_gpu_get_data(ibuf, do_rescale, size, use_premult, &freebuf);
/* Update Texture. */
GPU_texture_update_sub(tex, data_format, data, x, y, z, w, h, 1);
@ -238,7 +268,6 @@ GPUTexture *IMB_create_gpu_texture(const char *name,
eGPUTextureFormat tex_format;
imb_gpu_get_format(ibuf, use_high_bitdepth, &data_format, &tex_format);
const bool compress_as_srgb = (tex_format == GPU_SRGB8_A8);
bool freebuf = false;
/* Create Texture. */
@ -250,7 +279,7 @@ GPUTexture *IMB_create_gpu_texture(const char *name,
do_rescale = true;
}
BLI_assert(tex != NULL);
void *data = imb_gpu_get_data(ibuf, do_rescale, size, compress_as_srgb, use_premult, &freebuf);
void *data = imb_gpu_get_data(ibuf, do_rescale, size, use_premult, &freebuf);
GPU_texture_update(tex, data_format, data);
GPU_texture_anisotropic_filter(tex, true);