Cycles/Eevee: unified and improved texture image color space handling
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Tue, 23 Apr 2019 11:56:30 +0000 (13:56 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Mon, 13 May 2019 13:56:10 +0000 (15:56 +0200)
Cycles now uses the color space on the image datablock, and uses OpenColorIO
to convert to scene linear as needed. Byte images do not take extra memory,
they are compressed in scene linear + sRGB transfer function which in common
cases is a no-op.

Eevee and workbench were changed to work similar. Float images are stored as
scene linear. Byte images are compressed as scene linear + sRGB and stored in
a GL_SRGB8_ALPHA8 texture. From the GLSL shader side this means they are read
as scene linear, simplifying the code and taking advantage of hardware support.

Further, OpenGL image textures are now all stored with premultiplied alpha.
Eevee texture sampling looks a little different now because interpolation
happens premultiplied and in scene linear space.

Overlays and grease pencil work in sRGB space so those now have an extra
conversion to sRGB after reading from image textures. This is not particularly
elegant but as long as engines use different conventions, one or the other
needs to do conversion.

This change breaks compatibility for cases where multiple image texture nodes
were using the same image with different color space node settings. However it
gives more predictable behavior for baking and texture painting if save, load
and image editing operations have a single color space to handle.

Differential Revision: https://developer.blender.org/D4807

42 files changed:
intern/cycles/blender/blender_shader.cpp
intern/cycles/render/colorspace.cpp
intern/opencolorio/ocio_capi.h
intern/opencolorio/ocio_impl.cc
source/blender/blenkernel/BKE_blender_version.h
source/blender/blenloader/intern/versioning_cycles.c
source/blender/draw/engines/gpencil/gpencil_draw_utils.c
source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl
source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl
source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl
source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl
source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl
source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl
source/blender/draw/engines/workbench/workbench_forward.c
source/blender/draw/engines/workbench/workbench_materials.c
source/blender/draw/intern/draw_manager_data.c
source/blender/draw/modes/object_mode.c
source/blender/draw/modes/paint_texture_mode.c
source/blender/draw/modes/shaders/object_empty_image_frag.glsl
source/blender/draw/modes/shaders/paint_texture_frag.glsl
source/blender/editors/sculpt_paint/paint_image_proj.c
source/blender/editors/space_node/drawnode.c
source/blender/gpu/GPU_draw.h
source/blender/gpu/GPU_material.h
source/blender/gpu/GPU_texture.h
source/blender/gpu/intern/gpu_codegen.c
source/blender/gpu/intern/gpu_codegen.h
source/blender/gpu/intern/gpu_draw.c
source/blender/gpu/intern/gpu_material.c
source/blender/gpu/intern/gpu_texture.c
source/blender/gpu/shaders/gpu_shader_material.glsl
source/blender/imbuf/IMB_colormanagement.h
source/blender/imbuf/intern/IMB_colormanagement_intern.h
source/blender/imbuf/intern/colormanagement.c
source/blender/makesdna/DNA_image_types.h
source/blender/makesdna/DNA_node_types.h
source/blender/makesrna/intern/rna_color.c
source/blender/makesrna/intern/rna_image_api.c
source/blender/makesrna/intern/rna_nodetree.c
source/blender/nodes/shader/node_shader_util.h
source/blender/nodes/shader/nodes/node_shader_tex_environment.c
source/blender/nodes/shader/nodes/node_shader_tex_image.c

index e6ec8b2..a08f767 100644 (file)
@@ -651,6 +651,9 @@ static ShaderNode *add_node(Scene *scene,
         image->builtin_data = NULL;
       }
 
+      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
+      image->colorspace = get_enum_identifier(colorspace_ptr, "name");
+
       image->animated = b_image_node.image_user().use_auto_refresh();
       image->use_alpha = b_image.use_alpha();
 
@@ -662,18 +665,11 @@ static ShaderNode *add_node(Scene *scene,
                                                image->builtin_data,
                                                get_image_interpolation(b_image_node),
                                                get_image_extension(b_image_node),
-                                               image->use_alpha);
+                                               image->use_alpha,
+                                               image->colorspace);
       }
 #endif
     }
-    switch (b_image_node.color_space()) {
-      case BL::ShaderNodeTexImage::color_space_NONE:
-        image->colorspace = u_colorspace_raw;
-        break;
-      case BL::ShaderNodeTexImage::color_space_COLOR:
-        image->colorspace = u_colorspace_auto;
-        break;
-    }
     image->projection = (NodeImageProjection)b_image_node.projection();
     image->interpolation = get_image_interpolation(b_image_node);
     image->extension = get_image_extension(b_image_node);
@@ -703,6 +699,9 @@ static ShaderNode *add_node(Scene *scene,
         env->builtin_data = NULL;
       }
 
+      PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
+      env->colorspace = get_enum_identifier(colorspace_ptr, "name");
+
       env->animated = b_env_node.image_user().use_auto_refresh();
       env->use_alpha = b_image.use_alpha();
 
@@ -714,18 +713,11 @@ static ShaderNode *add_node(Scene *scene,
                                                env->builtin_data,
                                                get_image_interpolation(b_env_node),
                                                EXTENSION_REPEAT,
-                                               env->use_alpha);
+                                               env->use_alpha,
+                                               env->colorspace);
       }
 #endif
     }
-    switch (b_env_node.color_space()) {
-      case BL::ShaderNodeTexEnvironment::color_space_NONE:
-        env->colorspace = u_colorspace_raw;
-        break;
-      case BL::ShaderNodeTexEnvironment::color_space_COLOR:
-        env->colorspace = u_colorspace_auto;
-        break;
-    }
     env->interpolation = get_image_interpolation(b_env_node);
     env->projection = (NodeEnvironmentProjection)b_env_node.projection();
     BL::TexMapping b_texture_mapping(b_env_node.texture_mapping());
index cdce1f7..7b57478 100644 (file)
@@ -242,6 +242,10 @@ inline void processor_apply_pixels(const OCIO::Processor *processor,
                                    size_t width,
                                    size_t height)
 {
+  /* TODO: implement faster version for when we know the conversion
+   * is a simple matrix transform between linear spaces. In that case
+   * unpremultiply is not needed. */
+
   /* Process large images in chunks to keep temporary memory requirement down. */
   size_t y_chunk_size = max(1, 16 * 1024 * 1024 / (sizeof(float4) * width));
   vector<float4> float_pixels(y_chunk_size * width);
@@ -252,7 +256,16 @@ inline void processor_apply_pixels(const OCIO::Processor *processor,
 
     for (size_t y = y0; y < y1; y++) {
       for (size_t x = 0; x < width; x++, i++) {
-        float_pixels[i] = cast_to_float4(pixels + 4 * (y * width + x));
+        float4 value = cast_to_float4(pixels + 4 * (y * width + x));
+
+        if (!(value.w == 0.0f || value.w == 1.0f)) {
+          float inv_alpha = 1.0f / value.w;
+          value.x *= inv_alpha;
+          value.y *= inv_alpha;
+          value.z *= inv_alpha;
+        }
+
+        float_pixels[i] = value;
       }
     }
 
@@ -263,25 +276,20 @@ inline void processor_apply_pixels(const OCIO::Processor *processor,
     for (size_t y = y0; y < y1; y++) {
       for (size_t x = 0; x < width; x++, i++) {
         float4 value = float_pixels[i];
+
+        value.x *= value.w;
+        value.y *= value.w;
+        value.z *= value.w;
+
         if (compress_as_srgb) {
           value = color_linear_to_srgb_v4(value);
         }
+
         cast_from_float4(pixels + 4 * (y * width + x), value);
       }
     }
   }
 }
-
-/* Fast version for float images, which OpenColorIO can handle natively. */
-template<>
-inline void processor_apply_pixels(const OCIO::Processor *processor,
-                                   float *pixels,
-                                   size_t width,
-                                   size_t height)
-{
-  OCIO::PackedImageDesc desc(pixels, width, height, 4);
-  processor->apply(desc);
-}
 #endif
 
 template<typename T>
index f4d7717..9ba2d8f 100644 (file)
@@ -31,6 +31,7 @@ struct OCIO_GLSLDrawState;
     int unused; \
   } * name
 
+#define OCIO_ROLE_DATA "data"
 #define OCIO_ROLE_SCENE_LINEAR "scene_linear"
 #define OCIO_ROLE_COLOR_PICKING "color_picking"
 #define OCIO_ROLE_TEXTURE_PAINT "texture_paint"
index 644117e..25c1df7 100644 (file)
@@ -552,7 +552,7 @@ void OCIOImpl::colorSpaceIsBuiltin(OCIO_ConstConfigRcPtr *config_,
     processor = (*config)->getProcessor((*cs)->getName(), "scene_linear");
   }
   catch (Exception &exception) {
-    OCIO_reportException(exception);
+    /* Silently ignore if no conversion possible, then it's not scene linear or sRGB. */
     is_scene_linear = false;
     is_srgb = false;
     return;
index 16275c9..74d33a9 100644 (file)
@@ -27,7 +27,7 @@
  * \note Use #STRINGIFY() rather than defining with quotes.
  */
 #define BLENDER_VERSION 280
-#define BLENDER_SUBVERSION 62
+#define BLENDER_SUBVERSION 63
 /** Several breakages with 280, e.g. collections vs layers. */
 #define BLENDER_MINVERSION 280
 #define BLENDER_MINSUBVERSION 0
index 023bd68..2bd379c 100644 (file)
@@ -38,6 +38,8 @@
 #include "BKE_main.h"
 #include "BKE_node.h"
 
+#include "IMB_colormanagement.h"
+
 #include "BLO_readfile.h"
 #include "readfile.h"
 
@@ -262,6 +264,33 @@ static void ambient_occlusion_node_relink(bNodeTree *ntree)
   }
 }
 
+static void image_node_colorspace(bNode *node)
+{
+  if (node->id == NULL) {
+    return;
+  }
+
+  int color_space;
+  if (node->type == SH_NODE_TEX_IMAGE) {
+    NodeTexImage *tex = node->storage;
+    color_space = tex->color_space;
+  }
+  else if (node->type == SH_NODE_TEX_ENVIRONMENT) {
+    NodeTexEnvironment *tex = node->storage;
+    color_space = tex->color_space;
+  }
+  else {
+    return;
+  }
+
+  const int SHD_COLORSPACE_NONE = 0;
+  Image *image = (Image *)node->id;
+  if (color_space == SHD_COLORSPACE_NONE) {
+    STRNCPY(image->colorspace_settings.name,
+            IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA));
+  }
+}
+
 void blo_do_versions_cycles(FileData *UNUSED(fd), Library *UNUSED(lib), Main *bmain)
 {
   /* Particle shape shared with Eevee. */
@@ -326,6 +355,12 @@ void do_versions_after_linking_cycles(Main *bmain)
       if (!MAIN_VERSION_ATLEAST(bmain, 279, 5)) {
         ambient_occlusion_node_relink(ntree);
       }
+
+      if (!MAIN_VERSION_ATLEAST(bmain, 280, 63)) {
+        for (bNode *node = ntree->nodes.first; node; node = node->next) {
+          image_node_colorspace(node);
+        }
+      }
     }
     FOREACH_NODETREE_END;
   }
