diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index fdfa8378d57..5e58e38cfcd 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -31,7 +31,7 @@ extern "C" { /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 17 +#define BLENDER_FILE_SUBVERSION 18 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 2d8fcddef83..391b01b6684 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -10,6 +10,9 @@ #include #include +#include + +#include /* Define macros in `DNA_genfile.h`. */ #define DNA_GENFILE_VERSIONING_MACROS @@ -44,8 +47,10 @@ #include "BLI_assert.h" #include "BLI_listbase.h" #include "BLI_map.hh" +#include "BLI_math_base.hh" #include "BLI_math_rotation.h" #include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" #include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_ref.hh" @@ -62,6 +67,7 @@ #include "BKE_curve.hh" #include "BKE_customdata.hh" #include "BKE_effect.h" +#include "BKE_fcurve.hh" #include "BKE_file_handler.hh" #include "BKE_grease_pencil.hh" #include "BKE_idprop.hh" @@ -1048,6 +1054,203 @@ static bool versioning_clear_strip_unused_flag(Strip *seq, void * /*user_data*/) return true; } +/* Adjust the values of the given FCurve key frames by applying the given function. The function is + * expected to get and return a float representing the value of the key frame. The FCurve is + * potentially changed to have the given property type, if not already the case. */ +template +static void adjust_fcurve_key_frame_values(FCurve *fcurve, + const PropertyType property_type, + const Function &function) +{ + /* Adjust key frames. */ + if (fcurve->bezt) { + for (int i = 0; i < fcurve->totvert; i++) { + fcurve->bezt[i].vec[0][1] = function(fcurve->bezt[i].vec[0][1]); + fcurve->bezt[i].vec[1][1] = function(fcurve->bezt[i].vec[1][1]); + fcurve->bezt[i].vec[2][1] = function(fcurve->bezt[i].vec[2][1]); + } + } + + /* Adjust baked key frames. */ + if (fcurve->fpt) { + for (int i = 0; i < fcurve->totvert; i++) { + fcurve->fpt[i].vec[1] = function(fcurve->fpt[i].vec[1]); + } + } + + /* Setup the flags based on the property type. */ + fcurve->flag &= ~(FCURVE_INT_VALUES | FCURVE_DISCRETE_VALUES); + switch (property_type) { + case PROP_FLOAT: + break; + case PROP_INT: + fcurve->flag |= FCURVE_INT_VALUES; + break; + default: + fcurve->flag |= (FCURVE_DISCRETE_VALUES | FCURVE_INT_VALUES); + break; + } + + /* Recalculate the automatic handles of the FCurve after adjustments. */ + BKE_fcurve_handles_recalc(fcurve); +} + +/* The Threshold, Mix, and Size properties of the node were converted into node inputs, and + * two new outputs were added. + * + * A new Highlights output was added to expose the extracted highlights, this is not relevant for + * versioning. + * + * A new Glare output was added to expose just the generated glare without the input image itself. + * this relevant for versioning the Mix property as will be shown. + * + * The Threshold, Iterations, Fade, Color Modulation, Streaks, and Streaks Angle Offset properties + * were converted into node inputs, maintaining its type and range, so we just transfer its value + * as is. + * + * The Mix property was converted into a Strength input, but its range changed from [-1, 1] to [0, + * 1]. For the [-1, 0] sub-range, -1 used to mean zero strength and 0 used to mean full strength, + * so we can convert between the two ranges by negating the mix factor and subtracting it from 1. + * The [0, 1] sub-range on the other hand was useless except for the value 1, because it linearly + * interpolates between Image + Glare and Glare, so it essentially adds an attenuated version of + * the input image to the glare. When it is 1, only the glare is returned. So we split that range + * in half as a heuristic and for values in the range [0.5, 1], we just reconnect the output to the + * newly added Glare output. + * + * The Size property was converted into a float node input, and its range was changed from [1, 9] + * to [0, 1]. For Bloom, the [1, 9] range was related exponentially to the actual size of the + * glare, that is, 9 meant the glare covers the entire image, 8 meant it covers half, 7 meant it + * covers quarter and so on. The new range is linear and relative to the image size, that is, 1 + * means the entire image and 0 means nothing. So we can convert from the [1, 9] range to [0, 1] + * range using the relation 2^(x-9). + * For Fog Glow, the [1, 9] range was related to the absolute size of the Fog Glow kernel in + * pixels, where it is 2^size pixels in size. There is no way to version this accurately, since the + * new size is relative to the input image size, which is runtime information. But we can assume + * the render size as a guess and compute the size relative to that. */ +static void do_version_glare_node_options_to_inputs(const Scene *scene, + bNodeTree *node_tree, + bNode *node) +{ + NodeGlare *storage = static_cast(node->storage); + if (!storage) { + return; + } + + /* Get the newly added inputs. */ + bNodeSocket *threshold = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Threshold", "Threshold"); + bNodeSocket *strength = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Strength", "Strength"); + bNodeSocket *size = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Size", "Size"); + bNodeSocket *streaks = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_INT, PROP_NONE, "Streaks", "Streaks"); + bNodeSocket *streaks_angle = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_ANGLE, "Streaks Angle", "Streaks Angle"); + bNodeSocket *iterations = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_INT, PROP_NONE, "Iterations", "Iterations"); + bNodeSocket *fade = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Fade", "Fade"); + bNodeSocket *color_modulation = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_IN, SOCK_FLOAT, PROP_FACTOR, "Color Modulation", "Color Modulation"); + + /* Function to remap the Mix property to the range of the new Strength input. See function + * description. */ + auto mix_to_strength = [](const float mix) { + return 1.0f - blender::math::clamp(-mix, 0.0f, 1.0f); + }; + + /* Function to remap the Size property to its new range. See function description. */ + blender::int2 render_size; + BKE_render_resolution(&scene->r, true, &render_size.x, &render_size.y); + const int max_render_size = blender::math::reduce_max(render_size); + auto size_to_linear = [&](const int size) { + if (storage->type == CMP_NODE_GLARE_BLOOM) { + return blender::math::pow(2.0f, float(size - 9)); + } + return blender::math::min(1.0f, float((1 << size) + 1) / float(max_render_size)); + }; + + /* Assign the inputs the values from the old deprecated properties. */ + threshold->default_value_typed()->value = storage->threshold; + strength->default_value_typed()->value = mix_to_strength(storage->mix); + size->default_value_typed()->value = size_to_linear(storage->size); + streaks->default_value_typed()->value = storage->streaks; + streaks_angle->default_value_typed()->value = storage->angle_ofs; + iterations->default_value_typed()->value = storage->iter; + fade->default_value_typed()->value = storage->fade; + color_modulation->default_value_typed()->value = storage->colmod; + + /* Compute the RNA path of the node. */ + char escaped_node_name[sizeof(node->name) * 2 + 1]; + BLI_str_escape(escaped_node_name, node->name, sizeof(escaped_node_name)); + const std::string node_rna_path = fmt::format("nodes[\"{}\"]", escaped_node_name); + + BKE_fcurves_id_cb(&node_tree->id, [&](ID * /*id*/, FCurve *fcurve) { + /* The FCurve does not belong to the node since its RNA path doesn't start with the node's RNA + * path. */ + if (!blender::StringRef(fcurve->rna_path).startswith(node_rna_path)) { + return; + } + + /* Change the RNA path of the FCurve from the old properties to the new inputs, adjusting the + * values of the FCurves frames when needed. */ + char *old_rna_path = fcurve->rna_path; + if (BLI_str_endswith(fcurve->rna_path, "threshold")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[1].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "mix")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[2].default_value"); + adjust_fcurve_key_frame_values( + fcurve, PROP_FLOAT, [&](const float value) { return mix_to_strength(value); }); + } + else if (BLI_str_endswith(fcurve->rna_path, "size")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[3].default_value"); + adjust_fcurve_key_frame_values( + fcurve, PROP_FLOAT, [&](const float value) { return size_to_linear(value); }); + } + else if (BLI_str_endswith(fcurve->rna_path, "streaks")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[4].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "angle_offset")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[5].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "iterations")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[6].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "fade")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[7].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "color_modulation")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[8].default_value"); + } + + /* The RNA path was changed, free the old path. */ + if (fcurve->rna_path != old_rna_path) { + MEM_freeN(old_rna_path); + } + }); + + /* If the Mix factor is between [0.5, 1], then the user actually wants the Glare output, so + * reconnect the output to the newly created Glare output. */ + if (storage->mix > 0.5f) { + bNodeSocket *image_output = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_OUT, SOCK_RGBA, PROP_NONE, "Image", "Image"); + bNodeSocket *glare_output = version_node_add_socket_if_not_exist( + node_tree, node, SOCK_OUT, SOCK_RGBA, PROP_NONE, "Glare", "Glare"); + + LISTBASE_FOREACH_BACKWARD_MUTABLE (bNodeLink *, link, &node_tree->links) { + if (link->fromsock != image_output) { + continue; + } + + /* Relink from the Image output to the Glare output. */ + blender::bke::node_add_link(node_tree, node, glare_output, link->tonode, link->tosock); + blender::bke::node_remove_link(node_tree, link); + } + } +} + static bool all_scenes_use(Main *bmain, const blender::Span engines) { if (!bmain->scenes.first) { @@ -1283,6 +1486,22 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 18)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type != NTREE_COMPOSIT) { + continue; + } + + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type != CMP_NODE_GLARE) { + continue; + } + do_version_glare_node_options_to_inputs(reinterpret_cast(id), ntree, node); + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 5d7b13a7bbb..724c9fd7905 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -193,6 +193,8 @@ set(GLSL_SRC shaders/compositor_glare_simple_star_vertical_pass.glsl shaders/compositor_glare_streaks_accumulate.glsl shaders/compositor_glare_streaks_filter.glsl + shaders/compositor_glare_write_glare_output.glsl + shaders/compositor_glare_write_highlights_output.glsl shaders/compositor_id_mask.glsl shaders/compositor_image_crop.glsl shaders/compositor_inpaint_compute_boundary.glsl diff --git a/source/blender/compositor/shaders/compositor_glare_bloom_upsample.glsl b/source/blender/compositor/shaders/compositor_glare_bloom_upsample.glsl index c5edc96253e..b7a1a5564d5 100644 --- a/source/blender/compositor/shaders/compositor_glare_bloom_upsample.glsl +++ b/source/blender/compositor/shaders/compositor_glare_bloom_upsample.glsl @@ -37,5 +37,6 @@ void main() upsampled += (1.0 / 16.0) * texture(input_tx, coordinates + pixel_size * vec2(1.0, -1.0)); upsampled += (1.0 / 16.0) * texture(input_tx, coordinates + pixel_size * vec2(1.0, 1.0)); - imageStore(output_img, texel, imageLoad(output_img, texel) + upsampled); + vec4 combined = imageLoad(output_img, texel) + upsampled; + imageStore(output_img, texel, vec4(combined.rgb, 1.0f)); } diff --git a/source/blender/compositor/shaders/compositor_glare_ghost_accumulate.glsl b/source/blender/compositor/shaders/compositor_glare_ghost_accumulate.glsl index 10fd146348d..c2a269be81a 100644 --- a/source/blender/compositor/shaders/compositor_glare_ghost_accumulate.glsl +++ b/source/blender/compositor/shaders/compositor_glare_ghost_accumulate.glsl @@ -37,5 +37,6 @@ void main() } vec4 current_accumulated_ghost = imageLoad(accumulated_ghost_img, texel); - imageStore(accumulated_ghost_img, texel, current_accumulated_ghost + accumulated_ghost); + vec4 combined_ghost = current_accumulated_ghost + accumulated_ghost; + imageStore(accumulated_ghost_img, texel, vec4(combined_ghost.rgb, 1.0f)); } diff --git a/source/blender/compositor/shaders/compositor_glare_mix.glsl b/source/blender/compositor/shaders/compositor_glare_mix.glsl index 45718c8e3a6..6b3a542de0a 100644 --- a/source/blender/compositor/shaders/compositor_glare_mix.glsl +++ b/source/blender/compositor/shaders/compositor_glare_mix.glsl @@ -15,18 +15,7 @@ void main() vec4 glare_color = texture(glare_tx, normalized_coordinates); vec4 input_color = max(vec4(0.0), texture_load(input_tx, texel)); - /* The mix factor is in the range [-1, 1] and linearly interpolate between the three values such - * that: - * 1 => Glare only. - * 0 => Input + Glare. - * -1 => Input only. - * We implement that as a weighted sum as follows. When the mix factor is 1, the glare weight - * should be 1 and the input weight should be 0. When the mix factor is -1, the glare weight - * should be 0 and the input weight should be 1. When the mix factor is 0, both weights should - * be 1. This can be expressed using the following compact min max expressions. */ - float input_weight = 1.0 - max(0.0, mix_factor); - float glare_weight = 1.0 + min(0.0, mix_factor); - vec3 highlights = input_weight * input_color.rgb + glare_weight * glare_color.rgb; + vec3 highlights = input_color.rgb + glare_color.rgb * strength; imageStore(output_img, texel, vec4(highlights, input_color.a)); } diff --git a/source/blender/compositor/shaders/compositor_glare_simple_star_anti_diagonal_pass.glsl b/source/blender/compositor/shaders/compositor_glare_simple_star_anti_diagonal_pass.glsl index b2b60ab1883..16085c32ef4 100644 --- a/source/blender/compositor/shaders/compositor_glare_simple_star_anti_diagonal_pass.glsl +++ b/source/blender/compositor/shaders/compositor_glare_simple_star_anti_diagonal_pass.glsl @@ -54,6 +54,7 @@ void main() ivec2 texel = start + j * direction; vec4 horizontal = texture_load(diagonal_tx, texel); vec4 vertical = imageLoad(anti_diagonal_img, texel); - imageStore(anti_diagonal_img, texel, horizontal + vertical); + vec4 combined = horizontal + vertical; + imageStore(anti_diagonal_img, texel, vec4(combined.rgb, 1.0)); } } diff --git a/source/blender/compositor/shaders/compositor_glare_simple_star_vertical_pass.glsl b/source/blender/compositor/shaders/compositor_glare_simple_star_vertical_pass.glsl index 2dd5bbf4672..e993eed597f 100644 --- a/source/blender/compositor/shaders/compositor_glare_simple_star_vertical_pass.glsl +++ b/source/blender/compositor/shaders/compositor_glare_simple_star_vertical_pass.glsl @@ -48,6 +48,7 @@ void main() ivec2 texel = ivec2(gl_GlobalInvocationID.x, y); vec4 horizontal = texture_load(horizontal_tx, texel); vec4 vertical = imageLoad(vertical_img, texel); - imageStore(vertical_img, texel, horizontal + vertical); + vec4 combined = horizontal + vertical; + imageStore(vertical_img, texel, vec4(combined.rgb, 1.0)); } } diff --git a/source/blender/compositor/shaders/compositor_glare_streaks_accumulate.glsl b/source/blender/compositor/shaders/compositor_glare_streaks_accumulate.glsl index e51edc19966..75115c2e5d4 100644 --- a/source/blender/compositor/shaders/compositor_glare_streaks_accumulate.glsl +++ b/source/blender/compositor/shaders/compositor_glare_streaks_accumulate.glsl @@ -9,5 +9,6 @@ void main() ivec2 texel = ivec2(gl_GlobalInvocationID.xy); vec4 attenuated_streak = texture_load(streak_tx, texel) * attenuation_factor; vec4 current_accumulated_streaks = imageLoad(accumulated_streaks_img, texel); - imageStore(accumulated_streaks_img, texel, current_accumulated_streaks + attenuated_streak); + vec4 combined_streaks = current_accumulated_streaks + attenuated_streak; + imageStore(accumulated_streaks_img, texel, vec4(combined_streaks.rgb, 1.0)); } diff --git a/source/blender/compositor/shaders/compositor_glare_write_glare_output.glsl b/source/blender/compositor/shaders/compositor_glare_write_glare_output.glsl new file mode 100644 index 00000000000..ace835d7c6a --- /dev/null +++ b/source/blender/compositor/shaders/compositor_glare_write_glare_output.glsl @@ -0,0 +1,13 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + vec2 normalized_coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(output_img)); + vec4 glare_value = texture(input_tx, normalized_coordinates); + vec4 adjusted_glare_value = glare_value * strength; + imageStore(output_img, texel, vec4(adjusted_glare_value.rgb, 1.0)); +} diff --git a/source/blender/compositor/shaders/compositor_glare_write_highlights_output.glsl b/source/blender/compositor/shaders/compositor_glare_write_highlights_output.glsl new file mode 100644 index 00000000000..814a6f3cb70 --- /dev/null +++ b/source/blender/compositor/shaders/compositor_glare_write_highlights_output.glsl @@ -0,0 +1,11 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + vec2 normalized_coordinates = (vec2(texel) + vec2(0.5)) / vec2(imageSize(output_img)); + imageStore(output_img, texel, texture(input_tx, normalized_coordinates)); +} diff --git a/source/blender/compositor/shaders/infos/compositor_glare_info.hh b/source/blender/compositor/shaders/infos/compositor_glare_info.hh index dae0ecaac31..f15378831d2 100644 --- a/source/blender/compositor/shaders/infos/compositor_glare_info.hh +++ b/source/blender/compositor/shaders/infos/compositor_glare_info.hh @@ -19,7 +19,7 @@ GPU_SHADER_CREATE_END() GPU_SHADER_CREATE_INFO(compositor_glare_mix) LOCAL_GROUP_SIZE(16, 16) -PUSH_CONSTANT(FLOAT, mix_factor) +PUSH_CONSTANT(FLOAT, strength) SAMPLER(0, FLOAT_2D, input_tx) SAMPLER(1, FLOAT_2D, glare_tx) IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img) @@ -27,6 +27,23 @@ COMPUTE_SOURCE("compositor_glare_mix.glsl") DO_STATIC_COMPILATION() GPU_SHADER_CREATE_END() +GPU_SHADER_CREATE_INFO(compositor_glare_write_glare_output) +LOCAL_GROUP_SIZE(16, 16) +PUSH_CONSTANT(FLOAT, strength) +SAMPLER(0, FLOAT_2D, input_tx) +IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img) +COMPUTE_SOURCE("compositor_glare_write_glare_output.glsl") +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_glare_write_highlights_output) +LOCAL_GROUP_SIZE(16, 16) +SAMPLER(0, FLOAT_2D, input_tx) +IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img) +COMPUTE_SOURCE("compositor_glare_write_highlights_output.glsl") +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + /* ------------ * Ghost Glare. * ------------ */ diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 43f825b1b11..494e8003c60 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1223,12 +1223,19 @@ typedef struct NodeScriptDict { /** glare node. */ typedef struct NodeGlare { - char quality, type, iter; - /* XXX angle is only kept for backward/forward compatibility, - * was used for two different things, see #50736. */ - char angle DNA_DEPRECATED, _pad0, size, star_45, streaks; - float colmod, mix, threshold, fade; - float angle_ofs; + char type; + char quality; + char iter DNA_DEPRECATED; + char angle DNA_DEPRECATED; + char _pad0; + char size DNA_DEPRECATED; + char star_45; + char streaks DNA_DEPRECATED; + float colmod DNA_DEPRECATED; + float mix DNA_DEPRECATED; + float threshold DNA_DEPRECATED; + float fade DNA_DEPRECATED; + float angle_ofs DNA_DEPRECATED; char _pad1[4]; } NodeGlare; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index fd0b6c6d193..cee5580c6ef 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -14,6 +15,7 @@ #include "BLI_function_ref.hh" #include "BLI_linear_allocator.hh" +#include "BLI_math_base.hh" #include "BLI_math_rotation.h" #include "BLI_string.h" #include "BLI_string_utf8_symbols.h" @@ -3379,6 +3381,152 @@ static void rna_NodeColorBalance_update_cdl(Main *bmain, Scene *scene, PointerRN rna_Node_update(bmain, scene, ptr); } +/* -------------------------------------------------------------------- + * Glare Node Compatibility Setters/Getters. + * + * The Glare node properties are now deprecated and replaced corresponding inputs. So we provide + * setters/getters for compatibility until those are removed in 5.0. See the + * do_version_glare_node_options_to_inputs function for conversion + */ + +static float rna_NodeGlare_threshold_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Threshold"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return RNA_float_get(&input_rna_pointer, "default_value"); +} + +static void rna_NodeGlare_threshold_set(PointerRNA *ptr, const float value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Threshold"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_float_set(&input_rna_pointer, "default_value", value); +} + +static float rna_NodeGlare_mix_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Strength"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return RNA_float_get(&input_rna_pointer, "default_value") - 1; +} + +static void rna_NodeGlare_mix_set(PointerRNA *ptr, const float value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Strength"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + const float mix_value = 1.0f - blender::math::clamp(-value, 0.0f, 1.0f); + RNA_float_set(&input_rna_pointer, "default_value", mix_value); +} + +static int rna_NodeGlare_size_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Size"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + const float size_value = RNA_float_get(&input_rna_pointer, "default_value"); + if (size_value == 0.0f) { + return 1; + } + return blender::math::max(1, 9 - int(-std::log2(size_value))); +} + +static void rna_NodeGlare_size_set(PointerRNA *ptr, const int value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Size"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + const float size_value = blender::math::pow(2.0f, float(value - 9)); + RNA_float_set(&input_rna_pointer, "default_value", size_value); +} + +static int rna_NodeGlare_streaks_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return blender::math::clamp(RNA_int_get(&input_rna_pointer, "default_value"), 1, 16); +} + +static void rna_NodeGlare_streaks_set(PointerRNA *ptr, const int value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_int_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 1, 16)); +} + +static float rna_NodeGlare_angle_offset_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks Angle"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return RNA_float_get(&input_rna_pointer, "default_value"); +} + +static void rna_NodeGlare_angle_offset_set(PointerRNA *ptr, const float value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Streaks Angle"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_float_set(&input_rna_pointer, "default_value", value); +} + +static int rna_NodeGlare_iterations_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Iterations"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return blender::math::clamp(RNA_int_get(&input_rna_pointer, "default_value"), 2, 5); +} + +static void rna_NodeGlare_iterations_set(PointerRNA *ptr, const int value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Iterations"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_int_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 2, 5)); +} + +static float rna_NodeGlare_fade_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Fade"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return blender::math::clamp(RNA_float_get(&input_rna_pointer, "default_value"), 0.75f, 1.0f); +} + +static void rna_NodeGlare_fade_set(PointerRNA *ptr, const float value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Fade"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_float_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 0.75f, 1.0f)); +} + +static float rna_NodeGlare_color_modulation_get(PointerRNA *ptr) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Color Modulation"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + return blender::math::clamp(RNA_float_get(&input_rna_pointer, "default_value"), 0.0f, 1.0f); +} + +static void rna_NodeGlare_color_modulation_set(PointerRNA *ptr, const float value) +{ + bNode *node = static_cast(ptr->data); + bNodeSocket *input = blender::bke::node_find_socket(node, SOCK_IN, "Color Modulation"); + PointerRNA input_rna_pointer = RNA_pointer_create(ptr->owner_id, &RNA_NodeSocket, input); + RNA_float_set(&input_rna_pointer, "default_value", blender::math::clamp(value, 0.0f, 1.0f)); +} + +/* -------------------------------------------------------------------- + * White Balance Node. + */ + static void rna_NodeColorBalance_input_whitepoint_get(PointerRNA *ptr, float value[3]) { bNode *node = static_cast(ptr->data); @@ -7835,54 +7983,57 @@ static void def_cmp_glare(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "iterations", PROP_INT, PROP_NONE); - RNA_def_property_int_sdna(prop, nullptr, "iter"); + RNA_def_property_int_funcs( + prop, "rna_NodeGlare_iterations_get", "rna_NodeGlare_iterations_set", nullptr); RNA_def_property_range(prop, 2, 5); - RNA_def_property_ui_text(prop, "Iterations", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text(prop, "Iterations", "(Deprecated: Use Iterations input instead)"); prop = RNA_def_property(srna, "color_modulation", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, nullptr, "colmod"); + RNA_def_property_float_funcs( + prop, "rna_NodeGlare_color_modulation_get", "rna_NodeGlare_color_modulation_set", nullptr); RNA_def_property_range(prop, 0.0f, 1.0f); RNA_def_property_ui_text( prop, "Color Modulation", "Amount of Color Modulation, modulates colors of streaks and ghosts for " - "a spectral dispersion effect"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + "a spectral dispersion effect. (Deprecated: Use Color Modulation input instead)"); prop = RNA_def_property(srna, "mix", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, nullptr, "mix"); + RNA_def_property_float_funcs(prop, "rna_NodeGlare_mix_get", "rna_NodeGlare_mix_set", nullptr); RNA_def_property_range(prop, -1.0f, 1.0f); - RNA_def_property_ui_text( - prop, "Mix", "-1 is original image only, 0 is exact 50/50 mix, 1 is processed image only"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text(prop, + "Mix", + "1 is original image only, 0 is exact 50/50 mix, 1 is processed image " + "only. (Deprecated: Use Strength input instead)"); prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, nullptr, "threshold"); + RNA_def_property_float_funcs( + prop, "rna_NodeGlare_threshold_get", "rna_NodeGlare_threshold_set", nullptr); RNA_def_property_range(prop, 0.0f, FLT_MAX); - RNA_def_property_ui_text( - prop, - "Threshold", - "The glare filter will only be applied to pixels brighter than this value"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text(prop, + "Threshold", + "The glare filter will only be applied to pixels brighter than this " + "value. (Deprecated: Use Threshold input instead)"); prop = RNA_def_property(srna, "streaks", PROP_INT, PROP_NONE); - RNA_def_property_int_sdna(prop, nullptr, "streaks"); + RNA_def_property_int_funcs( + prop, "rna_NodeGlare_streaks_get", "rna_NodeGlare_streaks_set", nullptr); RNA_def_property_range(prop, 1, 16); - RNA_def_property_ui_text(prop, "Streaks", "Total number of streaks"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text( + prop, "Streaks", "Total number of streaks. (Deprecated: Use Streaks input instead)"); prop = RNA_def_property(srna, "angle_offset", PROP_FLOAT, PROP_ANGLE); - RNA_def_property_float_sdna(prop, nullptr, "angle_ofs"); + RNA_def_property_float_funcs( + prop, "rna_NodeGlare_angle_offset_get", "rna_NodeGlare_angle_offset_set", nullptr); RNA_def_property_range(prop, 0.0f, DEG2RADF(180.0f)); - RNA_def_property_ui_text(prop, "Angle Offset", "Streak angle offset"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text( + prop, "Angle Offset", "Streak angle offset. (Deprecated: Use Streaks Angle input instead)"); prop = RNA_def_property(srna, "fade", PROP_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, nullptr, "fade"); + RNA_def_property_float_funcs(prop, "rna_NodeGlare_fade_get", "rna_NodeGlare_fade_set", nullptr); RNA_def_property_range(prop, 0.75f, 1.0f); - RNA_def_property_ui_text(prop, "Fade", "Streak fade-out factor"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_ui_text( + prop, "Fade", "Streak fade-out factor. (Deprecated: Use Fade input instead)"); prop = RNA_def_property(srna, "use_rotate_45", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "star_45", 0); @@ -7890,15 +8041,12 @@ static void def_cmp_glare(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE); - RNA_def_property_int_sdna(prop, nullptr, "size"); + RNA_def_property_int_funcs(prop, "rna_NodeGlare_size_get", "rna_NodeGlare_size_set", nullptr); RNA_def_property_range(prop, 1, 9); - RNA_def_property_ui_text( - prop, - "Size", - "Glow/glare size (not actual size; relative to initial size of bright area of pixels)"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); - - /* TODO */ + RNA_def_property_ui_text(prop, + "Size", + "Glow/glare size (not actual size; relative to initial size of bright " + "area of pixels). (Deprecated: Use Size input instead)"); } static void def_cmp_tonemap(StructRNA *srna) diff --git a/source/blender/nodes/composite/nodes/node_composite_glare.cc b/source/blender/nodes/composite/nodes/node_composite_glare.cc index f74361a127d..9555b98559a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_glare.cc +++ b/source/blender/nodes/composite/nodes/node_composite_glare.cc @@ -7,6 +7,7 @@ */ #include +#include #include #include @@ -46,7 +47,6 @@ #include "node_composite_util.hh" #define MAX_GLARE_ITERATIONS 5 -#define MAX_GLARE_SIZE 9 namespace blender::nodes::node_composite_glare_cc { @@ -57,7 +57,67 @@ static void cmp_node_glare_declare(NodeDeclarationBuilder &b) b.add_input("Image") .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .compositor_domain_priority(0); - b.add_output("Image"); + b.add_input("Threshold") + .default_value(1.0f) + .min(0.0f) + .description( + "Defines the luminance at which pixels start to be considered part of the highlights " + "that will produce a glare") + .compositor_expects_single_value(); + b.add_input("Strength") + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description("The strength of the glare that will be added to the image") + .compositor_expects_single_value(); + b.add_input("Size") + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description( + "The size of the glare relative to the image. 1 means the glare covers the entire " + "image, 0.5 means the glare covers half the image, and so on") + .compositor_expects_single_value(); + b.add_input("Streaks") + .default_value(4) + .min(1) + .max(16) + .description("The number of streaks") + .compositor_expects_single_value(); + b.add_input("Streaks Angle") + .default_value(0.0f) + .subtype(PROP_ANGLE) + .description("The angle that the first streak makes with the horizontal axis") + .compositor_expects_single_value(); + b.add_input("Iterations") + .default_value(3) + .min(2) + .max(5) + .description( + "The number of ghosts for Ghost glare or the spread of Glare for Streaks and Simple " + "Star") + .compositor_expects_single_value(); + b.add_input("Fade") + .default_value(0.9f) + .min(0.75f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description("Streak fade-out factor") + .compositor_expects_single_value(); + b.add_input("Color Modulation") + .default_value(0.25) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description("Modulates colors of streaks and ghosts for a spectral dispersion effect") + .compositor_expects_single_value(); + + b.add_output("Image").description("The image with the generated glare added"); + b.add_output("Glare").description("The generated glare"); + b.add_output("Highlights") + .description("The extracted highlights from which the glare was generated"); } static void node_composit_init_glare(bNodeTree * /*ntree*/, bNode *node) @@ -65,15 +125,7 @@ static void node_composit_init_glare(bNodeTree * /*ntree*/, bNode *node) NodeGlare *ndg = MEM_cnew(__func__); ndg->quality = 1; ndg->type = CMP_NODE_GLARE_STREAKS; - ndg->iter = 3; - ndg->colmod = 0.25; - ndg->mix = 0; - ndg->threshold = 1; ndg->star_45 = true; - ndg->streaks = 4; - ndg->angle_ofs = 0.0f; - ndg->fade = 0.9; - ndg->size = 8; node->storage = ndg; } @@ -89,43 +141,42 @@ static void node_composit_buts_glare(uiLayout *layout, bContext * /*C*/, Pointer uiItemR(layout, ptr, "glare_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); uiItemR(layout, ptr, "quality", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); - if (ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)) { - uiItemR(layout, ptr, "iterations", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - - if (ELEM(glare_type, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)) { - uiItemR(layout, - ptr, - "color_modulation", - UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, - std::nullopt, - ICON_NONE); - } - - uiItemR(layout, ptr, "mix", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - uiItemR(layout, ptr, "threshold", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - if (glare_type == CMP_NODE_GLARE_STREAKS) { - uiItemR(layout, ptr, "streaks", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - uiItemR(layout, ptr, "angle_offset", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - - if (ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_STREAKS)) { - uiItemR(layout, - ptr, - "fade", - UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, - std::nullopt, - ICON_NONE); - } - if (glare_type == CMP_NODE_GLARE_SIMPLE_STAR) { uiItemR(layout, ptr, "use_rotate_45", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); } +} - if (ELEM(glare_type, CMP_NODE_GLARE_FOG_GLOW, CMP_NODE_GLARE_BLOOM)) { - uiItemR(layout, ptr, "size", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } +static void node_update(bNodeTree *ntree, bNode *node) +{ + const CMPNodeGlareType glare_type = static_cast(node_storage(*node).type); + + bNodeSocket *size_input = bke::node_find_socket(node, SOCK_IN, "Size"); + blender::bke::node_set_socket_availability( + ntree, size_input, ELEM(glare_type, CMP_NODE_GLARE_FOG_GLOW, CMP_NODE_GLARE_BLOOM)); + + bNodeSocket *iterations_input = bke::node_find_socket(node, SOCK_IN, "Iterations"); + blender::bke::node_set_socket_availability( + ntree, + iterations_input, + ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)); + + bNodeSocket *fade_input = bke::node_find_socket(node, SOCK_IN, "Fade"); + blender::bke::node_set_socket_availability( + ntree, fade_input, ELEM(glare_type, CMP_NODE_GLARE_SIMPLE_STAR, CMP_NODE_GLARE_STREAKS)); + + bNodeSocket *color_modulation_input = bke::node_find_socket(node, SOCK_IN, "Color Modulation"); + blender::bke::node_set_socket_availability( + ntree, + color_modulation_input, + ELEM(glare_type, CMP_NODE_GLARE_GHOST, CMP_NODE_GLARE_STREAKS)); + + bNodeSocket *streaks_input = bke::node_find_socket(node, SOCK_IN, "Streaks"); + blender::bke::node_set_socket_availability( + ntree, streaks_input, glare_type == CMP_NODE_GLARE_STREAKS); + + bNodeSocket *streaks_angle_input = bke::node_find_socket(node, SOCK_IN, "Streaks Angle"); + blender::bke::node_set_socket_availability( + ntree, streaks_angle_input, glare_type == CMP_NODE_GLARE_STREAKS); } using namespace blender::compositor; @@ -136,57 +187,54 @@ class GlareOperation : public NodeOperation { void execute() override { - if (is_identity()) { - get_input("Image").pass_through(get_result("Image")); + Result &image_input = this->get_input("Image"); + Result &image_output = this->get_result("Image"); + Result &glare_output = this->get_result("Glare"); + Result &highlights_output = this->get_result("Highlights"); + + if (image_input.is_single_value()) { + if (image_output.should_compute()) { + image_input.pass_through(image_output); + } + if (glare_output.should_compute()) { + glare_output.allocate_invalid(); + } + if (highlights_output.should_compute()) { + highlights_output.allocate_invalid(); + } return; } - Result highlights_result = execute_highlights(); - Result glare_result = execute_glare(highlights_result); - highlights_result.release(); - execute_mix(glare_result); - glare_result.release(); - } + Result highlights = this->compute_highlights(); + Result glare = this->compute_glare(highlights); - bool is_identity() - { - if (get_input("Image").is_single_value()) { - return true; + if (highlights_output.should_compute()) { + if (highlights.domain().size != image_input.domain().size) { + /* The highlights were computed on a fraction of the image size, see the get_quality_factor + * method. So we need to upsample them while writing as opposed to just stealing the + * existing data. */ + this->write_highlights_output(highlights); + } + else { + highlights_output.steal_data(highlights); + } } + highlights.release(); - /* A mix factor of -1 indicates that the original image is returned as is. See the execute_mix - * method for more information. */ - if (node_storage(bnode()).mix == -1.0f) { - return true; - } - - return false; - } - - Result execute_glare(Result &highlights_result) - { - switch (node_storage(bnode()).type) { - case CMP_NODE_GLARE_SIMPLE_STAR: - return execute_simple_star(highlights_result); - case CMP_NODE_GLARE_FOG_GLOW: - return execute_fog_glow(highlights_result); - case CMP_NODE_GLARE_STREAKS: - return execute_streaks(highlights_result); - case CMP_NODE_GLARE_GHOST: - return execute_ghost(highlights_result); - case CMP_NODE_GLARE_BLOOM: - return execute_bloom(highlights_result); - default: - BLI_assert_unreachable(); - return context().create_result(ResultType::Color); + /* Combine the original input and the generated glare. */ + execute_mix(glare); + + if (glare_output.should_compute()) { + this->write_glare_output(glare); } + glare.release(); } /* ----------------- * Glare Highlights. * ----------------- */ - Result execute_highlights() + Result compute_highlights() { if (this->context().use_gpu()) { return this->execute_highlights_gpu(); @@ -199,18 +247,18 @@ class GlareOperation : public NodeOperation { GPUShader *shader = context().get_shader("compositor_glare_highlights"); GPU_shader_bind(shader); - GPU_shader_uniform_1f(shader, "threshold", node_storage(bnode()).threshold); + GPU_shader_uniform_1f(shader, "threshold", this->get_threshold()); const Result &input_image = get_input("Image"); GPU_texture_filter_mode(input_image, true); input_image.bind_as_texture(shader, "input_tx"); - const int2 glare_size = get_glare_size(); + const int2 highlights_size = get_highlights_size(); Result highlights_result = context().create_result(ResultType::Color); - highlights_result.allocate_texture(glare_size); + highlights_result.allocate_texture(highlights_size); highlights_result.bind_as_image(shader, "output_img"); - compute_dispatch_threads_at_least(shader, glare_size); + compute_dispatch_threads_at_least(shader, highlights_size); GPU_shader_unbind(); input_image.unbind_as_texture(); @@ -221,21 +269,21 @@ class GlareOperation : public NodeOperation { Result execute_highlights_cpu() { - const float threshold = node_storage(bnode()).threshold; + const float threshold = this->get_threshold(); const Result &input = get_input("Image"); - const int2 glare_size = this->get_glare_size(); + const int2 highlights_size = this->get_highlights_size(); Result output = context().create_result(ResultType::Color); - output.allocate_texture(glare_size); + output.allocate_texture(highlights_size); /* The dispatch domain covers the output image size, which might be a fraction of the input * image size, so you will notice the glare size used throughout the code instead of the input * one. */ - parallel_for(glare_size, [&](const int2 texel) { + parallel_for(highlights_size, [&](const int2 texel) { /* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the image * size to get the coordinates into the sampler's expected [0, 1] range. */ - float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(glare_size); + float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(highlights_size); float4 hsva; rgb_to_hsv_v(input.sample_bilinear_extended(normalized_coordinates), hsva); @@ -254,6 +302,95 @@ class GlareOperation : public NodeOperation { return output; } + /* As a performance optimization, the operation can compute the glare on a fraction of the input + * image size, so we extract the highlights to a smaller result, whose size is returned by this + * method. */ + int2 get_highlights_size() + { + return this->compute_domain().size / this->get_quality_factor(); + } + + /* Writes the given input highlights by upsampling it using bilinear interpolation to match the + * size of the original input, allocating the highlights output and writing the result to it. */ + void write_highlights_output(const Result &highlights) + { + if (this->context().use_gpu()) { + this->write_highlights_output_gpu(highlights); + } + else { + this->write_highlights_output_cpu(highlights); + } + } + + void write_highlights_output_gpu(const Result &highlights) + { + GPUShader *shader = this->context().get_shader("compositor_glare_write_highlights_output"); + GPU_shader_bind(shader); + + GPU_texture_filter_mode(highlights, true); + GPU_texture_extend_mode(highlights, GPU_SAMPLER_EXTEND_MODE_EXTEND); + highlights.bind_as_texture(shader, "input_tx"); + + const Result &image_input = this->get_input("Image"); + Result &output = this->get_result("Highlights"); + output.allocate_texture(image_input.domain()); + output.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, output.domain().size); + + GPU_shader_unbind(); + output.unbind_as_image(); + highlights.unbind_as_texture(); + } + + void write_highlights_output_cpu(const Result &highlights) + { + const Result &image_input = this->get_input("Image"); + Result &output = this->get_result("Highlights"); + output.allocate_texture(image_input.domain()); + + const int2 size = output.domain().size; + parallel_for(size, [&](const int2 texel) { + float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(size); + output.store_pixel(texel, highlights.sample_bilinear_extended(normalized_coordinates)); + }); + } + + /* ------ + * Glare. + * ------ */ + + Result compute_glare(Result &highlights_result) + { + if (!this->should_compute_glare()) { + return this->context().create_result(ResultType::Color); + } + + switch (node_storage(bnode()).type) { + case CMP_NODE_GLARE_SIMPLE_STAR: + return this->execute_simple_star(highlights_result); + case CMP_NODE_GLARE_FOG_GLOW: + return this->execute_fog_glow(highlights_result); + case CMP_NODE_GLARE_STREAKS: + return this->execute_streaks(highlights_result); + case CMP_NODE_GLARE_GHOST: + return this->execute_ghost(highlights_result); + case CMP_NODE_GLARE_BLOOM: + return this->execute_bloom(highlights_result); + default: + BLI_assert_unreachable(); + return this->context().create_result(ResultType::Color); + } + } + + /* Glare should be computed either because the glare output is needed directly or the image + * output is needed. */ + bool should_compute_glare() + { + return this->get_result("Glare").should_compute() || + this->get_result("Image").should_compute(); + } + /* ------------------ * Simple Star Glare. * ------------------ */ @@ -289,7 +426,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result vertical_pass_result = context().create_result(ResultType::Color); vertical_pass_result.allocate_texture(size); GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); @@ -299,7 +436,7 @@ class GlareOperation : public NodeOperation { GPU_shader_bind(shader); GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations()); - GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade); + GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade()); horizontal_pass_result.bind_as_texture(shader, "horizontal_tx"); @@ -321,7 +458,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result output = this->context().create_result(ResultType::Color); output.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -329,7 +466,7 @@ class GlareOperation : public NodeOperation { }); const int iterations = this->get_number_of_iterations(); - const float fade_factor = node_storage(this->bnode()).fade; + const float fade_factor = this->get_fade(); /* Dispatch a thread for each column in the image. */ const int width = size.x; @@ -378,7 +515,8 @@ class GlareOperation : public NodeOperation { int2 texel = int2(x, y); float4 horizontal = horizontal_pass_result.load_pixel(texel); float4 vertical = output.load_pixel(texel); - output.store_pixel(texel, horizontal + vertical); + float4 combined = horizontal + vertical; + output.store_pixel(texel, float4(combined.xyz(), 1.0f)); } } }); @@ -398,7 +536,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result horizontal_pass_result = context().create_result(ResultType::Color); horizontal_pass_result.allocate_texture(size); GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); @@ -408,7 +546,7 @@ class GlareOperation : public NodeOperation { GPU_shader_bind(shader); GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations()); - GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade); + GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade()); horizontal_pass_result.bind_as_image(shader, "horizontal_img"); @@ -425,7 +563,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result horizontal_pass_result = context().create_result(ResultType::Color); horizontal_pass_result.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -433,7 +571,7 @@ class GlareOperation : public NodeOperation { }); const int iterations = this->get_number_of_iterations(); - const float fade_factor = node_storage(this->bnode()).fade; + const float fade_factor = this->get_fade(); /* Dispatch a thread for each row in the image. */ const int width = size.x; @@ -504,9 +642,9 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 glare_size = get_glare_size(); + const int2 size = highlights.domain().size; Result anti_diagonal_pass_result = context().create_result(ResultType::Color); - anti_diagonal_pass_result.allocate_texture(glare_size); + anti_diagonal_pass_result.allocate_texture(size); GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); GPU_texture_copy(anti_diagonal_pass_result, highlights); @@ -514,14 +652,14 @@ class GlareOperation : public NodeOperation { GPU_shader_bind(shader); GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations()); - GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade); + GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade()); diagonal_pass_result.bind_as_texture(shader, "diagonal_tx"); anti_diagonal_pass_result.bind_as_image(shader, "anti_diagonal_img"); /* Dispatch a thread for each diagonal in the image. */ - compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(glare_size), 1)); + compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(size), 1)); diagonal_pass_result.unbind_as_texture(); anti_diagonal_pass_result.unbind_as_image(); @@ -535,7 +673,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result output = this->context().create_result(ResultType::Color); output.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -543,7 +681,7 @@ class GlareOperation : public NodeOperation { }); const int iterations = this->get_number_of_iterations(); - const float fade_factor = node_storage(this->bnode()).fade; + const float fade_factor = this->get_fade(); /* Dispatch a thread for each diagonal in the image. */ const int diagonals_count = compute_number_of_diagonals(size); @@ -595,7 +733,8 @@ class GlareOperation : public NodeOperation { int2 texel = start + j * direction; float4 horizontal = diagonal_pass_result.load_pixel(texel); float4 vertical = output.load_pixel(texel); - output.store_pixel(texel, horizontal + vertical); + float4 combined = horizontal + vertical; + output.store_pixel(texel, float4(combined.xyz(), 1.0f)); } } }); @@ -615,9 +754,9 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 glare_size = get_glare_size(); + const int2 size = highlights.domain().size; Result diagonal_pass_result = context().create_result(ResultType::Color); - diagonal_pass_result.allocate_texture(glare_size); + diagonal_pass_result.allocate_texture(size); GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); GPU_texture_copy(diagonal_pass_result, highlights); @@ -625,12 +764,12 @@ class GlareOperation : public NodeOperation { GPU_shader_bind(shader); GPU_shader_uniform_1i(shader, "iterations", get_number_of_iterations()); - GPU_shader_uniform_1f(shader, "fade_factor", node_storage(bnode()).fade); + GPU_shader_uniform_1f(shader, "fade_factor", this->get_fade()); diagonal_pass_result.bind_as_image(shader, "diagonal_img"); /* Dispatch a thread for each diagonal in the image. */ - compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(glare_size), 1)); + compute_dispatch_threads_at_least(shader, int2(compute_number_of_diagonals(size), 1)); diagonal_pass_result.unbind_as_image(); GPU_shader_unbind(); @@ -642,7 +781,7 @@ class GlareOperation : public NodeOperation { { /* First, copy the highlights result to the output since we will be doing the computation * in-place. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result diagonal_pass_result = this->context().create_result(ResultType::Color); diagonal_pass_result.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -650,7 +789,7 @@ class GlareOperation : public NodeOperation { }); const int iterations = this->get_number_of_iterations(); - const float fade_factor = node_storage(this->bnode()).fade; + const float fade_factor = this->get_fade(); /* Dispatch a thread for each diagonal in the image. */ const int diagonals_count = compute_number_of_diagonals(size); @@ -712,7 +851,7 @@ class GlareOperation : public NodeOperation { Result execute_streaks(const Result &highlights) { /* Create an initially zero image where streaks will be accumulated. */ - const int2 size = get_glare_size(); + const int2 size = highlights.domain().size; Result accumulated_streaks_result = context().create_result(ResultType::Color); accumulated_streaks_result.allocate_texture(size); if (this->context().use_gpu()) { @@ -752,14 +891,14 @@ class GlareOperation : public NodeOperation { /* Copy the highlights result into a new result because the output will be copied to the input * after each iteration. */ - const int2 glare_size = get_glare_size(); + const int2 size = highlights.domain().size; Result input_streak_result = context().create_result(ResultType::Color); - input_streak_result.allocate_texture(glare_size); + input_streak_result.allocate_texture(size); GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); GPU_texture_copy(input_streak_result, highlights); Result output_streak_result = context().create_result(ResultType::Color); - output_streak_result.allocate_texture(glare_size); + output_streak_result.allocate_texture(size); /* For the given number of iterations, apply the streak filter in the given direction. The * result of the previous iteration is used as the input of the current iteration. */ @@ -780,7 +919,7 @@ class GlareOperation : public NodeOperation { output_streak_result.bind_as_image(shader, "output_streak_img"); - compute_dispatch_threads_at_least(shader, glare_size); + compute_dispatch_threads_at_least(shader, size); input_streak_result.unbind_as_texture(); output_streak_result.unbind_as_image(); @@ -804,7 +943,7 @@ class GlareOperation : public NodeOperation { { /* Copy the highlights result into a new result because the output will be copied to the input * after each iteration. */ - const int2 size = this->get_glare_size(); + const int2 size = highlights.domain().size; Result input = this->context().create_result(ResultType::Color); input.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -894,8 +1033,7 @@ class GlareOperation : public NodeOperation { streak_result.bind_as_texture(shader, "streak_tx"); accumulated_streaks_result.bind_as_image(shader, "accumulated_streaks_img", true); - const int2 glare_size = get_glare_size(); - compute_dispatch_threads_at_least(shader, glare_size); + compute_dispatch_threads_at_least(shader, streak_result.domain().size); streak_result.unbind_as_texture(); accumulated_streaks_result.unbind_as_image(); @@ -906,11 +1044,12 @@ class GlareOperation : public NodeOperation { { const float attenuation_factor = this->compute_streak_attenuation_factor(); - const int2 size = get_glare_size(); + const int2 size = streak.domain().size; parallel_for(size, [&](const int2 texel) { float4 attenuated_streak = streak.load_pixel(texel) * attenuation_factor; float4 current_accumulated_streaks = accumulated_streaks.load_pixel(texel); - accumulated_streaks.store_pixel(texel, current_accumulated_streaks + attenuated_streak); + float4 combined_streaks = current_accumulated_streaks + attenuated_streak; + accumulated_streaks.store_pixel(texel, float4(combined_streaks.xyz(), 1.0f)); }); } @@ -932,7 +1071,7 @@ class GlareOperation : public NodeOperation { float2 compute_streak_direction(int streak_index) { const int number_of_streaks = get_number_of_streaks(); - const float start_angle = get_streaks_start_angle(); + const float start_angle = this->get_streaks_angle(); const float angle = start_angle + (float(streak_index) / number_of_streaks) * (M_PI * 2.0f); return float2(math::cos(angle), math::sin(angle)); } @@ -948,7 +1087,7 @@ class GlareOperation : public NodeOperation { * one makes sure the power starts at one. */ float compute_streak_color_modulator(int iteration) { - return 1.0f - std::pow(get_color_modulation_factor(), iteration + 1); + return 1.0f - std::pow(this->get_color_modulation(), iteration + 1); } /* Streaks are computed by iteratively applying a filter that samples 3 neighboring pixels in the @@ -964,7 +1103,7 @@ class GlareOperation : public NodeOperation { * fade factors for those farther neighbors. */ float3 compute_streak_fade_factors(float iteration_magnitude) { - const float fade_factor = std::pow(node_storage(bnode()).fade, iteration_magnitude); + const float fade_factor = std::pow(this->get_fade(), iteration_magnitude); return float3(fade_factor, std::pow(fade_factor, 2.0f), std::pow(fade_factor, 3.0f)); } @@ -979,14 +1118,14 @@ class GlareOperation : public NodeOperation { return std::pow(4.0f, iteration); } - float get_streaks_start_angle() - { - return node_storage(bnode()).angle_ofs; - } - int get_number_of_streaks() { - return node_storage(bnode()).streaks; + return math::clamp(this->get_input("Streaks").get_single_value_default(4), 1, 16); + } + + float get_streaks_angle() + { + return this->get_input("Streaks Angle").get_single_value_default(0.0f); } /* ------------ @@ -1022,14 +1161,14 @@ class GlareOperation : public NodeOperation { /* Zero initialize output image where ghosts will be accumulated. */ const float4 zero_color = float4(0.0f); - const int2 glare_size = get_glare_size(); - accumulated_ghosts_result.allocate_texture(glare_size); + const int2 size = base_ghost_result.domain().size; + accumulated_ghosts_result.allocate_texture(size); GPU_texture_clear(accumulated_ghosts_result, GPU_DATA_FLOAT, zero_color); /* Copy the highlights result into a new result because the output will be copied to the input * after each iteration. */ Result input_ghost_result = context().create_result(ResultType::Color); - input_ghost_result.allocate_texture(glare_size); + input_ghost_result.allocate_texture(size); GPU_texture_copy(input_ghost_result, base_ghost_result); /* For the given number of iterations, accumulate four ghosts with different scales and color @@ -1044,7 +1183,7 @@ class GlareOperation : public NodeOperation { input_ghost_result.bind_as_texture(shader, "input_ghost_tx"); accumulated_ghosts_result.bind_as_image(shader, "accumulated_ghost_img", true); - compute_dispatch_threads_at_least(shader, glare_size); + compute_dispatch_threads_at_least(shader, size); input_ghost_result.unbind_as_texture(); accumulated_ghosts_result.unbind_as_image(); @@ -1068,7 +1207,7 @@ class GlareOperation : public NodeOperation { std::array color_modulators = this->compute_ghost_color_modulators(); /* Zero initialize output image where ghosts will be accumulated. */ - const int2 size = get_glare_size(); + const int2 size = base_ghost.domain().size; accumulated_ghosts_result.allocate_texture(size); parallel_for(size, [&](const int2 texel) { accumulated_ghosts_result.store_pixel(texel, float4(0.0f)); @@ -1119,8 +1258,8 @@ class GlareOperation : public NodeOperation { } float4 current_accumulated_ghost = accumulated_ghosts_result.load_pixel(texel); - accumulated_ghosts_result.store_pixel(texel, - current_accumulated_ghost + accumulated_ghost); + float4 combined_ghost = current_accumulated_ghost + accumulated_ghost; + accumulated_ghosts_result.store_pixel(texel, float4(combined_ghost.xyz(), 1.0f)); }); /* The accumulated result serves as the input for the next iteration, so copy the result to @@ -1186,11 +1325,10 @@ class GlareOperation : public NodeOperation { GPU_texture_extend_mode(big_ghost_result, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); big_ghost_result.bind_as_texture(shader, "big_ghost_tx"); - const int2 glare_size = get_glare_size(); - base_ghost_result.allocate_texture(glare_size); + base_ghost_result.allocate_texture(small_ghost_result.domain()); base_ghost_result.bind_as_image(shader, "combined_ghost_img"); - compute_dispatch_threads_at_least(shader, glare_size); + compute_dispatch_threads_at_least(shader, base_ghost_result.domain().size); GPU_shader_unbind(); small_ghost_result.unbind_as_texture(); @@ -1202,7 +1340,7 @@ class GlareOperation : public NodeOperation { const Result &big_ghost_result, Result &combined_ghost) { - const int2 size = get_glare_size(); + const int2 size = small_ghost_result.domain().size; combined_ghost.allocate_texture(size); parallel_for(size, [&](const int2 texel) { @@ -1320,7 +1458,7 @@ class GlareOperation : public NodeOperation { * subtract from one. */ float get_ghost_color_modulation_factor() { - return 1.0f - get_color_modulation_factor(); + return 1.0f - this->get_color_modulation(); } /* ------------ @@ -1357,11 +1495,9 @@ class GlareOperation : public NodeOperation { * smaller dimension of the size of the highlights. * * However, as users might want a smaller glare size, we reduce the chain length by the - * halving count supplied by the user. */ - const int2 glare_size = get_glare_size(); - const int smaller_glare_dimension = math::min(glare_size.x, glare_size.y); - const int chain_length = int(std::log2(smaller_glare_dimension)) - - compute_bloom_size_halving_count(); + * size supplied by the user. */ + const int smaller_dimension = math::reduce_min(highlights.domain().size); + const int chain_length = int(std::log2(smaller_dimension * this->get_size())); /* If the chain length is less than 2, that means no down-sampling will happen, so we just * return a copy of the highlights. This is a sanitization of a corner case, so no need to @@ -1460,7 +1596,8 @@ class GlareOperation : public NodeOperation { upsampled += (1.0f / 16.0f) * input.sample_bilinear_extended(coordinates + pixel_size * float2(1.0f, 1.0f)); - output.store_pixel(texel, output.load_pixel(texel) + upsampled); + float4 combined = output.load_pixel(texel) + upsampled; + output.store_pixel(texel, float4(combined.xyz(), 1.0f)); }); } @@ -1660,21 +1797,6 @@ class GlareOperation : public NodeOperation { math::safe_rcp(math::reduce_add(weights)); } - /* The bloom has a maximum possible size when the bloom size is equal to MAX_GLARE_SIZE and - * halves for every unit decrement of the bloom size. This method computes the number of halving - * that should take place, which is simply the difference to MAX_GLARE_SIZE. */ - int compute_bloom_size_halving_count() - { - return MAX_GLARE_SIZE - get_bloom_size(); - } - - /* The size of the bloom relative to its maximum possible size, see the - * compute_bloom_size_halving_count() method for more information. */ - int get_bloom_size() - { - return node_storage(bnode()).size; - } - /* --------------- * Fog Glow Glare. * --------------- */ @@ -1684,7 +1806,7 @@ class GlareOperation : public NodeOperation { #if defined(WITH_FFTW3) fftw::initialize_float(); - const int kernel_size = compute_fog_glow_kernel_size(); + const int kernel_size = compute_fog_glow_kernel_size(highlights); /* Since we will be doing a circular convolution, we need to zero pad our input image by half * the kernel size to avoid the kernel affecting the pixels at the other side of image. @@ -1850,11 +1972,11 @@ class GlareOperation : public NodeOperation { /* Computes the size of the fog glow kernel that will be convolved with the image, which is * essentially the extent of the glare in pixels. */ - int compute_fog_glow_kernel_size() + int compute_fog_glow_kernel_size(const Result &highlights) { - /* We use an odd sized kernel since an even one will typically introduce a tiny offset as it - * has no exact center value. */ - return (1 << node_storage(bnode()).size) + 1; + /* make sure the kernel size is odd since an even one will typically introduce a tiny offset as + * it has no exact center value. */ + return (int(math::reduce_max(highlights.domain().size) * this->get_size()) / 2) * 2 + 1; } /* ---------- @@ -1863,6 +1985,17 @@ class GlareOperation : public NodeOperation { void execute_mix(const Result &glare_result) { + Result &image_output = this->get_result("Image"); + if (!image_output.should_compute()) { + return; + } + + Result &image_input = this->get_input("Image"); + if (this->get_strength() == 0.0f) { + image_input.pass_through(image_output); + return; + } + if (this->context().use_gpu()) { this->execute_mix_gpu(glare_result); } @@ -1876,7 +2009,7 @@ class GlareOperation : public NodeOperation { GPUShader *shader = context().get_shader("compositor_glare_mix"); GPU_shader_bind(shader); - GPU_shader_uniform_1f(shader, "mix_factor", node_storage(bnode()).mix); + GPU_shader_uniform_1f(shader, "strength", this->get_strength()); const Result &input_image = get_input("Image"); input_image.bind_as_texture(shader, "input_tx"); @@ -1899,7 +2032,7 @@ class GlareOperation : public NodeOperation { void execute_mix_cpu(const Result &glare_result) { - const float mix_factor = node_storage(bnode()).mix; + const float strength = this->get_strength(); const Result &input = get_input("Image"); @@ -1910,46 +2043,104 @@ class GlareOperation : public NodeOperation { parallel_for(domain.size, [&](const int2 texel) { /* Add 0.5 to evaluate the input sampler at the center of the pixel and divide by the input * image size to get the relevant coordinates into the sampler's expected [0, 1] range. - * Make sure the input color is not negative to avoid a subtractive effect when mixing the - * glare. - */ + * Make sure the input color is not negative to avoid a subtractive effect when adding the + * glare. */ float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(input.domain().size); float4 glare_color = glare_result.sample_bilinear_extended(normalized_coordinates); float4 input_color = math::max(float4(0.0f), input.load_pixel(texel)); - /* The mix factor is in the range [-1, 1] and linearly interpolate between the three values - * such that: 1 => Glare only. 0 => Input + Glare. -1 => Input only. We implement that as a - * weighted sum as follows. When the mix factor is 1, the glare weight should be 1 and the - * input weight should be 0. When the mix factor is -1, the glare weight should be 0 and - * the input weight should be 1. When the mix factor is 0, both weights should be 1. This - * can be expressed using the following compact min max expressions. */ - float input_weight = 1.0f - math::max(0.0f, mix_factor); - float glare_weight = 1.0f + math::min(0.0f, mix_factor); - float3 highlights = input_weight * input_color.xyz() + glare_weight * glare_color.xyz(); + float3 highlights = input_color.xyz() + glare_color.xyz() * strength; output.store_pixel(texel, float4(highlights, input_color.w)); }); } + /* Writes the given input glare by adjusting it as needed and upsampling it using bilinear + * interpolation to match the size of the original input, allocating the glare output and writing + * the result to it. */ + void write_glare_output(const Result &glare) + { + if (this->context().use_gpu()) { + this->write_glare_output_gpu(glare); + } + else { + this->write_glare_output_cpu(glare); + } + } + + void write_glare_output_gpu(const Result &glare) + { + GPUShader *shader = this->context().get_shader("compositor_glare_write_glare_output"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1f(shader, "strength", this->get_strength()); + + GPU_texture_filter_mode(glare, true); + GPU_texture_extend_mode(glare, GPU_SAMPLER_EXTEND_MODE_EXTEND); + glare.bind_as_texture(shader, "input_tx"); + + const Result &image_input = this->get_input("Image"); + Result &output = this->get_result("Glare"); + output.allocate_texture(image_input.domain()); + output.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, output.domain().size); + + GPU_shader_unbind(); + output.unbind_as_image(); + glare.unbind_as_texture(); + } + + void write_glare_output_cpu(const Result &glare) + { + const float strength = this->get_strength(); + + const Result &image_input = this->get_input("Image"); + Result &output = this->get_result("Glare"); + output.allocate_texture(image_input.domain()); + + const int2 size = output.domain().size; + parallel_for(size, [&](const int2 texel) { + float2 normalized_coordinates = (float2(texel) + float2(0.5f)) / float2(size); + float4 glare_value = glare.sample_bilinear_extended(normalized_coordinates); + float4 adjusted_glare_value = glare_value * strength; + output.store_pixel(texel, float4(adjusted_glare_value.xyz(), 1.0f)); + }); + } + /* ------- * Common. * ------- */ - /* As a performance optimization, the operation can compute the glare on a fraction of the input - * image size, which is what this method returns. */ - int2 get_glare_size() + float get_threshold() { - return compute_domain().size / get_quality_factor(); + return math::max(0.0f, this->get_input("Threshold").get_single_value_default(1.0f)); + } + + float get_strength() + { + return math::max(0.0f, this->get_input("Strength").get_single_value_default(1.0f)); + } + + float get_size() + { + return math::clamp(this->get_input("Size").get_single_value_default(0.5f), 0.0f, 1.0f); } int get_number_of_iterations() { - return node_storage(bnode()).iter; + return math::clamp(this->get_input("Iterations").get_single_value_default(3), 2, 5); } - float get_color_modulation_factor() + float get_fade() { - return node_storage(bnode()).colmod; + return math::clamp(this->get_input("Fade").get_single_value_default(0.9f), 0.75f, 1.0f); + } + + float get_color_modulation() + { + return math::clamp( + this->get_input("Color Modulation").get_single_value_default(0.25f), 0.0f, 1.0f); } /* The glare node can compute the glare on a fraction of the input image size to improve @@ -1985,6 +2176,7 @@ void register_node_type_cmp_glare() cmp_node_type_base(&ntype, CMP_NODE_GLARE, "Glare", NODE_CLASS_OP_FILTER); ntype.enum_name_legacy = "GLARE"; ntype.declare = file_ns::cmp_node_glare_declare; + ntype.updatefunc = file_ns::node_update; ntype.draw_buttons = file_ns::node_composit_buts_glare; ntype.initfunc = file_ns::node_composit_init_glare; blender::bke::node_type_storage(