diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index e6ec8b22d7a..a08f767f964 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -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()); diff --git a/intern/cycles/render/colorspace.cpp b/intern/cycles/render/colorspace.cpp index cdce1f70188..7b57478ff51 100644 --- a/intern/cycles/render/colorspace.cpp +++ b/intern/cycles/render/colorspace.cpp @@ -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 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 diff --git a/intern/opencolorio/ocio_capi.h b/intern/opencolorio/ocio_capi.h index f4d7717ba46..9ba2d8fb8f9 100644 --- a/intern/opencolorio/ocio_capi.h +++ b/intern/opencolorio/ocio_capi.h @@ -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" diff --git a/intern/opencolorio/ocio_impl.cc b/intern/opencolorio/ocio_impl.cc index 644117e0000..25c1df70045 100644 --- a/intern/opencolorio/ocio_impl.cc +++ b/intern/opencolorio/ocio_impl.cc @@ -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; diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 16275c96eec..74d33a9c275 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -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 diff --git a/source/blender/blenloader/intern/versioning_cycles.c b/source/blender/blenloader/intern/versioning_cycles.c index 023bd685352..2bd379c6f19 100644 --- a/source/blender/blenloader/intern/versioning_cycles.c +++ b/source/blender/blenloader/intern/versioning_cycles.c @@ -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; } diff --git a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c index c23b4c254e4..d57c43ab375 100644 --- a/source/blender/draw/engines/gpencil/gpencil_draw_utils.c +++ b/source/blender/draw/engines/gpencil/gpencil_draw_utils.c @@ -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); diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl index acf60fc2d59..80fae9ab518 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl @@ -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; diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl index cc47e12b303..98c47b1f1f0 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl @@ -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); diff --git a/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl b/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl index 6b7cee888ea..6b3fcad1240 100644 --- a/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl +++ b/source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl @@ -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 */ diff --git a/source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl b/source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl index c76ad8c1d7b..96f8f6e4c7a 100644 --- a/source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl +++ b/source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl b/source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl index 32243787401..51bce639b63 100644 --- a/source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl +++ b/source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl @@ -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; } diff --git a/source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl b/source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl index 2596fc4cf88..af9f1d14f4a 100644 --- a/source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl +++ b/source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl @@ -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; } diff --git a/source/blender/draw/engines/workbench/workbench_forward.c b/source/blender/draw/engines/workbench/workbench_forward.c index 25f95f0d4bc..13bd6fe9e4d 100644 --- a/source/blender/draw/engines/workbench/workbench_forward.c +++ b/source/blender/draw/engines/workbench/workbench_forward.c @@ -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 { diff --git a/source/blender/draw/engines/workbench/workbench_materials.c b/source/blender/draw/engines/workbench/workbench_materials.c index c403e358d6a..b280b6fd01a 100644 --- a/source/blender/draw/engines/workbench/workbench_materials.c +++ b/source/blender/draw/engines/workbench/workbench_materials.c @@ -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 { diff --git a/source/blender/draw/intern/draw_manager_data.c b/source/blender/draw/intern/draw_manager_data.c index 1f48129116d..0375db12255 100644 --- a/source/blender/draw/intern/draw_manager_data.c +++ b/source/blender/draw/intern/draw_manager_data.c @@ -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); } diff --git a/source/blender/draw/modes/object_mode.c b/source/blender/draw/modes/object_mode.c index cc313620a11..9d6732fbcab 100644 --- a/source/blender/draw/modes/object_mode.c +++ b/source/blender/draw/modes/object_mode.c @@ -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); diff --git a/source/blender/draw/modes/paint_texture_mode.c b/source/blender/draw/modes/paint_texture_mode.c index 123dc7fca88..da32744b9ea 100644 --- a/source/blender/draw/modes/paint_texture_mode.c +++ b/source/blender/draw/modes/paint_texture_mode.c @@ -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( diff --git a/source/blender/draw/modes/shaders/object_empty_image_frag.glsl b/source/blender/draw/modes/shaders/object_empty_image_frag.glsl index 386d05636f9..dbc403dc39b 100644 --- a/source/blender/draw/modes/shaders/object_empty_image_frag.glsl +++ b/source/blender/draw/modes/shaders/object_empty_image_frag.glsl @@ -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) { diff --git a/source/blender/draw/modes/shaders/paint_texture_frag.glsl b/source/blender/draw/modes/shaders/paint_texture_frag.glsl index c7e110122c5..5d74213a445 100644 --- a/source/blender/draw/modes/shaders/paint_texture_frag.glsl +++ b/source/blender/draw/modes/shaders/paint_texture_frag.glsl @@ -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 diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 0340a4989e1..9cc12b41a63 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -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) { diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index 447fea8098c..bf6ec961a5d 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -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); } diff --git a/source/blender/gpu/GPU_draw.h b/source/blender/gpu/GPU_draw.h index 300fc7c65a2..eb54ff127d8 100644 --- a/source/blender/gpu/GPU_draw.h +++ b/source/blender/gpu/GPU_draw.h @@ -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, diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h index 1640209c717..86c9764a68f 100644 --- a/source/blender/gpu/GPU_material.h +++ b/source/blender/gpu/GPU_material.h @@ -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); diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h index 3527398a396..3fb7dfc6331 100644 --- a/source/blender/gpu/GPU_texture.h +++ b/source/blender/gpu/GPU_texture.h @@ -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, diff --git a/source/blender/gpu/intern/gpu_codegen.c b/source/blender/gpu/intern/gpu_codegen.c index a2c1a01a82c..6259780c261 100644 --- a/source/blender/gpu/intern/gpu_codegen.c +++ b/source/blender/gpu/intern/gpu_codegen.c @@ -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; } diff --git a/source/blender/gpu/intern/gpu_codegen.h b/source/blender/gpu/intern/gpu_codegen.h index 73155c3aafa..d1bb3f26920 100644 --- a/source/blender/gpu/intern/gpu_codegen.h +++ b/source/blender/gpu/intern/gpu_codegen.h @@ -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 ...) */ diff --git a/source/blender/gpu/intern/gpu_draw.c b/source/blender/gpu/intern/gpu_draw.c index edc2f2171a5..f1c82dc53a7 100644 --- a/source/blender/gpu/intern/gpu_draw.c +++ b/source/blender/gpu/intern/gpu_draw.c @@ -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 void gpu_verify_high_bit_srgb_buffer_slice(float *srgb_frect, - ImBuf *ibuf, - const int start_line, - const int height) +static uint gpu_texture_create_from_ibuf(Image *ima, ImBuf *ibuf, int textarget) { - 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); + 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; + } + + 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 verify_thread_do(void *data_v, int start_scanline, int num_scanlines) +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) { - VerifyThreadData *data = (VerifyThreadData *)data_v; - gpu_verify_high_bit_srgb_buffer_slice( - data->srgb_frect, data->ibuf, start_scanline, num_scanlines); -} + /* 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; -static void gpu_verify_high_bit_srgb_buffer(float *srgb_frect, ImBuf *ibuf) -{ - if (ibuf->y < 64) { - gpu_verify_high_bit_srgb_buffer_slice(srgb_frect, ibuf, 0, ibuf->y); + /* 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 { - VerifyThreadData data; - data.ibuf = ibuf; - data.srgb_frect = srgb_frect; - IMB_processor_apply_threaded_scanlines(ibuf->y, verify_thread_do, &data); + 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 gpu_texture_update_unscaled( + uchar *rect, float *rect_float, int x, int y, int w, int h, GLint tex_stride, GLint tex_offset) +{ + /* 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_texture_update_from_ibuf(ImBuf *ibuf, int x, int y, int w, int h) +{ + /* 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 { + /* 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); + gpu_texture_update_from_ibuf(ibuf, x, y, w, h); - 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); - - /* 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) diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c index 1a668a48eed..6aa13b0d8ca 100644 --- a/source/blender/gpu/intern/gpu_material.c +++ b/source/blender/gpu/intern/gpu_material.c @@ -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); diff --git a/source/blender/gpu/intern/gpu_texture.c b/source/blender/gpu/intern/gpu_texture.c index ad4831ed903..58d0dd5576f 100644 --- a/source/blender/gpu/intern/gpu_texture.c +++ b/source/blender/gpu/intern/gpu_texture.c @@ -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; diff --git a/source/blender/gpu/shaders/gpu_shader_material.glsl b/source/blender/gpu/shaders/gpu_shader_material.glsl index 2f80bc95a0c..5596940cbf6 100644 --- a/source/blender/gpu/shaders/gpu_shader_material.glsl +++ b/source/blender/gpu/shaders/gpu_shader_material.glsl @@ -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)); diff --git a/source/blender/imbuf/IMB_colormanagement.h b/source/blender/imbuf/IMB_colormanagement.h index 620f8984d9f..e683d38a0aa 100644 --- a/source/blender/imbuf/IMB_colormanagement.h +++ b/source/blender/imbuf/IMB_colormanagement.h @@ -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" diff --git a/source/blender/imbuf/intern/IMB_colormanagement_intern.h b/source/blender/imbuf/intern/IMB_colormanagement_intern.h index 2566016ffdd..a83f2d60b8c 100644 --- a/source/blender/imbuf/intern/IMB_colormanagement_intern.h +++ b/source/blender/imbuf/intern/IMB_colormanagement_intern.h @@ -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 { diff --git a/source/blender/imbuf/intern/colormanagement.c b/source/blender/imbuf/intern/colormanagement.c index f31d4ede693..b460d268d38 100644 --- a/source/blender/imbuf/intern/colormanagement.c +++ b/source/blender/imbuf/intern/colormanagement.c @@ -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 diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h index fec5f96b4f3..0aceeda20d5 100644 --- a/source/blender/makesdna/DNA_image_types.h +++ b/source/blender/makesdna/DNA_image_types.h @@ -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 */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 226e1d2f841..3890fc63f3f 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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 diff --git a/source/blender/makesrna/intern/rna_color.c b/source/blender/makesrna/intern/rna_color.c index 40ee069657c..8ba1f5440be 100644 --- a/source/blender/makesrna/intern/rna_color.c +++ b/source/blender/makesrna/intern/rna_color.c @@ -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"); diff --git a/source/blender/makesrna/intern/rna_image_api.c b/source/blender/makesrna/intern/rna_image_api.c index ca75f862b11..553dbeeb97b 100644 --- a/source/blender/makesrna/intern/rna_image_api.c +++ b/source/blender/makesrna/intern/rna_image_api.c @@ -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); + GPUTexture *tex = GPU_texture_from_blender(image, &iuser, GL_TEXTURE_2D); - /* clean glError buffer */ - while (glGetError() != GL_NO_ERROR) { - } - - 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); diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index e4270f3854e..4906f8ac28e 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -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( diff --git a/source/blender/nodes/shader/node_shader_util.h b/source/blender/nodes/shader/node_shader_util.h index 916cb81953e..165ada05a56 100644 --- a/source/blender/nodes/shader/node_shader_util.h +++ b/source/blender/nodes/shader/node_shader_util.h @@ -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); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_environment.c b/source/blender/nodes/shader/nodes/node_shader_tex_environment.c index c3b8bc8bfd0..615f55e4350 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_environment.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_environment.c @@ -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; } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_image.c b/source/blender/nodes/shader/nodes/node_shader_tex_image.c index 722434ce783..95e76491b4a 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_image.c +++ b/source/blender/nodes/shader/nodes/node_shader_tex_image.c @@ -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 (do_color_correction) { - GPU_link(mat, "srgb_to_linearrgb", out[0].link, &out[0].link); + } + + if (out[0].hasoutput) { + if (out[1].hasoutput) { + GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link); + } + else { + GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link); } }