index c23b4c2..d57c43a 100644 (file)
@@ -465,7 +465,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_fill_create(GPENCIL_e_data *e_data,
       BKE_image_release_ibuf(image, ibuf, NULL);
     }
     else {
-      GPUTexture *texture = GPU_texture_from_blender(gp_style->ima, &iuser, GL_TEXTURE_2D, true);
+      GPUTexture *texture = GPU_texture_from_blender(gp_style->ima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
 
       stl->shgroups[id].texture_clamp = gp_style->flag & GP_STYLE_COLOR_TEX_CLAMP ? 1 : 0;
@@ -631,7 +631,7 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create(GPENCIL_e_data *e_data,
       BKE_image_release_ibuf(image, ibuf, NULL);
     }
     else {
-      GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true);
+      GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
 
       BKE_image_release_ibuf(image, ibuf, NULL);
@@ -787,7 +787,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create(GPENCIL_e_data *e_data,
       BKE_image_release_ibuf(image, ibuf, NULL);
     }
     else {
-      GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D, true);
+      GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
 
       BKE_image_release_ibuf(image, ibuf, NULL);
index acf60fc..80fae9a 100644 (file)
@@ -89,6 +89,27 @@ void set_color(in vec4 color,
   ocolor.a *= layer_opacity;
 }
 
+float linearrgb_to_srgb(float c)
+{
+  if (c < 0.0031308) {
+    return (c < 0.0) ? 0.0 : c * 12.92;
+  }
+  else {
+    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+  }
+}
+
+vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+{
+  /* By convention image textures return scene linear colors, but
+   * grease pencil still works in srgb. */
+  vec4 color = texture2D(tex, co);
+  color.r = linearrgb_to_srgb(color.r);
+  color.g = linearrgb_to_srgb(color.g);
+  color.b = linearrgb_to_srgb(color.b);
+  return color;
+}
+
 void main()
 {
   vec2 t_center = vec2(0.5, 0.5);
@@ -97,8 +118,8 @@ void main()
   vec2 rot_tex = (matrot_tex * (texCoord_interp - t_center)) + t_center + texture_offset;
   vec4 tmp_color;
   tmp_color = (texture_clamp == 0) ?
-                  texture2D(myTexture, rot_tex * texture_scale) :
-                  texture2D(myTexture, clamp(rot_tex * texture_scale, 0.0, 1.0));
+                  texture_read_as_srgb(myTexture, rot_tex * texture_scale) :
+                  texture_read_as_srgb(myTexture, clamp(rot_tex * texture_scale, 0.0, 1.0));
   vec4 text_color = vec4(tmp_color[0], tmp_color[1], tmp_color[2], tmp_color[3] * texture_opacity);
   vec4 chesscolor;
 
index cc47e12..98c47b1 100644 (file)
@@ -47,6 +47,27 @@ vec2 check_box_point(vec2 pt, vec2 radius)
   return rtn;
 }
 
+float linearrgb_to_srgb(float c)
+{
+  if (c < 0.0031308) {
+    return (c < 0.0) ? 0.0 : c * 12.92;
+  }
+  else {
+    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+  }
+}
+
+vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+{
+  /* By convention image textures return scene linear colors, but
+   * grease pencil still works in srgb. */
+  vec4 color = texture2D(tex, co);
+  color.r = linearrgb_to_srgb(color.r);
+  color.g = linearrgb_to_srgb(color.g);
+  color.b = linearrgb_to_srgb(color.b);
+  return color;
+}
+
 void main()
 {
   vec2 centered = mTexCoord - vec2(0.5);
@@ -65,7 +86,7 @@ void main()
     }
   }
 
-  vec4 tmp_color = texture2D(myTexture, mTexCoord);
+  vec4 tmp_color = texture_read_as_srgb(myTexture, mTexCoord);
 
   /* Solid */
   if ((color_type == GPENCIL_COLOR_SOLID) || (no_texture)) {
@@ -73,7 +94,7 @@ void main()
   }
   /* texture */
   if ((color_type == GPENCIL_COLOR_TEXTURE) && (!no_texture)) {
-    vec4 text_color = texture2D(myTexture, mTexCoord);
+    vec4 text_color = texture_read_as_srgb(myTexture, mTexCoord);
     if (mix_stroke_factor > 0.0) {
       fragColor.rgb = mix(text_color.rgb, colormix.rgb, mix_stroke_factor);
       fragColor.a = text_color.a;
@@ -87,7 +108,7 @@ void main()
   }
   /* pattern */
   if ((color_type == GPENCIL_COLOR_PATTERN) && (!no_texture)) {
-    vec4 text_color = texture2D(myTexture, mTexCoord);
+    vec4 text_color = texture_read_as_srgb(myTexture, mTexCoord);
     fragColor = mColor;
     /* mult both alpha factor to use strength factor with color alpha limit */
     fragColor.a = min(text_color.a * mColor.a, mColor.a);
index 6b7cee8..6b3fcad 100644 (file)
@@ -27,6 +27,27 @@ out vec4 fragColor;
 
 bool no_texture = (shading_type[0] == OB_SOLID) && (shading_type[1] != V3D_SHADING_TEXTURE_COLOR);
 
+float linearrgb_to_srgb(float c)
+{
+  if (c < 0.0031308) {
+    return (c < 0.0) ? 0.0 : c * 12.92;
+  }
+  else {
+    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+  }
+}
+
+vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+{
+  /* By convention image textures return scene linear colors, but
+   * grease pencil still works in srgb. */
+  vec4 color = texture2D(tex, co);
+  color.r = linearrgb_to_srgb(color.r);
+  color.g = linearrgb_to_srgb(color.g);
+  color.b = linearrgb_to_srgb(color.b);
+  return color;
+}
+
 void main()
 {
 
@@ -47,10 +68,10 @@ void main()
   /* texture for endcaps */
   vec4 text_color;
   if (uvfac[1] == ENDCAP) {
-    text_color = texture2D(myTexture, vec2(mTexCoord.x, mTexCoord.y));
+    text_color = texture_read_as_srgb(myTexture, vec2(mTexCoord.x, mTexCoord.y));
   }
   else {
-    text_color = texture2D(myTexture, mTexCoord);
+    text_color = texture_read_as_srgb(myTexture, mTexCoord);
   }
 
   /* texture */
index c76ad8c..96f8f6e 100644 (file)
@@ -140,32 +140,18 @@ vec2 matcap_uv_compute(vec3 I, vec3 N, bool flipped)
   return matcap_uv * 0.496 + 0.5;
 }
 
-float srgb_to_linearrgb(float c)
-{
-  if (c < 0.04045) {
-    return (c < 0.0) ? 0.0 : c * (1.0 / 12.92);
-  }
-  else {
-    return pow((c + 0.055) * (1.0 / 1.055), 2.4);
-  }
-}
-
-vec4 srgb_to_linearrgb(vec4 col_from)
-{
-  vec4 col_to;
-  col_to.r = srgb_to_linearrgb(col_from.r);
-  col_to.g = srgb_to_linearrgb(col_from.g);
-  col_to.b = srgb_to_linearrgb(col_from.b);
-  col_to.a = col_from.a;
-  return col_to;
-}
-
-vec4 workbench_sample_texture(sampler2D image, vec2 coord, bool srgb, bool nearest_sampling)
+vec4 workbench_sample_texture(sampler2D image, vec2 coord, bool nearest_sampling)
 {
   vec2 tex_size = vec2(textureSize(image, 0).xy);
   /* TODO(fclem) We could do the same with sampler objects.
    * But this is a quick workaround instead of messing with the GPUTexture itself. */
   vec2 uv = nearest_sampling ? (floor(coord * tex_size) + 0.5) / tex_size : coord;
   vec4 color = texture(image, uv);
-  return (srgb) ? srgb_to_linearrgb(color) : color;
+
+  /* Unpremultiply, ideally shaders would be added so this is not needed. */
+  if (!(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
+
+  return color;
 }
index 3224378..51bce63 100644 (file)
@@ -1,7 +1,6 @@
 
 uniform float ImageTransparencyCutoff = 0.1;
 uniform sampler2D image;
-uniform bool imageSrgb;
 uniform bool imageNearest;
 
 uniform float alpha = 0.5;
@@ -44,7 +43,7 @@ void main()
   vec4 diffuse_color;
 
 #if defined(V3D_SHADING_TEXTURE_COLOR)
-  diffuse_color = workbench_sample_texture(image, uv_interp, imageSrgb, imageNearest);
+  diffuse_color = workbench_sample_texture(image, uv_interp, imageNearest);
   if (diffuse_color.a < ImageTransparencyCutoff) {
     discard;
   }
index 2596fc4..af9f1d1 100644 (file)
@@ -6,7 +6,6 @@ uniform float materialRoughness;
 
 uniform sampler2D image;
 uniform float ImageTransparencyCutoff = 0.1;
-uniform bool imageSrgb;
 uniform bool imageNearest;
 
 #ifdef NORMAL_VIEWPORT_PASS_ENABLED
@@ -41,7 +40,7 @@ void main()
   vec4 color;
 
 #  if defined(V3D_SHADING_TEXTURE_COLOR)
-  color = workbench_sample_texture(image, uv_interp, imageSrgb, imageNearest);
+  color = workbench_sample_texture(image, uv_interp, imageNearest);
   if (color.a < ImageTransparencyCutoff) {
     discard;
   }
index 25f95f0..13bd6fe 100644 (file)
@@ -202,8 +202,7 @@ WORKBENCH_MaterialData *workbench_forward_get_or_create_material_data(WORKBENCH_
         V3D_SHADING_TEXTURE_COLOR) {
       material->shgrp_object_outline = DRW_shgroup_create(sh_data->object_outline_texture_sh,
                                                           psl->object_outline_pass);
-      GPUTexture *tex = GPU_texture_from_blender(
-          material->ima, material->iuser, GL_TEXTURE_2D, false);
+      GPUTexture *tex = GPU_texture_from_blender(material->ima, material->iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(material->shgrp_object_outline, "image", tex);
     }
     else {
index c403e35..b280b6f 100644 (file)
@@ -294,15 +294,8 @@ void workbench_material_shgroup_uniform(WORKBENCH_PrivateData *wpd,
 
   if (workbench_material_determine_color_type(wpd, material->ima, ob, false) ==
       V3D_SHADING_TEXTURE_COLOR) {
-    ImBuf *ibuf = BKE_image_acquire_ibuf(material->ima, material->iuser, NULL);
-    const bool do_color_correction = wpd->use_color_management &&
-                                     (ibuf &&
-                                      (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) == 0);
-    BKE_image_release_ibuf(material->ima, ibuf, NULL);
-    GPUTexture *tex = GPU_texture_from_blender(
-        material->ima, material->iuser, GL_TEXTURE_2D, false);
+    GPUTexture *tex = GPU_texture_from_blender(material->ima, material->iuser, GL_TEXTURE_2D);
     DRW_shgroup_uniform_texture(grp, "image", tex);
-    DRW_shgroup_uniform_bool_copy(grp, "imageSrgb", do_color_correction);
     DRW_shgroup_uniform_bool_copy(grp, "imageNearest", (interp == SHD_INTERP_CLOSEST));
   }
   else {
index 1f48129..0375db1 100644 (file)
@@ -1009,8 +1009,7 @@ static DRWShadingGroup *drw_shgroup_material_inputs(DRWShadingGroup *grp,
       if (input->ima) {
         GPUTexture **tex_ref = BLI_memblock_alloc(DST.vmempool->images);
 
-        *tex_ref = tex = GPU_texture_from_blender(
-            input->ima, input->iuser, GL_TEXTURE_2D, input->image_isdata);
+        *tex_ref = tex = GPU_texture_from_blender(input->ima, input->iuser, GL_TEXTURE_2D);
 
         GPU_texture_ref(tex);
       }
index cc31362..9d6732f 100644 (file)
@@ -988,7 +988,7 @@ static void DRW_shgroup_empty_image(OBJECT_Shaders *sh_data,
   GPUTexture *tex = NULL;
 
   if (ob->data != NULL) {
-    tex = GPU_texture_from_blender(ob->data, ob->iuser, GL_TEXTURE_2D, false);
+    tex = GPU_texture_from_blender(ob->data, ob->iuser, GL_TEXTURE_2D);
     if (tex) {
       size[0] = GPU_texture_width(tex);
       size[1] = GPU_texture_height(tex);
index 123dc7f..da32744 100644 (file)
@@ -182,7 +182,7 @@ static DRWShadingGroup *create_texture_paint_shading_group(PAINT_TEXTURE_PassLis
 
   if (masking_enabled) {
     const bool masking_inverted = (imapaint->flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) > 0;
-    GPUTexture *stencil = GPU_texture_from_blender(imapaint->stencil, NULL, GL_TEXTURE_2D, false);
+    GPUTexture *stencil = GPU_texture_from_blender(imapaint->stencil, NULL, GL_TEXTURE_2D);
     DRW_shgroup_uniform_texture(grp, "maskingImage", stencil);
     DRW_shgroup_uniform_vec3(grp, "maskingColor", imapaint->stencil_col, 1);
     DRW_shgroup_uniform_bool_copy(grp, "maskingInvertStencil", masking_inverted);
@@ -236,7 +236,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
                                                   NULL;
           int interp = (ma && ma->texpaintslot) ? ma->texpaintslot[ma->paint_active_slot].interp :
                                                   0;
-          GPUTexture *tex = GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false);
+          GPUTexture *tex = GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D);
 
           if (tex) {
             DRWShadingGroup *grp = create_texture_paint_shading_group(
@@ -250,7 +250,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
       }
       else {
         Image *ima = imapaint->canvas;
-        GPUTexture *tex = GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D, false);
+        GPUTexture *tex = GPU_texture_from_blender(ima, NULL, GL_TEXTURE_2D);
 
         if (tex) {
           DRWShadingGroup *grp = create_texture_paint_shading_group(
index 386d056..dbc403d 100644 (file)
@@ -14,12 +14,33 @@ uniform sampler2D image;
 uniform int depthMode;
 uniform bool useAlphaTest;
 
+float linearrgb_to_srgb(float c)
+{
+  if (c < 0.0031308) {
+    return (c < 0.0) ? 0.0 : c * 12.92;
+  }
+  else {
+    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+  }
+}
+
+vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+{
+  /* By convention image textures return scene linear colors, but
+   * overlays still assume srgb. */
+  vec4 color = texture2D(tex, co);
+  color.r = linearrgb_to_srgb(color.r);
+  color.g = linearrgb_to_srgb(color.g);
+  color.b = linearrgb_to_srgb(color.b);
+  return color;
+}
+
 void main()
 {
 #ifdef USE_WIRE
   fragColor = finalColor;
 #else
-  vec4 tex_col = texture(image, texCoord_interp);
+  vec4 tex_col = texture_read_as_srgb(image, texCoord_interp);
   fragColor = finalColor * tex_col;
 
   if (useAlphaTest) {
index c7e1101..5d74213 100644 (file)
@@ -16,6 +16,27 @@ uniform vec3 maskingColor;
 uniform bool maskingInvertStencil;
 #endif
 
+float linearrgb_to_srgb(float c)
+{
+  if (c < 0.0031308) {
+    return (c < 0.0) ? 0.0 : c * 12.92;
+  }
+  else {
+    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+  }
+}
+
+vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+{
+  /* By convention image textures return scene linear colors, but
+   * overlays still assume srgb. */
+  vec4 color = texture2D(tex, co);
+  color.r = linearrgb_to_srgb(color.r);
+  color.g = linearrgb_to_srgb(color.g);
+  color.b = linearrgb_to_srgb(color.b);
+  return color;
+}
+
 void main()
 {
   vec2 uv = uv_interp;
@@ -24,7 +45,7 @@ void main()
     uv = (floor(uv_interp * tex_size) + 0.5) / tex_size;
   }
 
-  vec4 color = texture(image, uv);
+  vec4 color = texture_read_as_srgb(image, uv);
   color.a *= alpha;
 
 #ifdef TEXTURE_PAINT_MASK
index 0340a49..9cc12b4 100644 (file)
@@ -6394,7 +6394,7 @@ static const EnumPropertyItem layer_type_items[] = {
     {0, NULL, 0, NULL, NULL},
 };
 
-static Image *proj_paint_image_create(wmOperator *op, Main *bmain)
+static Image *proj_paint_image_create(wmOperator *op, Main *bmain, bool is_data)
 {
   Image *ima;
   float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
@@ -6417,6 +6417,11 @@ static Image *proj_paint_image_create(wmOperator *op, Main *bmain)
   ima = BKE_image_add_generated(
       bmain, width, height, imagename, alpha ? 32 : 24, use_float, gen_type, color, false);
 
+  if (is_data) {
+    STRNCPY(ima->colorspace_settings.name,
+            IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA));
+  }
+
   return ima;
 }
 
@@ -6487,6 +6492,7 @@ static bool proj_paint_add_slot(bContext *C, wmOperator *op)
   if (ma) {
     Main *bmain = CTX_data_main(C);
     int type = RNA_enum_get(op->ptr, "type");
+    bool is_data = (type > LAYER_BASE_COLOR);
 
     bNode *imanode;
     bNodeTree *ntree = ma->nodetree;
@@ -6501,7 +6507,7 @@ static bool proj_paint_add_slot(bContext *C, wmOperator *op)
     /* try to add an image node */
     imanode = nodeAddStaticNode(C, ntree, SH_NODE_TEX_IMAGE);
 
-    ima = proj_paint_image_create(op, bmain);
+    ima = proj_paint_image_create(op, bmain, is_data);
     imanode->id = &ima->id;
 
     nodeSetActive(ntree, imanode);
@@ -6553,12 +6559,6 @@ static bool proj_paint_add_slot(bContext *C, wmOperator *op)
         }
       }
 
-      if (type > LAYER_BASE_COLOR) {
-        /* This is a "non color data" image */
-        NodeTexImage *tex = imanode->storage;
-        tex->color_space = SHD_COLORSPACE_NONE;
-      }
-
       /* Check if the socket in already connected to something */
       bNodeLink *link = in_sock ? in_sock->link : NULL;
       if (in_sock != NULL && link == NULL) {
index 447fea8..bf6ec96 100644 (file)
@@ -708,6 +708,11 @@ static void node_buts_image_user(uiLayout *layout,
     col = uiLayoutColumn(layout, false);
     uiItemR(col, ptr, "layer", 0, NULL, ICON_NONE);
   }
+
+  uiLayout *split = uiLayoutSplit(layout, 0.5f, true);
+  PointerRNA colorspace_settings_ptr = RNA_pointer_get(imaptr, "colorspace_settings");
+  uiItemL(split, IFACE_("Color Space"), ICON_NONE);
+  uiItemR(split, &colorspace_settings_ptr, "name", 0, "", ICON_NONE);
 }
 
 static void node_shader_buts_mapping(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
@@ -782,7 +787,6 @@ static void node_shader_buts_tex_image(uiLayout *layout, bContext *C, PointerRNA
                NULL,
                UI_TEMPLATE_ID_FILTER_ALL,
                false);
-  uiItemR(layout, ptr, "color_space", 0, "", ICON_NONE);
   uiItemR(layout, ptr, "interpolation", 0, "", ICON_NONE);
   uiItemR(layout, ptr, "projection", 0, "", ICON_NONE);
 
@@ -820,11 +824,10 @@ static void node_shader_buts_tex_environment(uiLayout *layout, bContext *C, Poin
                UI_TEMPLATE_ID_FILTER_ALL,
                false);
 
-  node_buts_image_user(layout, C, &iuserptr, &imaptr, &iuserptr, false);
-
-  uiItemR(layout, ptr, "color_space", 0, "", ICON_NONE);
   uiItemR(layout, ptr, "interpolation", 0, "", ICON_NONE);
   uiItemR(layout, ptr, "projection", 0, "", ICON_NONE);
+
+  node_buts_image_user(layout, C, &iuserptr, &imaptr, &iuserptr, false);
 }
 
 static void node_shader_buts_tex_environment_ex(uiLayout *layout, bContext *C, PointerRNA *ptr)
@@ -875,7 +878,6 @@ static void node_shader_buts_tex_environment_ex(uiLayout *layout, bContext *C, P
     uiTemplateImageInfo(layout, C, ima, iuserptr.data);
   }
 
-  uiItemR(layout, ptr, "color_space", 0, IFACE_("Color Space"), ICON_NONE);
   uiItemR(layout, ptr, "interpolation", 0, IFACE_("Interpolation"), ICON_NONE);
   uiItemR(layout, ptr, "projection", 0, IFACE_("Projection"), ICON_NONE);
 }
index 300fc7c..eb54ff1 100644 (file)
@@ -84,7 +84,7 @@ void GPU_create_gl_tex(unsigned int *bind,
                        int recth,
                        int textarget,
                        bool mipmap,
-                       bool use_hight_bit_depth,
+                       bool use_srgb,
                        struct Image *ima);
 void GPU_create_gl_tex_compressed(unsigned int *bind,
                                   unsigned int *pix,
index 1640209..86c9764 100644 (file)
@@ -145,7 +145,7 @@ typedef enum eGPUMaterialStatus {
 GPUNodeLink *GPU_attribute(CustomDataType type, const char *name);
 GPUNodeLink *GPU_constant(float *num);
 GPUNodeLink *GPU_uniform(float *num);
-GPUNodeLink *GPU_image(struct Image *ima, struct ImageUser *iuser, bool is_data);
+GPUNodeLink *GPU_image(struct Image *ima, struct ImageUser *iuser);
 GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *layer);
 GPUNodeLink *GPU_builtin(eGPUBuiltin builtin);
 
@@ -202,7 +202,6 @@ struct GPUUniformBuffer *GPU_material_create_sss_profile_ubo(void);
 
 void GPU_material_vertex_attrs(GPUMaterial *material, struct GPUVertAttrLayers *attrs);
 
-bool GPU_material_do_color_management(GPUMaterial *mat);
 bool GPU_material_use_domain_surface(GPUMaterial *mat);
 bool GPU_material_use_domain_volume(GPUMaterial *mat);
 
index 3527398..3fb7dfc 100644 (file)
@@ -186,10 +186,7 @@ GPUTexture *GPU_texture_create_from_vertbuf(struct GPUVertBuf *vert);
 GPUTexture *GPU_texture_create_buffer(eGPUTextureFormat data_type, const uint buffer);
 
 GPUTexture *GPU_texture_from_bindcode(int textarget, int bindcode);
-GPUTexture *GPU_texture_from_blender(struct Image *ima,
-                                     struct ImageUser *iuser,
-                                     int textarget,
-                                     bool is_data);
+GPUTexture *GPU_texture_from_blender(struct Image *ima, struct ImageUser *iuser, int textarget);
 GPUTexture *GPU_texture_from_preview(struct PreviewImage *prv, int mipmap);
 
 void GPU_texture_add_mipmap(GPUTexture *tex,
index a2c1a01..6259780 100644 (file)
@@ -1503,7 +1503,6 @@ static void gpu_node_input_link(GPUNode *node, GPUNodeLink *link, const eGPUType
       input->source = GPU_SOURCE_TEX;
       input->ima = link->ima;
       input->iuser = link->iuser;
-      input->image_isdata = link->image_isdata;
       break;
     case GPU_NODE_LINK_ATTR:
       input->source = GPU_SOURCE_ATTR;
@@ -1748,13 +1747,12 @@ GPUNodeLink *GPU_uniform(float *num)
   return link;
 }
 
-GPUNodeLink *GPU_image(Image *ima, ImageUser *iuser, bool is_data)
+GPUNodeLink *GPU_image(Image *ima, ImageUser *iuser)
 {
   GPUNodeLink *link = GPU_node_link_create();
   link->link_type = GPU_NODE_LINK_IMAGE_BLENDER;
   link->ima = ima;
   link->iuser = iuser;
-  link->image_isdata = is_data;
   return link;
 }
 
index 73155c3..d1bb3f2 100644 (file)
@@ -99,7 +99,6 @@ struct GPUNodeLink {
     struct {
       struct Image *ima;
       struct ImageUser *iuser;
-      bool image_isdata;
     };
   };
 };
@@ -137,7 +136,6 @@ typedef struct GPUInput {
       struct GPUTexture **coba; /* input texture, only set at runtime */
       struct Image *ima;        /* image */
       struct ImageUser *iuser;  /* image user */
-      bool image_isdata;        /* image does not contain color data */
       bool bindtex;             /* input is responsible for binding the texture? */
       int texid;                /* number for multitexture, starting from zero */
       eGPUType textype;         /* texture type (2D, 1D Array ...) */
index edc2f21..f1c82dc 100644 (file)
@@ -50,6 +50,7 @@
 
 #include "MEM_guardedalloc.h"
 
+#include "IMB_colormanagement.h"
 #include "IMB_imbuf.h"
 #include "IMB_imbuf_types.h"
 
@@ -207,53 +208,232 @@ static GPUTexture **gpu_get_image_gputexture(Image *ima, GLenum textarget)
   return NULL;
 }
 
-typedef struct VerifyThreadData {
-  ImBuf *ibuf;
-  float *srgb_frect;
-} VerifyThreadData;
+static uint gpu_texture_create_from_ibuf(Image *ima, ImBuf *ibuf, int textarget)
+{
+  uint bindcode = 0;
+  const bool mipmap = GPU_get_mipmap();
+
+#ifdef WITH_DDS
+  if (ibuf->ftype == IMB_FTYPE_DDS) {
+    /* DDS is loaded directly in compressed form. */
+    GPU_create_gl_tex_compressed(
+        &bindcode, ibuf->rect, ibuf->x, ibuf->y, textarget, mipmap, ima, ibuf);
+    return bindcode;
+  }
+#endif
+
+  /* Regular uncompressed texture. */
+  float *rect_float = ibuf->rect_float;
+  uchar *rect = (uchar *)ibuf->rect;
+  bool compress_as_srgb = false;
+
+  if (rect_float == NULL) {
+    /* 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.
+     *
+     * We must also convert to premultiplied for correct texture interpolation
+     * and consistency with float images. */
+    if (!IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) {
+      compress_as_srgb = !IMB_colormanagement_space_is_scene_linear(ibuf->rect_colorspace);
+
+      rect = MEM_mallocN(sizeof(uchar) * 4 * ibuf->x * ibuf->y, __func__);
+      if (rect == NULL) {
+        return bindcode;
+      }
+
+      IMB_colormanagement_imbuf_to_srgb_texture(
+          rect, 0, 0, ibuf->x, ibuf->y, ibuf, compress_as_srgb);
+    }
+  }
+  else if (ibuf->channels != 4) {
+    /* 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. */
+    rect_float = MEM_mallocN(sizeof(float) * 4 * ibuf->x * ibuf->y, __func__);
+    if (rect_float == NULL) {
+      return bindcode;
+    }
 
-static void gpu_verify_high_bit_srgb_buffer_slice(float *srgb_frect,
-                                                  ImBuf *ibuf,
-                                                  const int start_line,
-                                                  const int height)
+    IMB_buffer_float_from_float(rect_float,
+                                ibuf->rect_float,
+                                ibuf->channels,
+                                IB_PROFILE_LINEAR_RGB,
+                                IB_PROFILE_LINEAR_RGB,
+                                false,
+                                ibuf->x,
+                                ibuf->y,
+                                ibuf->x,
+                                ibuf->x);
+  }
+
+  /* Create OpenGL texture. */
+  GPU_create_gl_tex(&bindcode,
+                    (uint *)rect,
+                    rect_float,
+                    ibuf->x,
+                    ibuf->y,
+                    textarget,
+                    mipmap,
+                    compress_as_srgb,
+                    ima);
+
+  /* Free buffers if needed. */
+  if (rect && rect != (uchar *)ibuf->rect) {
+    MEM_freeN(rect);
+  }
+  if (rect_float && rect_float != ibuf->rect_float) {
+    MEM_freeN(rect_float);
+  }
+
+  return bindcode;
+}
+
+static void gpu_texture_update_scaled(
+    uchar *rect, float *rect_float, int full_w, int full_h, int x, int y, int w, int h)
 {
-  size_t offset = ibuf->channels * start_line * ibuf->x;
-  float *current_srgb_frect = srgb_frect + offset;
-  float *current_rect_float = ibuf->rect_float + offset;
-  IMB_buffer_float_from_float(current_srgb_frect,
-                              current_rect_float,
-                              ibuf->channels,
-                              IB_PROFILE_SRGB,
-                              IB_PROFILE_LINEAR_RGB,
-                              true,
-                              ibuf->x,
-                              height,
-                              ibuf->x,
-                              ibuf->x);
-  IMB_buffer_float_unpremultiply(current_srgb_frect, ibuf->x, height);
+  /* Partial update with scaling. */
+  int limit_w = smaller_power_of_2_limit(full_w);
+  int limit_h = smaller_power_of_2_limit(full_h);
+  float xratio = limit_w / (float)full_w;
+  float yratio = limit_h / (float)full_h;
+
+  /* Find sub coordinates in scaled image. Take ceiling because we will be
+   * losing 1 pixel due to rounding errors in x,y. */
+  int sub_x = x * xratio;
+  int sub_y = y * yratio;
+  int sub_w = (int)ceil(xratio * w);
+  int sub_h = (int)ceil(yratio * h);
+
+  /* ...but take back if we are over the limit! */
+  if (sub_w + sub_x > limit_w) {
+    sub_w--;
+  }
+  if (sub_h + sub_y > limit_h) {
+    sub_h--;
+  }
+
+  /* Scale pixels. */
+  ImBuf *ibuf = IMB_allocFromBuffer((uint *)rect, rect_float, w, h);
+  IMB_scaleImBuf(ibuf, sub_w, sub_h);
+
+  if (ibuf->rect_float) {
+    glTexSubImage2D(
+        GL_TEXTURE_2D, 0, sub_x, sub_y, sub_w, sub_h, GL_RGBA, GL_FLOAT, ibuf->rect_float);
+  }
+  else {
+    glTexSubImage2D(
+        GL_TEXTURE_2D, 0, sub_x, sub_y, sub_w, sub_h, GL_RGBA, GL_UNSIGNED_BYTE, ibuf->rect);
+  }
+
+  IMB_freeImBuf(ibuf);
 }
 
-static void verify_thread_do(void *data_v, int start_scanline, int num_scanlines)
+static void gpu_texture_update_unscaled(
+    uchar *rect, float *rect_float, int x, int y, int w, int h, GLint tex_stride, GLint tex_offset)
 {
-  VerifyThreadData *data = (VerifyThreadData *)data_v;
-  gpu_verify_high_bit_srgb_buffer_slice(
-      data->srgb_frect, data->ibuf, start_scanline, num_scanlines);
+  /* Partial update without scaling. Stride and offset are used to copy only a
+   * subset of a possible larger buffer than what we are updating. */
+  GLint row_length;
+  glGetIntegerv(GL_UNPACK_ROW_LENGTH, &row_length);
+  glPixelStorei(GL_UNPACK_ROW_LENGTH, tex_stride);
+
+  if (rect_float == NULL) {
+    glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rect + tex_offset);
+  }
+  else {
+    glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_FLOAT, rect_float + tex_offset);
+  }
+
+  glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
 }
 
-static void gpu_verify_high_bit_srgb_buffer(float *srgb_frect, ImBuf *ibuf)
+static void gpu_texture_update_from_ibuf(ImBuf *ibuf, int x, int y, int w, int h)
 {
-  if (ibuf->y < 64) {
-    gpu_verify_high_bit_srgb_buffer_slice(srgb_frect, ibuf, 0, ibuf->y);
+  /* Partial update of texture for texture painting. This is often much
+   * quicker than fully updating the texture for high resolution images.
+   * Assumes the OpenGL texture is bound to 0. */
+  const bool scaled = is_over_resolution_limit(GL_TEXTURE_2D, ibuf->x, ibuf->y);
+
+  if (scaled) {
+    /* Extra padding to account for bleed from neighboring pixels. */
+    const int padding = 4;
+    const int xmax = min_ii(x + w + padding, ibuf->x);
+    const int ymax = min_ii(y + h + padding, ibuf->y);
+    x = max_ii(x - padding, 0);
+    y = max_ii(y - padding, 0);
+    w = xmax - x;
+    h = ymax - y;
+  }
+
+  /* Get texture data pointers. */
+  float *rect_float = ibuf->rect_float;
+  uchar *rect = (uchar *)ibuf->rect;
+  GLint tex_stride = ibuf->x;
+  GLint tex_offset = ibuf->channels * (y * ibuf->x + x);
+
+  if (rect_float == NULL) {
+    /* 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);
+
+      rect = MEM_mallocN(sizeof(uchar) * 4 * w * h, __func__);
+      if (rect == NULL) {
+        return;
+      }
+
+      tex_stride = w;
+      tex_offset = 0;
+
+      /* Convert to scene linear with sRGB compression, and premultiplied for
+       * correct texture interpolation. */
+      IMB_colormanagement_imbuf_to_srgb_texture(rect, x, y, w, h, ibuf, compress_as_srgb);
+    }
+  }
+  else if (ibuf->channels != 4 || scaled) {
+    /* Float pixels. */
+    rect_float = MEM_mallocN(sizeof(float) * 4 * x * y, __func__);
+    if (rect_float == NULL) {
+      return;
+    }
+
+    tex_stride = w;
+    tex_offset = 0;
+
+    size_t ibuf_offset = (y * ibuf->x + x) * ibuf->channels;
+    IMB_buffer_float_from_float(rect_float,
+                                ibuf->rect_float + ibuf_offset,
+                                ibuf->channels,
+                                IB_PROFILE_LINEAR_RGB,
+                                IB_PROFILE_LINEAR_RGB,
+                                false,
+                                w,
+                                h,
+                                x,
+                                ibuf->x);
+  }
+
+  if (scaled) {
+    /* Slower update where we first have to scale the input pixels. */
+    gpu_texture_update_scaled(rect, rect_float, ibuf->x, ibuf->y, x, y, w, h);
   }
   else {
-    VerifyThreadData data;
-    data.ibuf = ibuf;
-    data.srgb_frect = srgb_frect;
-    IMB_processor_apply_threaded_scanlines(ibuf->y, verify_thread_do, &data);
+    /* Fast update at same resolution. */
+    gpu_texture_update_unscaled(rect, rect_float, x, y, w, h, tex_stride, tex_offset);
+  }
+
+  /* Free buffers if needed. */
+  if (rect && rect != (uchar *)ibuf->rect) {
+    MEM_freeN(rect);
+  }
+  if (rect_float && rect_float != ibuf->rect_float) {
+    MEM_freeN(rect_float);
   }
 }
 
-GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget, bool is_data)
+GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget)
 {
   if (ima == NULL) {
     return NULL;
@@ -286,62 +466,7 @@ GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget
     return *tex;
   }
 
-  /* flag to determine whether deep format is used */
-  bool use_high_bit_depth = false, do_color_management = false;
-
-  if (ibuf->rect_float) {
-    use_high_bit_depth = true;
-
-    /* TODO unneeded when float images are correctly treated as linear always */
-    if (!is_data) {
-      do_color_management = true;
-    }
-  }
-
-  const int rectw = ibuf->x;
-  const int recth = ibuf->y;
-  uint *rect = ibuf->rect;
-  float *frect = NULL;
-  float *srgb_frect = NULL;
-
-  if (use_high_bit_depth) {
-    if (do_color_management) {
-      frect = srgb_frect = MEM_mallocN(ibuf->x * ibuf->y * sizeof(*srgb_frect) * 4,
-                                       "floar_buf_col_cor");
-      gpu_verify_high_bit_srgb_buffer(srgb_frect, ibuf);
-    }
-    else {
-      frect = ibuf->rect_float;
-    }
-  }
-
-  const bool mipmap = GPU_get_mipmap();
-
-#ifdef WITH_DDS
-  if (ibuf->ftype == IMB_FTYPE_DDS) {
-    GPU_create_gl_tex_compressed(&bindcode, rect, rectw, recth, textarget, mipmap, ima, ibuf);
-  }
-  else
-#endif
-  {
-    GPU_create_gl_tex(
-        &bindcode, rect, frect, rectw, recth, textarget, mipmap, use_high_bit_depth, ima);
-  }
-
-  /* mark as non-color data texture */
-  if (bindcode) {
-    if (is_data) {
-      ima->gpuflag |= IMA_GPU_IS_DATA;
-    }
-    else {
-      ima->gpuflag &= ~IMA_GPU_IS_DATA;
-    }
-  }
-
-  /* clean up */
-  if (srgb_frect) {
-    MEM_freeN(srgb_frect);
-  }
+  bindcode = gpu_texture_create_from_ibuf(ima, ibuf, textarget);
 
   BKE_image_release_ibuf(ima, ibuf, NULL);
 
@@ -349,15 +474,14 @@ GPUTexture *GPU_texture_from_blender(Image *ima, ImageUser *iuser, int textarget
   return *tex;
 }
 
-static void **gpu_gen_cube_map(
-    uint *rect, float *frect, int rectw, int recth, bool use_high_bit_depth)
+static void **gpu_gen_cube_map(uint *rect, float *frect, int rectw, int recth)
 {
-  size_t block_size = use_high_bit_depth ? sizeof(float[4]) : sizeof(uchar[4]);
+  size_t block_size = frect ? sizeof(float[4]) : sizeof(uchar[4]);
   void **sides = NULL;
   int h = recth / 2;
   int w = rectw / 3;
 
-  if ((use_high_bit_depth && frect == NULL) || (!use_high_bit_depth && rect == NULL) || w != h) {
+  if (w != h) {
     return sides;
   }
 
@@ -376,7 +500,7 @@ static void **gpu_gen_cube_map(
    * | NegZ | PosZ | PosY |
    * |______|______|______|
    */
-  if (use_high_bit_depth) {
+  if (frect) {
     float(*frectb)[4] = (float(*)[4])frect;
     float(**fsides)[4] = (float(**)[4])sides;
 
@@ -430,7 +554,7 @@ void GPU_create_gl_tex(uint *bind,
                        int recth,
                        int textarget,
                        bool mipmap,
-                       bool use_high_bit_depth,
+                       bool use_srgb,
                        Image *ima)
 {
   ImBuf *ibuf = NULL;
@@ -441,7 +565,7 @@ void GPU_create_gl_tex(uint *bind,
     rectw = smaller_power_of_2_limit(rectw);
     recth = smaller_power_of_2_limit(recth);
 
-    if (use_high_bit_depth) {
+    if (frect) {
       ibuf = IMB_allocFromBuffer(NULL, frect, tpx, tpy);
       IMB_scaleImBuf(ibuf, rectw, recth);
 
@@ -459,12 +583,15 @@ void GPU_create_gl_tex(uint *bind,
   glGenTextures(1, (GLuint *)bind);
   glBindTexture(textarget, *bind);
 
+  GLenum internal_format = (frect) ? GL_RGBA16F : (use_srgb) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
+
   if (textarget == GL_TEXTURE_2D) {
-    if (use_high_bit_depth) {
-      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, rectw, recth, 0, GL_RGBA, GL_FLOAT, frect);
+    if (frect) {
+      glTexImage2D(GL_TEXTURE_2D, 0, internal_format, rectw, recth, 0, GL_RGBA, GL_FLOAT, frect);
     }
     else {
-      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, rect);
+      glTexImage2D(
+          GL_TEXTURE_2D, 0, internal_format, rectw, recth, 0, GL_RGBA, GL_UNSIGNED_BYTE, rect);
     }
 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gpu_get_mipmap_filter(1));
@@ -484,15 +611,14 @@ void GPU_create_gl_tex(uint *bind,
     int w = rectw / 3, h = recth / 2;
 
     if (h == w && is_power_of_2_i(h) && !is_over_resolution_limit(textarget, h, w)) {
-      void **cube_map = gpu_gen_cube_map(rect, frect, rectw, recth, use_high_bit_depth);
-      GLenum informat = use_high_bit_depth ? GL_RGBA16F : GL_RGBA8;
-      GLenum type = use_high_bit_depth ? GL_FLOAT : GL_UNSIGNED_BYTE;
+      void **cube_map = gpu_gen_cube_map(rect, frect, rectw, recth);
+      GLenum type = frect ? GL_FLOAT : GL_UNSIGNED_BYTE;
 
       if (cube_map) {
         for (int i = 0; i < 6; i++) {
           glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
                        0,
-                       informat,
+                       internal_format,
                        w,
                        h,
                        0,
@@ -617,14 +743,14 @@ void GPU_create_gl_tex_compressed(
 #ifndef WITH_DDS
   (void)ibuf;
   /* Fall back to uncompressed if DDS isn't enabled */
-  GPU_create_gl_tex(bind, pix, NULL, x, y, textarget, mipmap, 0, ima);
+  GPU_create_gl_tex(bind, pix, NULL, x, y, textarget, mipmap, true, ima);
 #else
   glGenTextures(1, (GLuint *)bind);
   glBindTexture(textarget, *bind);
 
   if (textarget == GL_TEXTURE_2D && GPU_upload_dxt_texture(ibuf) == 0) {
     glDeleteTextures(1, (GLuint *)bind);
-    GPU_create_gl_tex(bind, pix, NULL, x, y, textarget, mipmap, 0, ima);
+    GPU_create_gl_tex(bind, pix, NULL, x, y, textarget, mipmap, true, ima);
   }
 
   glBindTexture(textarget, 0);
@@ -680,146 +806,20 @@ void GPU_paint_set_mipmap(Main *bmain, bool mipmap)
   }
 }
 
-/* check if image has been downscaled and do scaled partial update */
-static bool gpu_check_scaled_image(
-    ImBuf *ibuf, Image *ima, float *frect, int x, int y, int w, int h)
-{
-  if (is_over_resolution_limit(GL_TEXTURE_2D, ibuf->x, ibuf->y)) {
-    int x_limit = smaller_power_of_2_limit(ibuf->x);
-    int y_limit = smaller_power_of_2_limit(ibuf->y);
-
-    float xratio = x_limit / (float)ibuf->x;
-    float yratio = y_limit / (float)ibuf->y;
-
-    /* find new width, height and x,y gpu texture coordinates */
-
-    /* take ceiling because we will be losing 1 pixel due to rounding errors in x,y... */
-    int rectw = (int)ceil(xratio * w);
-    int recth = (int)ceil(yratio * h);
-
-    x *= xratio;
-    y *= yratio;
-
-    /* ...but take back if we are over the limit! */
-    if (rectw + x > x_limit) {
-      rectw--;
-    }
-    if (recth + y > y_limit) {
-      recth--;
-    }
-
-    GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
-
-    /* float rectangles are already continuous in memory so we can use IMB_scaleImBuf */
-    if (frect) {
-      ImBuf *ibuf_scale = IMB_allocFromBuffer(NULL, frect, w, h);
-      IMB_scaleImBuf(ibuf_scale, rectw, recth);
-
-      glTexSubImage2D(
-          GL_TEXTURE_2D, 0, x, y, rectw, recth, GL_RGBA, GL_FLOAT, ibuf_scale->rect_float);
-
-      IMB_freeImBuf(ibuf_scale);
-    }
-    /* byte images are not continuous in memory so do manual interpolation */
-    else {
-      uchar *scalerect = MEM_mallocN(rectw * recth * sizeof(*scalerect) * 4, "scalerect");
-      uint *p = (uint *)scalerect;
-      int i, j;
-      float inv_xratio = 1.0f / xratio;
-      float inv_yratio = 1.0f / yratio;
-      for (i = 0; i < rectw; i++) {
-        float u = (x + i) * inv_xratio;
-        for (j = 0; j < recth; j++) {
-          float v = (y + j) * inv_yratio;
-          bilinear_interpolation_color_wrap(ibuf, (uchar *)(p + i + j * (rectw)), NULL, u, v);
-        }
-      }
-
-      glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, rectw, recth, GL_RGBA, GL_UNSIGNED_BYTE, scalerect);
-
-      MEM_freeN(scalerect);
-    }
-
-    if (GPU_get_mipmap()) {
-      glGenerateMipmap(GL_TEXTURE_2D);
-    }
-    else {
-      ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
-    }
-
-    GPU_texture_unbind(ima->gputexture[TEXTARGET_TEXTURE_2D]);
-
-    return true;
-  }
-
-  return false;
-}
-
 void GPU_paint_update_image(Image *ima, ImageUser *iuser, int x, int y, int w, int h)
 {
   ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
 
   if ((ima->gputexture[TEXTARGET_TEXTURE_2D] == NULL) || (ibuf == NULL) || (w == 0) || (h == 0)) {
-    /* these cases require full reload still */
+    /* Full reload of texture. */
     GPU_free_image(ima);
   }
   else {
-    /* for the special case, we can do a partial update
-     * which is much quicker for painting */
-    GLint row_length, skip_pixels, skip_rows;
-
-    /* if color correction is needed, we must update the part that needs updating. */
-    if (ibuf->rect_float) {
-      float *buffer = MEM_mallocN(w * h * sizeof(float) * 4, "temp_texpaint_float_buf");
-      bool is_data = (ima->gpuflag & IMA_GPU_IS_DATA) != 0;
-      IMB_partial_rect_from_float(ibuf, buffer, x, y, w, h, is_data);
-
-      if (gpu_check_scaled_image(ibuf, ima, buffer, x, y, w, h)) {
-        MEM_freeN(buffer);
-        BKE_image_release_ibuf(ima, ibuf, NULL);
-        return;
-      }
-
-      GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
-      glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_FLOAT, buffer);
-
-      MEM_freeN(buffer);
-
-      if (GPU_get_mipmap()) {
-        glGenerateMipmap(GL_TEXTURE_2D);
-      }
-      else {
-        ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
-      }
-
-      GPU_texture_unbind(ima->gputexture[TEXTARGET_TEXTURE_2D]);
-
-      BKE_image_release_ibuf(ima, ibuf, NULL);
-      return;
-    }
-
-    if (gpu_check_scaled_image(ibuf, ima, NULL, x, y, w, h)) {
-      BKE_image_release_ibuf(ima, ibuf, NULL);
-      return;
-    }
-
+    /* Partial update of texture. */
     GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
 
-    glGetIntegerv(GL_UNPACK_ROW_LENGTH, &row_length);
-    glGetIntegerv(GL_UNPACK_SKIP_PIXELS, &skip_pixels);
-    glGetIntegerv(GL_UNPACK_SKIP_ROWS, &skip_rows);
-
-    glPixelStorei(GL_UNPACK_ROW_LENGTH, ibuf->x);
-    glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
-    glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
-
-    glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ibuf->rect);
-
-    glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
-    glPixelStorei(GL_UNPACK_SKIP_PIXELS, skip_pixels);
-    glPixelStorei(GL_UNPACK_SKIP_ROWS, skip_rows);
+    gpu_texture_update_from_ibuf(ibuf, x, y, w, h);
 
-    /* see comment above as to why we are using gpu mipmap generation here */
     if (GPU_get_mipmap()) {
       glGenerateMipmap(GL_TEXTURE_2D);
     }
@@ -1242,7 +1242,7 @@ static void gpu_free_image_immediate(Image *ima)
     }
   }
 
-  ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE | IMA_GPU_IS_DATA);
+  ima->gpuflag &= ~(IMA_GPU_MIPMAP_COMPLETE);
 }
 
 void GPU_free_image(Image *ima)
index 1a668a4..6aa13b0 100644 (file)
@@ -597,15 +597,6 @@ eGPUMaterialStatus GPU_material_status(GPUMaterial *mat)
 
 /* Code generation */
 
-bool GPU_material_do_color_management(GPUMaterial *mat)
-{
-  if (!BKE_scene_check_color_management_enabled(mat->scene)) {
-    return false;
-  }
-
-  return true;
-}
-
 bool GPU_material_use_domain_surface(GPUMaterial *mat)
 {
   return (mat->domain & GPU_DOMAIN_SURFACE);
index ad4831e..58d0dd5 100644 (file)
@@ -1081,7 +1081,7 @@ GPUTexture *GPU_texture_from_preview(PreviewImage *prv, int mipmap)
   /* this binds a texture, so that's why we restore it to 0 */
   if (bindcode == 0) {
     GPU_create_gl_tex(
-        &bindcode, prv->rect[0], NULL, prv->w[0], prv->h[0], GL_TEXTURE_2D, mipmap, 0, NULL);
+        &bindcode, prv->rect[0], NULL, prv->w[0], prv->h[0], GL_TEXTURE_2D, mipmap, false, NULL);
   }
   if (tex) {
     tex->bindcode = bindcode;
index 2f80bc9..5596940 100644 (file)
@@ -117,38 +117,6 @@ void hsv_to_rgb(vec4 hsv, out vec4 outcol)
   outcol = vec4(rgb, hsv.w);
 }
 
-float srgb_to_linearrgb(float c)
-{
-  if (c < 0.04045)
-    return (c < 0.0) ? 0.0 : c * (1.0 / 12.92);
-  else
-    return pow((c + 0.055) * (1.0 / 1.055), 2.4);
-}
-
-float linearrgb_to_srgb(float c)
-{
-  if (c < 0.0031308)
-    return (c < 0.0) ? 0.0 : c * 12.92;
-  else
-    return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
-}
-
-void srgb_to_linearrgb(vec4 col_from, out vec4 col_to)
-{
-  col_to.r = srgb_to_linearrgb(col_from.r);
-  col_to.g = srgb_to_linearrgb(col_from.g);
-  col_to.b = srgb_to_linearrgb(col_from.b);
-  col_to.a = col_from.a;
-}
-
-void linearrgb_to_srgb(vec4 col_from, out vec4 col_to)
-{
-  col_to.r = linearrgb_to_srgb(col_from.r);
-  col_to.g = linearrgb_to_srgb(col_from.g);
-  col_to.b = linearrgb_to_srgb(col_from.b);
-  col_to.a = col_from.a;
-}
-
 void color_to_normal_new_shading(vec3 color, out vec3 normal)
 {
   normal = vec3(2.0) * color - vec3(1.0);
@@ -2330,6 +2298,21 @@ void node_tex_environment_empty(vec3 co, out vec4 color)
 /* 16bits floats limits. Higher/Lower values produce +/-inf. */
 #define safe_color(a) (clamp(a, -65520.0, 65520.0))
 
+void tex_color_alpha_clear(vec4 color, out vec4 result)
+{
+  result = vec4(color.rgb, 1.0);
+}
+
+void tex_color_alpha_unpremultiply(vec4 color, out vec4 result)
+{
+  if (color.a == 0.0 || color.a == 1.0) {
+    result = vec4(color.rgb, 1.0);
+  }
+  else {
+    result = vec4(color.rgb / color.a, 1.0);
+  }
+}
+
 void node_tex_image_linear(vec3 co, sampler2D ima, out vec4 color, out float alpha)
 {
   color = safe_color(texture(ima, co.xy));
index 620f898..e683d38 100644 (file)
@@ -58,6 +58,10 @@ void IMB_colormanagement_assign_rect_colorspace(struct ImBuf *ibuf, const char *
 const char *IMB_colormanagement_get_float_colorspace(struct ImBuf *ibuf);
 const char *IMB_colormanagement_get_rect_colorspace(struct ImBuf *ibuf);
 
+bool IMB_colormanagement_space_is_data(struct ColorSpace *colorspace);
+bool IMB_colormanagement_space_is_scene_linear(struct ColorSpace *colorspace);
+bool IMB_colormanagement_space_is_srgb(struct ColorSpace *colorspace);
+
 BLI_INLINE float IMB_colormanagement_get_luminance(const float rgb[3]);
 BLI_INLINE unsigned char IMB_colormanagement_get_luminance_byte(const unsigned char[3]);
 BLI_INLINE void IMB_colormangement_xyz_to_rgb(float rgb[3], const float xyz[3]);
@@ -124,6 +128,14 @@ void IMB_colormanagement_colorspace_to_scene_linear(float *buffer,
                                                     struct ColorSpace *colorspace,
                                                     bool predivide);
 
+void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *rect,
+                                               const int x,
+                                               const int y,
+                                               const int width,
+                                               const int height,
+                                               const struct ImBuf *ibuf,
+                                               const bool compress_as_srgb);
+
 void IMB_colormanagement_scene_linear_to_color_picking_v3(float pixel[3]);
 void IMB_colormanagement_color_picking_to_scene_linear_v3(float pixel[3]);
 
@@ -340,6 +352,7 @@ enum {
   COLOR_ROLE_DEFAULT_SEQUENCER,
   COLOR_ROLE_DEFAULT_BYTE,
   COLOR_ROLE_DEFAULT_FLOAT,
+  COLOR_ROLE_DATA,
 };
 
 #include "intern/colormanagement_inline.c"
index 2566016..a83f2d6 100644 (file)
@@ -48,6 +48,13 @@ typedef struct ColorSpace {
 
   bool is_invertible;
   bool is_data;
+
+  /* Additional info computed only when needed since it's not cheap. */
+  struct {
+    bool cached;
+    bool is_srgb;
+    bool is_scene_linear;
+  } info;
 } ColorSpace;
 
 typedef struct ColorManagedDisplay {
index f31d4ed..b460d26 100644 (file)
@@ -65,6 +65,7 @@
 #define DISPLAY_BUFFER_CHANNELS 4
 
 /* ** list of all supported color spaces, displays and views */
+static char global_role_data[MAX_COLORSPACE_NAME];
 static char global_role_scene_linear[MAX_COLORSPACE_NAME];
 static char global_role_color_picking[MAX_COLORSPACE_NAME];
 static char global_role_texture_painting[MAX_COLORSPACE_NAME];
@@ -488,6 +489,7 @@ static void colormanage_load_config(OCIO_ConstConfigRcPtr *config)
   const char *name;
 
   /* get roles */
+  colormanage_role_color_space_name_get(config, global_role_data, OCIO_ROLE_DATA, NULL);
   colormanage_role_color_space_name_get(
       config, global_role_scene_linear, OCIO_ROLE_SCENE_LINEAR, NULL);
   colormanage_role_color_space_name_get(
@@ -1260,6 +1262,8 @@ void IMB_colormanagement_validate_settings(const ColorManagedDisplaySettings *di
 const char *IMB_colormanagement_role_colorspace_name_get(int role)
 {
   switch (role) {
+    case COLOR_ROLE_DATA:
+      return global_role_data;
     case COLOR_ROLE_SCENE_LINEAR:
       return global_role_scene_linear;
     case COLOR_ROLE_COLOR_PICKING:
@@ -1341,6 +1345,42 @@ const char *IMB_colormanagement_get_rect_colorspace(ImBuf *ibuf)
   }
 }
 
+bool IMB_colormanagement_space_is_data(ColorSpace *colorspace)
+{
+  return (colorspace && colorspace->is_data);
+}
+
+static void colormanage_ensure_srgb_scene_linear_info(ColorSpace *colorspace)
+{
+  if (!colorspace->info.cached) {
+    OCIO_ConstConfigRcPtr *config = OCIO_getCurrentConfig();
+    OCIO_ConstColorSpaceRcPtr *ocio_colorspace = OCIO_configGetColorSpace(config,
+                                                                          colorspace->name);
+
+    bool is_scene_linear, is_srgb;
+    OCIO_colorSpaceIsBuiltin(config, ocio_colorspace, &is_scene_linear, &is_srgb);
+
+    OCIO_colorSpaceRelease(ocio_colorspace);
+    OCIO_configRelease(config);
+
+    colorspace->info.is_scene_linear = is_scene_linear;
+    colorspace->info.is_srgb = is_srgb;
+    colorspace->info.cached = true;
+  }
+}
+
+bool IMB_colormanagement_space_is_scene_linear(ColorSpace *colorspace)
+{
+  colormanage_ensure_srgb_scene_linear_info(colorspace);
+  return (colorspace && colorspace->info.is_scene_linear);
+}
+
+bool IMB_colormanagement_space_is_srgb(ColorSpace *colorspace)
+{
+  colormanage_ensure_srgb_scene_linear_info(colorspace);
+  return (colorspace && colorspace->info.is_srgb);
+}
+
 /*********************** Threaded display buffer transform routines *************************/
 
 typedef struct DisplayBufferThread {
@@ -2111,6 +2151,57 @@ void IMB_colormanagement_colorspace_to_scene_linear(float *buffer,
   }
 }
 
+void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
+                                               const int offset_x,
+                                               const int offset_y,
+                                               const int width,
+                                               const int height,
+                                               const struct ImBuf *ibuf,
+                                               const bool compress_as_srgb)
+{
+  /* 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. */
+  BLI_assert(ibuf->rect && ibuf->rect_float == NULL);
+
+  OCIO_ConstProcessorRcPtr *processor = NULL;
+  if (compress_as_srgb && ibuf->rect_colorspace &&
+      !IMB_colormanagement_space_is_srgb(ibuf->rect_colorspace)) {
+    processor = colorspace_to_scene_linear_processor(ibuf->rect_colorspace);
+  }
+
+  /* TODO(brecht): make this multithreaded, or at least process in batches. */
+  const unsigned char *in_buffer = (unsigned char *)ibuf->rect;
+
+  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;
+    unsigned char *out = out_buffer + out_offset * 4;
+
+    if (processor) {
+      /* 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_processorApplyRGB(processor, pixel);
+        linearrgb_to_srgb_v3_v3(pixel, pixel);
+        mul_v3_fl(pixel, pixel[3]);
+        rgba_float_to_uchar(out, pixel);
+      }
+    }
+    else {
+      /* Premultiply only. */
+      for (int x = 0; x < width; x++, in += 4, out += 4) {
+        out[0] = (in[0] * in[3]) >> 8;
+        out[1] = (in[1] * in[3]) >> 8;
+        out[2] = (in[2] * in[3]) >> 8;
+        out[3] = in[3];
+      }
+    }
+  }
+}
+
 /* Conversion between color picking role. Typically we would expect such a
  * requirements:
  * - It is approximately perceptually linear, so that the HSV numbers and
index fec5f96..0aceeda 100644 (file)
@@ -191,8 +191,6 @@ enum {
   IMA_GPU_REFRESH = (1 << 0),
   /** All mipmap levels in OpenGL texture set? */
   IMA_GPU_MIPMAP_COMPLETE = (1 << 1),
-  /** OpenGL image texture bound as non-color data. */
-  IMA_GPU_IS_DATA = (1 << 2),
 };
 
 /* ima->type and ima->source moved to BKE_image.h, for API */
index 226e1d2..3890fc6 100644 (file)
@@ -832,7 +832,7 @@ typedef struct NodeTexSky {
 typedef struct NodeTexImage {
   NodeTexBase base;
   ImageUser iuser;
-  int color_space;
+  int color_space DNA_DEPRECATED;
   int projection;
   float projection_blend;
   int interpolation;
@@ -853,7 +853,7 @@ typedef struct NodeTexBrick {
 typedef struct NodeTexEnvironment {
   NodeTexBase base;
   ImageUser iuser;
-  int color_space;
+  int color_space DNA_DEPRECATED;
   int projection;
   int interpolation;
   char _pad[4];
@@ -1121,10 +1121,6 @@ typedef struct NodeCryptomatte {
 #define SHD_SKY_OLD 0
 #define SHD_SKY_NEW 1
 
-/* image/environment texture */
-#define SHD_COLORSPACE_NONE 0
-#define SHD_COLORSPACE_COLOR 1
-
 /* environment texture */
 #define SHD_PROJ_EQUIRECTANGULAR 0
 #define SHD_PROJ_MIRROR_BALL 1
index 40ee069..8ba1f54 100644 (file)
@@ -525,6 +525,22 @@ static char *rna_ColorManagedViewSettings_path(PointerRNA *UNUSED(ptr))
   return BLI_strdup("view_settings");
 }
 
+static bool rna_ColorManagedColorspaceSettings_is_data_get(struct PointerRNA *ptr)
+{
+  ColorManagedColorspaceSettings *colorspace = (ColorManagedColorspaceSettings *)ptr->data;
+  const char *data_name = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA);
+  return STREQ(colorspace->name, data_name);
+}
+
+static void rna_ColorManagedColorspaceSettings_is_data_set(struct PointerRNA *ptr, bool value)
+{
+  ColorManagedColorspaceSettings *colorspace = (ColorManagedColorspaceSettings *)ptr->data;
+  if (value) {
+    const char *data_name = IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA);
+    STRNCPY(colorspace->name, data_name);
+  }
+}
+
 static int rna_ColorManagedColorspaceSettings_colorspace_get(struct PointerRNA *ptr)
 {
   ColorManagedColorspaceSettings *colorspace = (ColorManagedColorspaceSettings *)ptr->data;
@@ -1227,6 +1243,7 @@ static void rna_def_colormanage(BlenderRNA *brna)
 
   prop = RNA_def_property(srna, "name", PROP_ENUM, PROP_NONE);
   RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT);
+  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_enum_items(prop, color_space_items);
   RNA_def_property_enum_funcs(prop,
                               "rna_ColorManagedColorspaceSettings_colorspace_get",
@@ -1235,6 +1252,17 @@ static void rna_def_colormanage(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Input Color Space", "Color space of the image or movie on disk");
   RNA_def_property_update(prop, NC_WINDOW, "rna_ColorManagedColorspaceSettings_reload_update");
 
+  prop = RNA_def_property(srna, "is_data", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+  RNA_def_property_boolean_funcs(prop,
+                                 "rna_ColorManagedColorspaceSettings_is_data_get",
+                                 "rna_ColorManagedColorspaceSettings_is_data_set");
+  RNA_def_property_ui_text(
+      prop,
+      "Is Data",
+      "Treat image as non-color data without color management, like normal or displacement maps");
+  RNA_def_property_update(prop, NC_WINDOW, "rna_ColorManagement_update");
+
   //
   srna = RNA_def_struct(brna, "ColorManagedSequencerColorspaceSettings", NULL);
   RNA_def_struct_path_func(srna, "rna_ColorManagedSequencerColorspaceSettings_path");
index ca75f86..553dbee 100644 (file)
@@ -211,69 +211,30 @@ static void rna_Image_scale(Image *image, ReportList *reports, int width, int he
   }
 }
 
-static int rna_Image_gl_load(Image *image, ReportList *reports, int frame, int filter, int mag)
+static int rna_Image_gl_load(Image *image, ReportList *reports, int frame)
 {
-  GPUTexture *tex = image->gputexture[TEXTARGET_TEXTURE_2D];
-  int error = GL_NO_ERROR;
-
-  if (tex)
-    return error;
-
   ImageUser iuser = {NULL};
   iuser.framenr = frame;
   iuser.ok = true;
 
-  void *lock;
-  ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, &lock);
-
-  /* clean glError buffer */
-  while (glGetError() != GL_NO_ERROR) {
-  }
+  GPUTexture *tex = GPU_texture_from_blender(image, &iuser, GL_TEXTURE_2D);
 
-  if (ibuf == NULL || ibuf->rect == NULL) {
-    BKE_reportf(reports, RPT_ERROR, "Image '%s' does not have any image data", image->id.name + 2);
-    BKE_image_release_ibuf(image, ibuf, lock);
+  if (tex == NULL) {
+    BKE_reportf(reports, RPT_ERROR, "Failed to load image texture '%s'", image->id.name + 2);
     return (int)GL_INVALID_OPERATION;
   }
 
-  unsigned int bindcode = 0;
-  GPU_create_gl_tex(&bindcode,
-                    ibuf->rect,
-                    ibuf->rect_float,
-                    ibuf->x,
-                    ibuf->y,
-                    GL_TEXTURE_2D,
-                    (filter != GL_NEAREST && filter != GL_LINEAR),
-                    false,
-                    image);
-
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLint)filter);
-  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLint)mag);
-
-  /* TODO(merwin): validate input (dimensions, filter, mag) before calling OpenGL
-   *               instead of trusting input & testing for error after */
-  error = glGetError();
-
-  if (error) {
-    glDeleteTextures(1, (GLuint *)&bindcode);
-  }
-  else {
-    image->gputexture[TEXTARGET_TEXTURE_2D] = GPU_texture_from_bindcode(GL_TEXTURE_2D, bindcode);
-  }
-
-  BKE_image_release_ibuf(image, ibuf, lock);
-
-  return error;
+  return GL_NO_ERROR;
 }
 
-static int rna_Image_gl_touch(Image *image, ReportList *reports, int frame, int filter, int mag)
+static int rna_Image_gl_touch(Image *image, ReportList *reports, int frame)
 {
   int error = GL_NO_ERROR;
 
   BKE_image_tag_time(image);
 
   if (image->gputexture[TEXTARGET_TEXTURE_2D] == NULL)
-    error = rna_Image_gl_load(image, reports, frame, filter, mag);
+    error = rna_Image_gl_load(image, reports, frame);
 
   return error;
 }
@@ -367,52 +328,20 @@ void RNA_api_image(StructRNA *srna)
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   RNA_def_int(
       func, "frame", 0, 0, INT_MAX, "Frame", "Frame of image sequence or movie", 0, INT_MAX);
-  RNA_def_int(func,
-              "filter",
-              GL_LINEAR_MIPMAP_NEAREST,
-              -INT_MAX,
-              INT_MAX,
-              "Filter",
-              "The texture minifying function to use if the image wasn't loaded",
-              -INT_MAX,
-              INT_MAX);
-  RNA_def_int(func,
-              "mag",
-              GL_LINEAR,
-              -INT_MAX,
-              INT_MAX,
-              "Magnification",
-              "The texture magnification function to use if the image wasn't loaded",
-              -INT_MAX,
-              INT_MAX);
   /* return value */
   parm = RNA_def_int(
       func, "error", 0, -INT_MAX, INT_MAX, "Error", "OpenGL error value", -INT_MAX, INT_MAX);
   RNA_def_function_return(func, parm);
 
   func = RNA_def_function(srna, "gl_load", "rna_Image_gl_load");
-  RNA_def_function_ui_description(func, "Load the image into OpenGL graphics memory");
+  RNA_def_function_ui_description(
+      func,
+      "Load the image into an OpenGL texture. On success, image.bindcode will contain the "
+      "OpenGL texture bindcode. Colors read from the texture will be in scene linear color space "
+      "and have premultiplied alpha.");
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   RNA_def_int(
       func, "frame", 0, 0, INT_MAX, "Frame", "Frame of image sequence or movie", 0, INT_MAX);
-  RNA_def_int(func,
-              "filter",
-              GL_LINEAR_MIPMAP_NEAREST,
-              -INT_MAX,
-              INT_MAX,
-              "Filter",
-              "The texture minifying function",
-              -INT_MAX,
-              INT_MAX);
-  RNA_def_int(func,
-              "mag",
-              GL_LINEAR,
-              -INT_MAX,
-              INT_MAX,
-              "Magnification",
-              "The texture magnification function",
-              -INT_MAX,
-              INT_MAX);
   /* return value */
   parm = RNA_def_int(
       func, "error", 0, -INT_MAX, INT_MAX, "Error", "OpenGL error value", -INT_MAX, INT_MAX);
index e4270f3..4906f8a 100644 (file)
@@ -3987,21 +3987,6 @@ static void def_sh_tex_sky(StructRNA *srna)
   RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
 }
 
-static const EnumPropertyItem sh_tex_prop_color_space_items[] = {
-    {SHD_COLORSPACE_COLOR,
-     "COLOR",
-     0,
-     "Color",
-     "Image contains color data, and will be converted to linear color for rendering"},
-    {SHD_COLORSPACE_NONE,
-     "NONE",
-     0,
-     "Non-Color Data",
-     "Image contains non-color data, for example a displacement or normal map, "
-     "and will not be converted"},
-    {0, NULL, 0, NULL, NULL},
-};
-
 static const EnumPropertyItem sh_tex_prop_interpolation_items[] = {
     {SHD_INTERP_LINEAR, "Linear", 0, "Linear", "Linear interpolation"},
     {SHD_INTERP_CLOSEST, "Closest", 0, "Closest", "No interpolation (sample closest texel)"},
@@ -4038,12 +4023,6 @@ static void def_sh_tex_environment(StructRNA *srna)
   RNA_def_struct_sdna_from(srna, "NodeTexEnvironment", "storage");
   def_sh_tex(srna);
 
-  prop = RNA_def_property(srna, "color_space", PROP_ENUM, PROP_NONE);
-  RNA_def_property_enum_items(prop, sh_tex_prop_color_space_items);
-  RNA_def_property_enum_default(prop, SHD_COLORSPACE_COLOR);
-  RNA_def_property_ui_text(prop, "Color Space", "Image file color space");
-  RNA_def_property_update(prop, 0, "rna_Node_update");
-
   prop = RNA_def_property(srna, "projection", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, prop_projection_items);
   RNA_def_property_ui_text(prop, "Projection", "Projection of the input image");
@@ -4122,12 +4101,6 @@ static void def_sh_tex_image(StructRNA *srna)
   RNA_def_struct_sdna_from(srna, "NodeTexImage", "storage");
   def_sh_tex(srna);
 
-  prop = RNA_def_property(srna, "color_space", PROP_ENUM, PROP_NONE);
-  RNA_def_property_enum_items(prop, sh_tex_prop_color_space_items);
-  RNA_def_property_enum_default(prop, SHD_COLORSPACE_COLOR);
-  RNA_def_property_ui_text(prop, "Color Space", "Image file color space");
-  RNA_def_property_update(prop, 0, "rna_Node_update");
-
   prop = RNA_def_property(srna, "projection", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, prop_projection_items);
   RNA_def_property_ui_text(
index 916cb81..165ada0 100644 (file)
@@ -67,6 +67,7 @@
 #include "RE_shader_ext.h"
 
 #include "GPU_material.h"
+#include "GPU_texture.h"
 #include "GPU_uniformbuffer.h"
 
 bool sh_node_poll_default(struct bNodeType *ntype, struct bNodeTree *ntree);
index c3b8bc8..615f55e 100644 (file)
@@ -46,7 +46,6 @@ static void node_shader_init_tex_environment(bNodeTree *UNUSED(ntree), bNode *no
   NodeTexEnvironment *tex = MEM_callocN(sizeof(NodeTexEnvironment), "NodeTexEnvironment");
   BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
   BKE_texture_colormapping_default(&tex->base.color_mapping);
-  tex->color_space = SHD_COLORSPACE_COLOR;
   tex->projection = SHD_PROJ_EQUIRECTANGULAR;
   BKE_imageuser_default(&tex->iuser);
 
@@ -68,7 +67,6 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
   NodeTexImage *tex_original = node_original->storage;
   ImageUser *iuser = &tex_original->iuser;
 
-  int isdata = tex->color_space == SHD_COLORSPACE_NONE;
   GPUNodeLink *outalpha;
 
   if (!ima) {
@@ -89,7 +87,7 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
              "node_tex_environment_equirectangular",
              in[0].link,
              GPU_constant(&clamp_size),
-             GPU_image(ima, iuser, isdata),
+             GPU_image(ima, iuser),
              &in[0].link);
   }
   else {
@@ -104,7 +102,7 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
       GPU_link(mat,
                "node_tex_image_linear_no_mip",
                in[0].link,
-               GPU_image(ima, iuser, isdata),
+               GPU_image(ima, iuser),
                &out[0].link,
                &outalpha);
       break;
@@ -112,26 +110,19 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
       GPU_link(mat,
                "node_tex_image_nearest",
                in[0].link,
-               GPU_image(ima, iuser, isdata),
+               GPU_image(ima, iuser),
                &out[0].link,
                &outalpha);
       break;
     default:
-      GPU_link(mat,
-               "node_tex_image_cubic",
-               in[0].link,
-               GPU_image(ima, iuser, isdata),
-               &out[0].link,
-               &outalpha);
+      GPU_link(
+          mat, "node_tex_image_cubic", in[0].link, GPU_image(ima, iuser), &out[0].link, &outalpha);
       break;
   }
 
-  ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
-  if (ibuf && (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) == 0 &&
-      GPU_material_do_color_management(mat)) {
-    GPU_link(mat, "srgb_to_linearrgb", out[0].link, &out[0].link);
+  if (out[0].hasoutput) {
+    GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
   }
-  BKE_image_release_ibuf(ima, ibuf, NULL);
 
   return true;
 }
index 722434c..95e7649 100644 (file)
@@ -57,7 +57,6 @@ static void node_shader_init_tex_image(bNodeTree *UNUSED(ntree), bNode *node)
   NodeTexImage *tex = MEM_callocN(sizeof(NodeTexImage), "NodeTexImage");
   BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
   BKE_texture_colormapping_default(&tex->base.color_mapping);
-  tex->color_space = SHD_COLORSPACE_COLOR;
   BKE_imageuser_default(&tex->iuser);
 
   node->storage = tex;
@@ -99,7 +98,6 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
 
   const char *gpu_node_name = (tex->projection == SHD_PROJ_BOX) ? names_box[tex->interpolation] :
                                                                   names[tex->interpolation];
-  bool do_color_correction = false;
   bool do_texco_extend = (tex->extension != SHD_IMAGE_EXTENSION_REPEAT);
   const bool do_texco_clip = (tex->extension == SHD_IMAGE_EXTENSION_CLIP);
 
@@ -114,20 +112,10 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
   GPUNodeLink *vnor, *ob_mat, *blend;
   GPUNodeLink **texco = &in[0].link;
 
-  int isdata = tex->color_space == SHD_COLORSPACE_NONE;
-
   if (!ima) {
     return GPU_stack_link(mat, node, "node_tex_image_empty", in, out);
   }
 
-  ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL);
-  if ((tex->color_space == SHD_COLORSPACE_COLOR) && ibuf &&
-      (ibuf->colormanage_flag & IMB_COLORMANAGE_IS_DATA) == 0 &&
-      GPU_material_do_color_management(mat)) {
-    do_color_correction = true;
-  }
-  BKE_image_release_ibuf(ima, ibuf, NULL);
-
   if (!*texco) {
     *texco = GPU_attribute(CD_MTFACE, "");
   }
@@ -140,26 +128,20 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
         GPU_link(mat, "set_rgb", *texco, &input_coords);
       }
       if (do_texco_extend) {
-        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser, isdata), texco);
+        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
       }
-      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, isdata));
+      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
       break;
 
     case SHD_PROJ_BOX:
       vnor = GPU_builtin(GPU_WORLD_NORMAL);
       ob_mat = GPU_builtin(GPU_OBJECT_MATRIX);
       blend = GPU_uniform(&tex->projection_blend);
-      gpu_image = GPU_image(ima, iuser, isdata);
+      gpu_image = GPU_image(ima, iuser);
 
       /* equivalent to normal_world_to_object */
       GPU_link(mat, "normal_transform_transposed_m4v3", vnor, ob_mat, &norm);
-      GPU_link(
-          mat, gpu_node_name, *texco, norm, GPU_image(ima, iuser, isdata), &col1, &col2, &col3);
-      if (do_color_correction) {
-        GPU_link(mat, "srgb_to_linearrgb", col1, &col1);
-        GPU_link(mat, "srgb_to_linearrgb", col2, &col2);
-        GPU_link(mat, "srgb_to_linearrgb", col3, &col3);
-      }
+      GPU_link(mat, gpu_node_name, *texco, norm, GPU_image(ima, iuser), &col1, &col2, &col3);
       GPU_stack_link(
           mat, node, "node_tex_image_box", in, out, norm, col1, col2, col3, gpu_image, blend);
       break;
@@ -171,9 +153,9 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
         GPU_link(mat, "set_rgb", *texco, &input_coords);
       }
       if (do_texco_extend) {
-        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser, isdata), texco);
+        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
       }
-      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, isdata));
+      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
       break;
 
     case SHD_PROJ_TUBE:
@@ -183,20 +165,25 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
         GPU_link(mat, "set_rgb", *texco, &input_coords);
       }
       if (do_texco_extend) {
-        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser, isdata), texco);
+        GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
       }
-      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, isdata));
+      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
       break;
   }
 
   if (tex->projection != SHD_PROJ_BOX) {
     if (do_texco_clip) {
       gpu_node_name = names_clip[tex->interpolation];
-      GPU_stack_link(
-          mat, node, gpu_node_name, in, out, GPU_image(ima, iuser, isdata), out[0].link);
+      GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser), out[0].link);
+    }
+  }
+
+  if (out[0].hasoutput) {
+    if (out[1].hasoutput) {
+      GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link);
     }
-    if (do_color_correction) {
-      GPU_link(mat, "srgb_to_linearrgb", out[0].link, &out[0].link);
+    else {
+      GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
     }
   }