Merge branch 'refactor-mesh-position-generic' into refactor-mesh-corners-generic
This commit is contained in:
commit
1f13b64330
|
@ -1061,16 +1061,11 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
|||
|
||||
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
|
||||
if (window->getCursorGrabModeIsWarp()) {
|
||||
static uint64_t last_warp_time = 0;
|
||||
{
|
||||
/* WORKAROUND: Check the mouse event timestamp so we can ignore mouse-move events that were
|
||||
* already in the queue before we changed the cursor position. */
|
||||
MOUSEMOVEPOINT mp = {x_screen, y_screen};
|
||||
::GetMouseMovePointsEx(sizeof(MOUSEMOVEPOINT), &mp, &mp, 1, GMMP_USE_DISPLAY_POINTS);
|
||||
if (mp.time <= last_warp_time) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
/* WORKAROUND:
|
||||
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
|
||||
* outdated. Identify these cases by checking if the cursor is not yet within bounds. */
|
||||
static bool is_warping_x = false;
|
||||
static bool is_warping_y = false;
|
||||
|
||||
int32_t x_new = x_screen;
|
||||
int32_t y_new = y_screen;
|
||||
|
@ -1117,35 +1112,31 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
|
|||
|
||||
window->getCursorGrabAccum(x_accum, y_accum);
|
||||
if (x_new != x_screen || y_new != y_screen) {
|
||||
/* WORKAROUND: Store the current time so that we ignore outdated mouse-move events. */
|
||||
last_warp_time = ::GetTickCount64();
|
||||
system->setCursorPosition(x_new, y_new); /* wrap */
|
||||
|
||||
/* For more control over which timestamp to store in the event, we use `SendInput` instead of
|
||||
* `SetCursorPos` here.
|
||||
* It is quite unlikely to happen, but still possible that some event between
|
||||
* `last_warp_time` and `GHOST_SystemWin32::setCursorPosition` is sent. */
|
||||
INPUT input[3] = {0};
|
||||
input[0].type = INPUT_MOUSE;
|
||||
input[0].mi.dx = (LONG)(x_new * (65535.0f / GetSystemMetrics(SM_CXSCREEN)));
|
||||
input[0].mi.dy = (LONG)(y_new * (65535.0f / GetSystemMetrics(SM_CYSCREEN)));
|
||||
input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
|
||||
input[0].mi.time = last_warp_time;
|
||||
/* Do not update the accum values if we are an outdated or failed pos-warp event. */
|
||||
if (!is_warping_x) {
|
||||
is_warping_x = x_new != x_screen;
|
||||
if (is_warping_x) {
|
||||
x_accum += (x_screen - x_new);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send 3 events with a jitter to make sure Windows does not occasionally and
|
||||
* inexplicably ignore `SetCursorPos` or `SendInput`. */
|
||||
input[2] = input[1] = input[0];
|
||||
input[1].mi.dx += 1;
|
||||
::SendInput(3, input, sizeof(INPUT));
|
||||
|
||||
x_accum += (x_screen - x_new);
|
||||
y_accum += (y_screen - y_new);
|
||||
if (!is_warping_y) {
|
||||
is_warping_y = y_new != y_screen;
|
||||
if (is_warping_y) {
|
||||
y_accum += (y_screen - y_new);
|
||||
}
|
||||
}
|
||||
window->setCursorGrabAccum(x_accum, y_accum);
|
||||
|
||||
/* When wrapping we don't need to add an event because the `SendInput` call will cause new
|
||||
* events after. */
|
||||
/* When wrapping we don't need to add an event because the setCursorPosition call will cause
|
||||
* a new event after. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
is_warping_x = false;
|
||||
is_warping_y = false;
|
||||
x_screen += x_accum;
|
||||
y_screen += y_accum;
|
||||
}
|
||||
|
|
|
@ -1486,7 +1486,7 @@ AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid)
|
|||
(FileDirEntry *)CTX_data_pointer_get_type(C, "active_file", &RNA_FileSelectEntry).data;
|
||||
if (file && file->asset) {
|
||||
*r_is_valid = true;
|
||||
AssetHandle{file};
|
||||
return AssetHandle{file};
|
||||
}
|
||||
|
||||
*r_is_valid = false;
|
||||
|
|
|
@ -961,6 +961,18 @@ Mesh *BKE_mesh_new_nomain(
|
|||
return mesh;
|
||||
}
|
||||
|
||||
static void copy_attribute_names(const Mesh &mesh_src, Mesh &mesh_dst)
|
||||
{
|
||||
if (mesh_src.active_color_attribute) {
|
||||
MEM_SAFE_FREE(mesh_dst.active_color_attribute);
|
||||
mesh_dst.active_color_attribute = BLI_strdup(mesh_src.active_color_attribute);
|
||||
}
|
||||
if (mesh_src.default_color_attribute) {
|
||||
MEM_SAFE_FREE(mesh_dst.default_color_attribute);
|
||||
mesh_dst.default_color_attribute = BLI_strdup(mesh_src.default_color_attribute);
|
||||
}
|
||||
}
|
||||
|
||||
void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src)
|
||||
{
|
||||
/* Copy general settings. */
|
||||
|
@ -990,6 +1002,7 @@ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src)
|
|||
BLI_assert(me_dst->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE));
|
||||
|
||||
BKE_mesh_copy_parameters(me_dst, me_src);
|
||||
copy_attribute_names(*me_src, *me_dst);
|
||||
|
||||
/* Copy vertex group names. */
|
||||
BLI_assert(BLI_listbase_is_empty(&me_dst->vertex_group_names));
|
||||
|
|
|
@ -2020,8 +2020,15 @@ static void direct_link_id_common(
|
|||
/* When actually reading a file, we do want to reset/re-generate session UUIDS.
|
||||
* In undo case, we want to re-use existing ones. */
|
||||
id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
|
||||
|
||||
/* Runtime IDs should never be written in .blend files (except memfiles from undo). */
|
||||
BLI_assert((id->tag & LIB_TAG_RUNTIME) == 0);
|
||||
}
|
||||
|
||||
/* No-main and other types of special IDs should never be written in .blend files. */
|
||||
BLI_assert((id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT | LIB_TAG_NOT_ALLOCATED)) ==
|
||||
0);
|
||||
|
||||
if ((tag & LIB_TAG_TEMP_MAIN) == 0) {
|
||||
BKE_lib_libblock_session_uuid_ensure(id);
|
||||
}
|
||||
|
@ -2034,7 +2041,12 @@ static void direct_link_id_common(
|
|||
id->py_instance = nullptr;
|
||||
|
||||
/* Initialize with provided tag. */
|
||||
id->tag = tag;
|
||||
if (BLO_read_data_is_undo(reader)) {
|
||||
id->tag = tag | (id->tag & LIB_TAG_KEEP_ON_UNDO);
|
||||
}
|
||||
else {
|
||||
id->tag = tag;
|
||||
}
|
||||
|
||||
if (ID_IS_LINKED(id)) {
|
||||
id->library_weak_reference = nullptr;
|
||||
|
@ -3105,7 +3117,7 @@ static void read_libblock_undo_restore_identical(
|
|||
BLI_assert(id_old != nullptr);
|
||||
|
||||
/* Some tags need to be preserved here. */
|
||||
id_old->tag = tag | (id_old->tag & LIB_TAG_EXTRAUSER);
|
||||
id_old->tag = tag | (id_old->tag & LIB_TAG_KEEP_ON_UNDO);
|
||||
id_old->lib = main->curlib;
|
||||
id_old->us = ID_FAKE_USERS(id_old);
|
||||
/* Do not reset id->icon_id here, memory allocated for it remains valid. */
|
||||
|
|
|
@ -1097,7 +1097,10 @@ static int write_id_direct_linked_data_process_cb(LibraryIDLinkCallbackData *cb_
|
|||
}
|
||||
BLI_assert(!ID_IS_LINKED(id_self));
|
||||
BLI_assert((cb_flag & IDWALK_CB_INDIRECT_USAGE) == 0);
|
||||
UNUSED_VARS_NDEBUG(id_self);
|
||||
|
||||
if (id_self->tag & LIB_TAG_RUNTIME) {
|
||||
return IDWALK_RET_NOP;
|
||||
}
|
||||
|
||||
if (cb_flag & IDWALK_CB_DIRECT_WEAK_LINK) {
|
||||
id_lib_indirect_weak_link(id);
|
||||
|
@ -1207,12 +1210,18 @@ static bool write_file_handle(Main *mainvar,
|
|||
|
||||
/* We only write unused IDs in undo case.
|
||||
* NOTE: All Scenes, WindowManagers and WorkSpaces should always be written to disk, so
|
||||
* their user-count should never be nullptr currently. */
|
||||
* their user-count should never be zero currently. */
|
||||
if (id->us == 0 && !wd->use_memfile) {
|
||||
BLI_assert(!ELEM(GS(id->name), ID_SCE, ID_WM, ID_WS));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((id->tag & LIB_TAG_RUNTIME) != 0 && !wd->use_memfile) {
|
||||
/* Runtime IDs are never written to .blend files, and they should not influence
|
||||
* (in)direct status of linked IDs they may use. */
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool do_override = !ELEM(override_storage, nullptr, bmain) &&
|
||||
ID_IS_OVERRIDE_LIBRARY_REAL(id);
|
||||
|
||||
|
@ -1253,7 +1262,12 @@ static bool write_file_handle(Main *mainvar,
|
|||
memcpy(id_buffer, id, idtype_struct_size);
|
||||
|
||||
/* Clear runtime data to reduce false detection of changed data in undo/redo context. */
|
||||
((ID *)id_buffer)->tag = 0;
|
||||
if (wd->use_memfile) {
|
||||
((ID *)id_buffer)->tag &= LIB_TAG_KEEP_ON_UNDO;
|
||||
}
|
||||
else {
|
||||
((ID *)id_buffer)->tag = 0;
|
||||
}
|
||||
((ID *)id_buffer)->us = 0;
|
||||
((ID *)id_buffer)->icon_id = 0;
|
||||
/* Those listbase data change every time we add/remove an ID, and also often when
|
||||
|
|
|
@ -106,6 +106,8 @@ set(GLSL_SRC
|
|||
shaders/compositor_glare_simple_star_diagonal_pass.glsl
|
||||
shaders/compositor_glare_simple_star_horizontal_pass.glsl
|
||||
shaders/compositor_glare_simple_star_vertical_pass.glsl
|
||||
shaders/compositor_glare_streaks_accumulate.glsl
|
||||
shaders/compositor_glare_streaks_filter.glsl
|
||||
shaders/compositor_image_crop.glsl
|
||||
shaders/compositor_morphological_distance.glsl
|
||||
shaders/compositor_morphological_distance_feather.glsl
|
||||
|
@ -119,6 +121,7 @@ set(GLSL_SRC
|
|||
shaders/compositor_set_alpha.glsl
|
||||
shaders/compositor_split_viewer.glsl
|
||||
shaders/compositor_symmetric_blur.glsl
|
||||
shaders/compositor_symmetric_blur_variable_size.glsl
|
||||
shaders/compositor_symmetric_separable_blur.glsl
|
||||
shaders/compositor_tone_map_photoreceptor.glsl
|
||||
shaders/compositor_tone_map_simple.glsl
|
||||
|
@ -206,6 +209,7 @@ set(SRC_SHADER_CREATE_INFOS
|
|||
shaders/infos/compositor_set_alpha_info.hh
|
||||
shaders/infos/compositor_split_viewer_info.hh
|
||||
shaders/infos/compositor_symmetric_blur_info.hh
|
||||
shaders/infos/compositor_symmetric_blur_variable_size_info.hh
|
||||
shaders/infos/compositor_symmetric_separable_blur_info.hh
|
||||
shaders/infos/compositor_tone_map_photoreceptor_info.hh
|
||||
shaders/infos/compositor_tone_map_simple_info.hh
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 input_size = texture_size(input_streak_tx);
|
||||
|
||||
/* 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. Similarly, transform the
|
||||
* vector into the sampler's space by dividing by the input size. */
|
||||
vec2 coordinates = (vec2(texel) + vec2(0.5)) / input_size;
|
||||
vec2 vector = streak_vector / input_size;
|
||||
|
||||
/* Load three equally spaced neighbours to the current pixel in the direction of the streak
|
||||
* vector. */
|
||||
vec4 neighbours[3];
|
||||
neighbours[0] = texture(input_streak_tx, coordinates + vector);
|
||||
neighbours[1] = texture(input_streak_tx, coordinates + vector * 2.0);
|
||||
neighbours[2] = texture(input_streak_tx, coordinates + vector * 3.0);
|
||||
|
||||
/* Attenuate the value of two of the channels for each of the neighbours by multiplying by the
|
||||
* color modulator. The particular channels for each neighbour were chosen to be visually similar
|
||||
* to the modulation pattern of chromatic aberration. */
|
||||
neighbours[0].gb *= color_modulator;
|
||||
neighbours[1].rg *= color_modulator;
|
||||
neighbours[2].rb *= color_modulator;
|
||||
|
||||
/* Compute the weighted sum of all neighbours using the given fade factors as weights. The
|
||||
* weights are expected to be lower for neighbours that are further away. */
|
||||
vec4 weighted_neighbours_sum = vec4(0.0);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
weighted_neighbours_sum += fade_factors[i] * neighbours[i];
|
||||
}
|
||||
|
||||
/* The output is the average between the center color and the weighted sum of the neighbours.
|
||||
* Which intuitively mean that highlights will spread in the direction of the streak, which is
|
||||
* the desired result. */
|
||||
vec4 center_color = texture(input_streak_tx, coordinates);
|
||||
vec4 output_color = (center_color + weighted_neighbours_sum) / 2.0;
|
||||
imageStore(output_streak_img, texel, output_color);
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_blur_common.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
/* Loads the input color of the pixel at the given texel. If gamma correction is enabled, the color
|
||||
* is gamma corrected. If bounds are extended, then the input is treated as padded by a blur size
|
||||
* amount of pixels of zero color, and the given texel is assumed to be in the space of the image
|
||||
* after padding. So we offset the texel by the blur radius amount and fallback to a zero color if
|
||||
* it is out of bounds. For instance, if the input is padded by 5 pixels to the left of the image,
|
||||
* the first 5 pixels should be out of bounds and thus zero, hence the introduced offset. */
|
||||
vec4 load_input(ivec2 texel)
|
||||
{
|
||||
vec4 color;
|
||||
if (extend_bounds) {
|
||||
/* If bounds are extended, then we treat the input as padded by a radius amount of pixels. So
|
||||
* we load the input with an offset by the radius amount and fallback to a transparent color if
|
||||
* it is out of bounds. Notice that we subtract 1 because the weights texture have an extra
|
||||
* center weight, see the SymmetricBlurWeights for more information. */
|
||||
ivec2 blur_size = texture_size(weights_tx) - 1;
|
||||
color = texture_load(input_tx, texel - blur_size, vec4(0.0));
|
||||
/* Notice that we subtract 1 because the weights texture have an extra center weight, see the
|
||||
* SymmetricBlurWeights class for more information. */
|
||||
ivec2 blur_radius = texture_size(weights_tx) - 1;
|
||||
color = texture_load(input_tx, texel - blur_radius, vec4(0.0));
|
||||
}
|
||||
else {
|
||||
color = texture_load(input_tx, texel);
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_blur_common.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
|
||||
|
||||
/* Loads the input color of the pixel at the given texel. If gamma correction is enabled, the color
|
||||
* is gamma corrected. If bounds are extended, then the input is treated as padded by a blur size
|
||||
* amount of pixels of zero color, and the given texel is assumed to be in the space of the image
|
||||
* after padding. So we offset the texel by the blur radius amount and fallback to a zero color if
|
||||
* it is out of bounds. For instance, if the input is padded by 5 pixels to the left of the image,
|
||||
* the first 5 pixels should be out of bounds and thus zero, hence the introduced offset. */
|
||||
vec4 load_input(ivec2 texel)
|
||||
{
|
||||
vec4 color;
|
||||
if (extend_bounds) {
|
||||
/* Notice that we subtract 1 because the weights texture have an extra center weight, see the
|
||||
* SymmetricBlurWeights class for more information. */
|
||||
ivec2 blur_radius = texture_size(weights_tx) - 1;
|
||||
color = texture_load(input_tx, texel - blur_radius, vec4(0.0));
|
||||
}
|
||||
else {
|
||||
color = texture_load(input_tx, texel);
|
||||
}
|
||||
|
||||
if (gamma_correct) {
|
||||
color = gamma_correct_blur_input(color);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/* Similar to load_input but loads the size instead, has no gamma correction, and clamps to borders
|
||||
* instead of returning zero for out of bound access. See load_input for more information. */
|
||||
float load_size(ivec2 texel)
|
||||
{
|
||||
if (extend_bounds) {
|
||||
ivec2 blur_radius = texture_size(weights_tx) - 1;
|
||||
return texture_load(size_tx, texel - blur_radius).x;
|
||||
}
|
||||
else {
|
||||
return texture_load(size_tx, texel).x;
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
vec4 accumulated_color = vec4(0.0);
|
||||
vec4 accumulated_weight = vec4(0.0);
|
||||
|
||||
/* First, compute the contribution of the center pixel. */
|
||||
vec4 center_color = load_input(texel);
|
||||
float center_weight = texture_load(weights_tx, ivec2(0)).x;
|
||||
accumulated_color += center_color * center_weight;
|
||||
accumulated_weight += center_weight;
|
||||
|
||||
ivec2 weights_size = texture_size(weights_tx);
|
||||
|
||||
/* Then, compute the contributions of the pixels along the x axis of the filter, but only
|
||||
* accumulate them if their distance to the center is less their computed variable blur size,
|
||||
* noting that the weights texture only stores the weights for the positive half, but since the
|
||||
* filter is symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int x = 1; x < weights_size.x; x++) {
|
||||
float weight = texture_load(weights_tx, ivec2(x, 0)).x;
|
||||
|
||||
float right_size = load_size(texel + ivec2(x, 0));
|
||||
float right_blur_radius = right_size * weights_size.x;
|
||||
if (x < right_blur_radius) {
|
||||
accumulated_color += load_input(texel + ivec2(x, 0)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
|
||||
float left_size = load_size(texel + ivec2(-x, 0));
|
||||
float left_blur_radius = right_size * weights_size.x;
|
||||
if (x < left_blur_radius) {
|
||||
accumulated_color += load_input(texel + ivec2(-x, 0)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
/* Then, compute the contributions of the pixels along the y axis of the filter, but only
|
||||
* accumulate them if their distance to the center is less their computed variable blur size,
|
||||
* noting that the weights texture only stores the weights for the positive half, but since the
|
||||
* filter is symmetric, the same weight is used for the negative half and we add both of their
|
||||
* contributions. */
|
||||
for (int y = 1; y < weights_size.y; y++) {
|
||||
float weight = texture_load(weights_tx, ivec2(0, y)).x;
|
||||
|
||||
float top_size = load_size(texel + ivec2(0, y));
|
||||
float top_blur_radius = top_size * weights_size.y;
|
||||
if (y < top_blur_radius) {
|
||||
accumulated_color += load_input(texel + ivec2(0, y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
|
||||
float bottom_size = load_size(texel + ivec2(0, -y));
|
||||
float bottom_blur_radius = bottom_size * weights_size.x;
|
||||
if (y < bottom_blur_radius) {
|
||||
accumulated_color += load_input(texel + ivec2(0, -y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, compute the contributions of the pixels in the four quadrants of the filter, but only
|
||||
* accumulate them if the center lies inside the rectangle centered at the pixel whose width and
|
||||
* height is the variable blur size, noting that the weights texture only stores the weights for
|
||||
* the upper right quadrant, but since the filter is symmetric, the same weight is used for the
|
||||
* rest of the quadrants and we add all four of their contributions. */
|
||||
for (int y = 1; y < weights_size.y; y++) {
|
||||
for (int x = 1; x < weights_size.x; x++) {
|
||||
float weight = texture_load(weights_tx, ivec2(x, y)).x;
|
||||
|
||||
/* Upper right quadrant. */
|
||||
float upper_right_size = load_size(texel + ivec2(x, y));
|
||||
vec2 upper_right_blur_radius = upper_right_size * weights_size;
|
||||
if (x < upper_right_blur_radius.x && y < upper_right_blur_radius.y) {
|
||||
accumulated_color += load_input(texel + ivec2(x, y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
|
||||
/* Upper left quadrant. */
|
||||
float upper_left_size = load_size(texel + ivec2(-x, y));
|
||||
vec2 upper_left_blur_radius = upper_left_size * weights_size;
|
||||
if (x < upper_left_blur_radius.x && y < upper_left_blur_radius.y) {
|
||||
accumulated_color += load_input(texel + ivec2(-x, y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
|
||||
/* Bottom right quadrant. */
|
||||
float bottom_right_size = load_size(texel + ivec2(x, -y));
|
||||
vec2 bottom_right_blur_radius = bottom_right_size * weights_size;
|
||||
if (x < bottom_right_blur_radius.x && y < bottom_right_blur_radius.y) {
|
||||
accumulated_color += load_input(texel + ivec2(x, -y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
|
||||
/* Bottom left quadrant. */
|
||||
float bottom_left_size = load_size(texel + ivec2(-x, -y));
|
||||
vec2 bottom_left_blur_radius = bottom_left_size * weights_size;
|
||||
if (x < bottom_left_blur_radius.x && y < bottom_left_blur_radius.y) {
|
||||
accumulated_color += load_input(texel + ivec2(-x, -y)) * weight;
|
||||
accumulated_weight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accumulated_color = safe_divide(accumulated_color, accumulated_weight);
|
||||
|
||||
if (gamma_correct) {
|
||||
accumulated_color = gamma_uncorrect_blur_output(accumulated_color);
|
||||
}
|
||||
|
||||
imageStore(output_img, texel, accumulated_color);
|
||||
}
|
|
@ -82,3 +82,25 @@ GPU_SHADER_CREATE_INFO(compositor_glare_simple_star_anti_diagonal_pass)
|
|||
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "anti_diagonal_img")
|
||||
.compute_source("compositor_glare_simple_star_anti_diagonal_pass.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
/* -------
|
||||
* Streaks
|
||||
* ------- */
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_glare_streaks_filter)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::FLOAT, "color_modulator")
|
||||
.push_constant(Type::VEC3, "fade_factors")
|
||||
.push_constant(Type::VEC2, "streak_vector")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_streak_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_streak_img")
|
||||
.compute_source("compositor_glare_streaks_filter.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_glare_streaks_accumulate)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::FLOAT, "attenuation_factor")
|
||||
.sampler(0, ImageType::FLOAT_2D, "streak_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::READ_WRITE, ImageType::FLOAT_2D, "accumulated_streaks_img")
|
||||
.compute_source("compositor_glare_streaks_accumulate.glsl")
|
||||
.do_static_compilation(true);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_create_info.hh"
|
||||
|
||||
GPU_SHADER_CREATE_INFO(compositor_symmetric_blur_variable_size)
|
||||
.local_group_size(16, 16)
|
||||
.push_constant(Type::BOOL, "extend_bounds")
|
||||
.push_constant(Type::BOOL, "gamma_correct")
|
||||
.sampler(0, ImageType::FLOAT_2D, "input_tx")
|
||||
.sampler(1, ImageType::FLOAT_2D, "weights_tx")
|
||||
.sampler(2, ImageType::FLOAT_2D, "size_tx")
|
||||
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
|
||||
.compute_source("compositor_symmetric_blur_variable_size.glsl")
|
||||
.do_static_compilation(true);
|
|
@ -187,6 +187,13 @@ vec4 gpencil_vertex(vec4 viewport_size,
|
|||
is_squares = false;
|
||||
}
|
||||
|
||||
/* Endpoints, we discard the vertices. */
|
||||
if (!is_dot && ma2.x == -1) {
|
||||
/* We set the vertex at the camera origin to generate 0 fragments. */
|
||||
out_ndc = vec4(0.0, 0.0, -3e36, 0.0);
|
||||
return out_ndc;
|
||||
}
|
||||
|
||||
/* Avoid using a vertex attribute for quad positioning. */
|
||||
float x = float(gl_VertexID & 1) * 2.0 - 1.0; /* [-1..1] */
|
||||
float y = float(gl_VertexID & 2) - 1.0; /* [-1..1] */
|
||||
|
|
|
@ -133,9 +133,11 @@ TreeElement *outliner_find_item_at_x_in_row(const SpaceOutliner *space_outliner,
|
|||
bool *r_is_merged_icon,
|
||||
bool *r_is_over_icon)
|
||||
{
|
||||
/* if parent_te is opened, it doesn't show children in row */
|
||||
TreeStoreElem *parent_tselem = TREESTORE(parent_te);
|
||||
TreeElement *te = parent_te;
|
||||
if (!TSELEM_OPEN(TREESTORE(parent_te), space_outliner)) {
|
||||
|
||||
/* If parent_te is opened, or it is a ViewLayer, it doesn't show children in row. */
|
||||
if (!TSELEM_OPEN(parent_tselem, space_outliner) && parent_tselem->type != TSE_R_LAYER) {
|
||||
te = outliner_find_item_at_x_in_row_recursive(parent_te, view_co_x, r_is_merged_icon);
|
||||
}
|
||||
|
||||
|
|
|
@ -1128,15 +1128,6 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options,
|
|||
}
|
||||
});
|
||||
|
||||
if (first_mesh.active_color_attribute) {
|
||||
MEM_SAFE_FREE(dst_mesh->active_color_attribute);
|
||||
dst_mesh->active_color_attribute = BLI_strdup(first_mesh.active_color_attribute);
|
||||
}
|
||||
if (first_mesh.default_color_attribute) {
|
||||
MEM_SAFE_FREE(dst_mesh->default_color_attribute);
|
||||
dst_mesh->default_color_attribute = BLI_strdup(first_mesh.default_color_attribute);
|
||||
}
|
||||
|
||||
/* Tag modified attributes. */
|
||||
for (GSpanAttributeWriter &dst_attribute : dst_attribute_writers) {
|
||||
dst_attribute.finish();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "gpu_backend.hh"
|
||||
#include "gpu_node_graph.h"
|
||||
|
||||
#include "GPU_context.h"
|
||||
#include "GPU_material.h"
|
||||
|
||||
#include "GPU_uniform_buffer.h"
|
||||
|
@ -56,6 +57,10 @@ static eGPUType get_padded_gpu_type(LinkData *link)
|
|||
{
|
||||
GPUInput *input = (GPUInput *)link->data;
|
||||
eGPUType gputype = input->type;
|
||||
/* Metal cannot pack floats after vec3. */
|
||||
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
|
||||
return (gputype == GPU_VEC3) ? GPU_VEC4 : gputype;
|
||||
}
|
||||
/* Unless the vec3 is followed by a float we need to treat it as a vec4. */
|
||||
if (gputype == GPU_VEC3 && (link->next != nullptr) &&
|
||||
(((GPUInput *)link->next->data)->type != GPU_FLOAT)) {
|
||||
|
@ -89,6 +94,11 @@ static void buffer_from_list_inputs_sort(ListBase *inputs)
|
|||
/* Order them as mat4, vec4, vec3, vec2, float. */
|
||||
BLI_listbase_sort(inputs, inputs_cmp);
|
||||
|
||||
/* Metal cannot pack floats after vec3. */
|
||||
if (GPU_backend_get_type() == GPU_BACKEND_METAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Creates a lookup table for the different types; */
|
||||
LinkData *inputs_lookup[MAX_UBO_GPU_TYPE + 1] = {nullptr};
|
||||
eGPUType cur_type = static_cast<eGPUType>(MAX_UBO_GPU_TYPE + 1);
|
||||
|
|
|
@ -808,6 +808,16 @@ enum {
|
|||
* RESET_NEVER
|
||||
*/
|
||||
LIB_TAG_NO_MAIN = 1 << 15,
|
||||
/**
|
||||
* ID is considered as runtime, and should not be saved when writing .blend file, nor influence
|
||||
* (in)direct status of linked data.
|
||||
*
|
||||
* Only meaningful for IDs belonging to regular Main database, all other cases are implicitely
|
||||
* considered runtime-only.
|
||||
*
|
||||
* RESET_NEVER
|
||||
*/
|
||||
LIB_TAG_RUNTIME = 1 << 22,
|
||||
/**
|
||||
* Datablock does not refcount usages of other IDs.
|
||||
*
|
||||
|
@ -848,6 +858,15 @@ enum {
|
|||
LIB_TAG_LIB_OVERRIDE_NEED_RESYNC = 1 << 21,
|
||||
};
|
||||
|
||||
/**
|
||||
* Most of ID tags are cleared on file write (i.e. also when storing undo steps), since they
|
||||
* either have of very short lifetime (not expected to exist accross undo steps), or are info that
|
||||
* will be re-generated when reading undo steps.
|
||||
*
|
||||
* However a few of these need to be explicitely preserved accross undo steps.
|
||||
*/
|
||||
#define LIB_TAG_KEEP_ON_UNDO (LIB_TAG_EXTRAUSER | LIB_TAG_MISSING | LIB_TAG_RUNTIME)
|
||||
|
||||
/* Tag given ID for an update in all the dependency graphs. */
|
||||
typedef enum IDRecalcFlag {
|
||||
/***************************************************************************
|
||||
|
|
|
@ -574,6 +574,33 @@ IDProperty **rna_ID_idprops(PointerRNA *ptr)
|
|||
return &id->properties;
|
||||
}
|
||||
|
||||
int rna_ID_is_runtime_editable(PointerRNA *ptr, const char **r_info)
|
||||
{
|
||||
ID *id = (ID *)ptr->data;
|
||||
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
|
||||
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
|
||||
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
|
||||
*r_info =
|
||||
"Cannot edit 'runtime' status of non-blendfile data-blocks, as they are by definition "
|
||||
"always runtime";
|
||||
return 0;
|
||||
}
|
||||
|
||||
return PROP_EDITABLE;
|
||||
}
|
||||
|
||||
bool rna_ID_is_runtime_get(PointerRNA *ptr)
|
||||
{
|
||||
ID *id = (ID *)ptr->data;
|
||||
/* TODO: This should be abstracted in a BKE function or define, somewhat related to T88555. */
|
||||
if (id->tag & (LIB_TAG_NO_MAIN | LIB_TAG_TEMP_MAIN | LIB_TAG_LOCALIZED |
|
||||
LIB_TAG_COPIED_ON_WRITE_EVAL_RESULT | LIB_TAG_COPIED_ON_WRITE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (id->tag & LIB_TAG_RUNTIME) != 0;
|
||||
}
|
||||
|
||||
void rna_ID_fake_user_set(PointerRNA *ptr, bool value)
|
||||
{
|
||||
ID *id = (ID *)ptr->data;
|
||||
|
@ -2030,6 +2057,17 @@ static void rna_def_ID(BlenderRNA *brna)
|
|||
"This data-block is not an independent one, but is actually a sub-data of another ID "
|
||||
"(typical example: root node trees or master collections)");
|
||||
|
||||
prop = RNA_def_property(srna, "is_runtime_data", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_RUNTIME);
|
||||
RNA_def_property_editable_func(prop, "rna_ID_is_runtime_editable");
|
||||
RNA_def_property_boolean_funcs(prop, "rna_ID_is_runtime_get", NULL);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_NO_COMPARISON);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Runtime Data",
|
||||
"This data-block is runtime data, i.e. it won't be saved in .blend "
|
||||
"file. Note that e.g. evaluated IDs are always runtime, so this value "
|
||||
"is only editable for data-blocks in Main data-base");
|
||||
|
||||
prop = RNA_def_property(srna, "tag", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "tag", LIB_TAG_DOIT);
|
||||
RNA_def_property_flag(prop, PROP_LIB_EXCEPTION);
|
||||
|
|
|
@ -212,8 +212,8 @@ static void rna_ParticleHairKey_location_object_get(PointerRNA *ptr, float *valu
|
|||
|
||||
if (pa) {
|
||||
Mesh *hair_mesh = (psmd->psys->flag & PSYS_HAIR_DYNAMICS) ? psmd->psys->hair_out_mesh : NULL;
|
||||
const float(*positions)[3] = BKE_mesh_positions(hair_mesh);
|
||||
if (hair_mesh) {
|
||||
const float(*positions)[3] = BKE_mesh_positions(hair_mesh);
|
||||
copy_v3_v3(values, positions[pa->hair_index + (hkey - pa->hair)]);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1282,29 +1282,29 @@ static void modifyGeometry(ModifierData *md,
|
|||
bool use_orig_index_verts = false;
|
||||
bool use_orig_index_edges = false;
|
||||
bool use_orig_index_polys = false;
|
||||
if (geometry_set.has_mesh()) {
|
||||
const Mesh &mesh = *geometry_set.get_mesh_for_read();
|
||||
use_orig_index_verts = CustomData_has_layer(&mesh.vdata, CD_ORIGINDEX);
|
||||
use_orig_index_edges = CustomData_has_layer(&mesh.edata, CD_ORIGINDEX);
|
||||
use_orig_index_polys = CustomData_has_layer(&mesh.pdata, CD_ORIGINDEX);
|
||||
if (const Mesh *mesh = geometry_set.get_mesh_for_read()) {
|
||||
use_orig_index_verts = CustomData_has_layer(&mesh->vdata, CD_ORIGINDEX);
|
||||
use_orig_index_edges = CustomData_has_layer(&mesh->edata, CD_ORIGINDEX);
|
||||
use_orig_index_polys = CustomData_has_layer(&mesh->pdata, CD_ORIGINDEX);
|
||||
}
|
||||
|
||||
geometry_set = compute_geometry(
|
||||
tree, *lf_graph_info, *output_node, std::move(geometry_set), nmd, ctx);
|
||||
|
||||
if (geometry_set.has_mesh()) {
|
||||
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
||||
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
|
||||
* assumed that the output mesh does not have a mapping to the original mesh. */
|
||||
Mesh &mesh = *geometry_set.get_mesh_for_write();
|
||||
if (use_orig_index_verts) {
|
||||
CustomData_add_layer(&mesh.vdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totvert);
|
||||
}
|
||||
if (use_orig_index_edges) {
|
||||
CustomData_add_layer(&mesh.edata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totedge);
|
||||
}
|
||||
if (use_orig_index_polys) {
|
||||
CustomData_add_layer(&mesh.pdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh.totpoly);
|
||||
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_polys) {
|
||||
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
|
||||
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
|
||||
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
|
||||
* assumed that the output mesh does not have a mapping to the original mesh. */
|
||||
if (use_orig_index_verts) {
|
||||
CustomData_add_layer(&mesh->vdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totvert);
|
||||
}
|
||||
if (use_orig_index_edges) {
|
||||
CustomData_add_layer(&mesh->edata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totedge);
|
||||
}
|
||||
if (use_orig_index_polys) {
|
||||
CustomData_add_layer(&mesh->pdata, CD_ORIGINDEX, CD_SET_DEFAULT, nullptr, mesh->totpoly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,10 @@ class BlurOperation : public NodeOperation {
|
|||
return;
|
||||
}
|
||||
|
||||
if (use_separable_filter()) {
|
||||
if (!get_input("Size").is_single_value() && get_variable_size()) {
|
||||
execute_variable_size();
|
||||
}
|
||||
else if (use_separable_filter()) {
|
||||
symmetric_separable_blur(context(),
|
||||
get_input("Image"),
|
||||
get_result("Image"),
|
||||
|
@ -116,11 +119,11 @@ class BlurOperation : public NodeOperation {
|
|||
node_storage(bnode()).gamma);
|
||||
}
|
||||
else {
|
||||
execute_blur();
|
||||
execute_constant_size();
|
||||
}
|
||||
}
|
||||
|
||||
void execute_blur()
|
||||
void execute_constant_size()
|
||||
{
|
||||
GPUShader *shader = shader_manager().get("compositor_symmetric_blur");
|
||||
GPU_shader_bind(shader);
|
||||
|
@ -155,6 +158,45 @@ class BlurOperation : public NodeOperation {
|
|||
weights.unbind_as_texture();
|
||||
}
|
||||
|
||||
void execute_variable_size()
|
||||
{
|
||||
GPUShader *shader = shader_manager().get("compositor_symmetric_blur_variable_size");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_shader_uniform_1b(shader, "extend_bounds", get_extend_bounds());
|
||||
GPU_shader_uniform_1b(shader, "gamma_correct", node_storage(bnode()).gamma);
|
||||
|
||||
const Result &input_image = get_input("Image");
|
||||
input_image.bind_as_texture(shader, "input_tx");
|
||||
|
||||
const float2 blur_radius = compute_blur_radius();
|
||||
|
||||
const SymmetricBlurWeights &weights = context().cache_manager().get_symmetric_blur_weights(
|
||||
node_storage(bnode()).filtertype, blur_radius);
|
||||
weights.bind_as_texture(shader, "weights_tx");
|
||||
|
||||
const Result &input_size = get_input("Size");
|
||||
input_size.bind_as_texture(shader, "size_tx");
|
||||
|
||||
Domain domain = compute_domain();
|
||||
if (get_extend_bounds()) {
|
||||
/* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
|
||||
domain.size += int2(math::ceil(blur_radius)) * 2;
|
||||
}
|
||||
|
||||
Result &output_image = get_result("Image");
|
||||
output_image.allocate_texture(domain);
|
||||
output_image.bind_as_image(shader, "output_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, domain.size);
|
||||
|
||||
GPU_shader_unbind();
|
||||
output_image.unbind_as_image();
|
||||
input_image.unbind_as_texture();
|
||||
weights.unbind_as_texture();
|
||||
input_size.unbind_as_texture();
|
||||
}
|
||||
|
||||
float2 compute_blur_radius()
|
||||
{
|
||||
const float size = math::clamp(get_input("Size").get_float_value_default(1.0f), 0.0f, 1.0f);
|
||||
|
@ -228,6 +270,11 @@ class BlurOperation : public NodeOperation {
|
|||
{
|
||||
return bnode().custom1 & CMP_NODEFLAG_BLUR_EXTEND_BOUNDS;
|
||||
}
|
||||
|
||||
bool get_variable_size()
|
||||
{
|
||||
return bnode().custom1 & CMP_NODEFLAG_BLUR_VARIABLE_SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, DNode node)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_vec_types.hh"
|
||||
|
||||
|
@ -31,6 +32,8 @@
|
|||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
#define MAX_GLARE_ITERATIONS 5
|
||||
|
||||
namespace blender::nodes::node_composite_glare_cc {
|
||||
|
||||
NODE_STORAGE_FUNCS(NodeGlare)
|
||||
|
@ -128,19 +131,9 @@ class GlareOperation : public NodeOperation {
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Only the ghost and simple star operations are currently supported. */
|
||||
switch (node_storage(bnode()).type) {
|
||||
case CMP_NODE_GLARE_SIMPLE_STAR:
|
||||
return false;
|
||||
case CMP_NODE_GLARE_FOG_GLOW:
|
||||
return true;
|
||||
case CMP_NODE_GLARE_STREAKS:
|
||||
return true;
|
||||
case CMP_NODE_GLARE_GHOST:
|
||||
return false;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
return true;
|
||||
/* The fog glow mode is currently unsupported. */
|
||||
if (node_storage(bnode()).type == CMP_NODE_GLARE_FOG_GLOW) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -334,26 +327,174 @@ class GlareOperation : public NodeOperation {
|
|||
return size.x + size.y - 1;
|
||||
}
|
||||
|
||||
/* ---------------
|
||||
* Fog Glow Glare.
|
||||
* --------------- */
|
||||
|
||||
/* Not yet implemented. Unreachable code due to the is_identity method. */
|
||||
Result execute_fog_glow(Result & /*highlights_result*/)
|
||||
{
|
||||
BLI_assert_unreachable();
|
||||
return Result(ResultType::Color, texture_pool());
|
||||
}
|
||||
|
||||
/* --------------
|
||||
* Streaks Glare.
|
||||
* -------------- */
|
||||
|
||||
/* Not yet implemented. Unreachable code due to the is_identity method. */
|
||||
Result execute_streaks(Result & /*highlights_result*/)
|
||||
Result execute_streaks(Result &highlights_result)
|
||||
{
|
||||
BLI_assert_unreachable();
|
||||
return Result(ResultType::Color, texture_pool());
|
||||
/* Create an initially zero image where streaks will be accumulated. */
|
||||
const float4 zero_color = float4(0.0f);
|
||||
const int2 glare_size = get_glare_size();
|
||||
Result accumulated_streaks_result = Result::Temporary(ResultType::Color, texture_pool());
|
||||
accumulated_streaks_result.allocate_texture(glare_size);
|
||||
GPU_texture_clear(accumulated_streaks_result.texture(), GPU_DATA_FLOAT, zero_color);
|
||||
|
||||
/* For each streak, compute its direction and apply a streak filter in that direction, then
|
||||
* accumulate the result into the accumulated streaks result. */
|
||||
for (const int streak_index : IndexRange(get_number_of_streaks())) {
|
||||
const float2 streak_direction = compute_streak_direction(streak_index);
|
||||
Result streak_result = apply_streak_filter(highlights_result, streak_direction);
|
||||
|
||||
GPUShader *shader = shader_manager().get("compositor_glare_streaks_accumulate");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
const float attenuation_factor = compute_streak_attenuation_factor();
|
||||
GPU_shader_uniform_1f(shader, "attenuation_factor", attenuation_factor);
|
||||
|
||||
streak_result.bind_as_texture(shader, "streak_tx");
|
||||
accumulated_streaks_result.bind_as_image(shader, "accumulated_streaks_img", true);
|
||||
|
||||
compute_dispatch_threads_at_least(shader, glare_size);
|
||||
|
||||
streak_result.unbind_as_texture();
|
||||
accumulated_streaks_result.unbind_as_image();
|
||||
|
||||
streak_result.release();
|
||||
GPU_shader_unbind();
|
||||
}
|
||||
|
||||
return accumulated_streaks_result;
|
||||
}
|
||||
|
||||
Result apply_streak_filter(Result &highlights_result, const float2 &streak_direction)
|
||||
{
|
||||
GPUShader *shader = shader_manager().get("compositor_glare_streaks_filter");
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
/* Copy the highlights result into a new image because the output will be copied to the input
|
||||
* after each iteration and the highlights result is still needed to compute other streaks. */
|
||||
const int2 glare_size = get_glare_size();
|
||||
Result input_streak_result = Result::Temporary(ResultType::Color, texture_pool());
|
||||
input_streak_result.allocate_texture(glare_size);
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
GPU_texture_copy(input_streak_result.texture(), highlights_result.texture());
|
||||
|
||||
Result output_streak_result = Result::Temporary(ResultType::Color, texture_pool());
|
||||
output_streak_result.allocate_texture(glare_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. */
|
||||
const IndexRange iterations_range = IndexRange(get_number_of_iterations());
|
||||
for (const int iteration : iterations_range) {
|
||||
const float color_modulator = compute_streak_color_modulator(iteration);
|
||||
const float iteration_magnitude = compute_streak_iteration_magnitude(iteration);
|
||||
const float3 fade_factors = compute_streak_fade_factors(iteration_magnitude);
|
||||
const float2 streak_vector = streak_direction * iteration_magnitude;
|
||||
|
||||
GPU_shader_uniform_1f(shader, "color_modulator", color_modulator);
|
||||
GPU_shader_uniform_3fv(shader, "fade_factors", fade_factors);
|
||||
GPU_shader_uniform_2fv(shader, "streak_vector", streak_vector);
|
||||
|
||||
input_streak_result.bind_as_texture(shader, "input_streak_tx");
|
||||
GPU_texture_filter_mode(input_streak_result.texture(), true);
|
||||
GPU_texture_wrap_mode(input_streak_result.texture(), false, false);
|
||||
|
||||
output_streak_result.bind_as_image(shader, "output_streak_img");
|
||||
|
||||
compute_dispatch_threads_at_least(shader, glare_size);
|
||||
|
||||
input_streak_result.unbind_as_texture();
|
||||
output_streak_result.unbind_as_image();
|
||||
|
||||
/* The accumulated result serves as the input for the next iteration, so copy the result to
|
||||
* the input result since it can't be used for reading and writing simultaneously. Skip
|
||||
* copying for the last iteration since it is not needed. */
|
||||
if (iteration != iterations_range.last()) {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
GPU_texture_copy(input_streak_result.texture(), output_streak_result.texture());
|
||||
}
|
||||
}
|
||||
|
||||
input_streak_result.release();
|
||||
GPU_shader_unbind();
|
||||
|
||||
return output_streak_result;
|
||||
}
|
||||
|
||||
/* As the number of iterations increase, the streaks spread farther and their intensity decrease.
|
||||
* To maintain similar intensities regardless of the number of iterations, streaks with lower
|
||||
* number of iteration are linearly attenuated. When the number of iterations is maximum, we need
|
||||
* not attenuate, so the denominator should be one, and when the number of iterations is one, we
|
||||
* need the attenuation to be maximum. This can be modeled as a simple decreasing linear equation
|
||||
* by substituting the two aforementioned cases. */
|
||||
float compute_streak_attenuation_factor()
|
||||
{
|
||||
return 1.0f / (MAX_GLARE_ITERATIONS + 1 - get_number_of_iterations());
|
||||
}
|
||||
|
||||
/* Given the index of the streak in the [0, Number Of Streaks - 1] range, compute the unit
|
||||
* direction vector defining the streak. The streak directions should make angles with the
|
||||
* x-axis that are equally spaced and covers the whole two pi range, starting with the user
|
||||
* supplied angle. */
|
||||
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 angle = start_angle + (float(streak_index) / number_of_streaks) * (M_PI * 2.0f);
|
||||
return float2(math::cos(angle), math::sin(angle));
|
||||
}
|
||||
|
||||
/* Different color channels of the streaks can be modulated by being multiplied by the color
|
||||
* modulator computed by this method. The color modulation is expected to be maximum when the
|
||||
* modulation factor is 1 and non existent when it is zero. But since the color modulator is
|
||||
* multiplied to the channel and the multiplicative identity is 1, we invert the modulation
|
||||
* factor. Moreover, color modulation should be less visible on higher iterations because they
|
||||
* produce the farther more faded away parts of the streaks. To achieve that, the modulation
|
||||
* factor is raised to the power of the iteration, noting that the modulation value is in the
|
||||
* [0, 1] range so the higher the iteration the lower the resulting modulation factor. The plus
|
||||
* 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);
|
||||
}
|
||||
|
||||
/* Streaks are computed by iteratively applying a filter that samples 3 neighbouring pixels in
|
||||
* the direction of the streak. Those neighbouring pixels are then combined using a weighted sum.
|
||||
* The weights of the neighbours are the fade factors computed by this method. Farther neighbours
|
||||
* are expected to have lower weights because they contribute less to the combined result. Since
|
||||
* the iteration magnitude represents how far the neighbours are, as noted in the description of
|
||||
* the compute_streak_iteration_magnitude method, the fade factor for the closest neighbour is
|
||||
* computed as the user supplied fade parameter raised to the power of the magnitude, noting that
|
||||
* the fade value is in the [0, 1] range while the magnitude is larger than or equal one, so the
|
||||
* higher the power the lower the resulting fade factor. Furthermore, the other two neighbours
|
||||
* are just squared and cubed versions of the fade factor for the closest neighbour to get even
|
||||
* lower fade factors for those farther neighbours. */
|
||||
float3 compute_streak_fade_factors(float iteration_magnitude)
|
||||
{
|
||||
const float fade_factor = std::pow(node_storage(bnode()).fade, iteration_magnitude);
|
||||
return float3(fade_factor, std::pow(fade_factor, 2.0f), std::pow(fade_factor, 3.0f));
|
||||
}
|
||||
|
||||
/* Streaks are computed by iteratively applying a filter that samples the neighbouring pixels in
|
||||
* the direction of the streak. Each higher iteration samples pixels that are farther away, the
|
||||
* magnitude computed by this method describes how farther away the neighbours are sampled. The
|
||||
* magnitude exponentially increase with the iteration. A base of 4, was chosen as compromise
|
||||
* between better quality and performance, since a lower base corresponds to more tightly spaced
|
||||
* neighbours but would require more iterations to produce a streak of the same length. */
|
||||
float compute_streak_iteration_magnitude(int iteration)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* ------------
|
||||
|
@ -377,9 +518,9 @@ class GlareOperation : public NodeOperation {
|
|||
/* Create an initially zero image where ghosts will be accumulated. */
|
||||
const float4 zero_color = float4(0.0f);
|
||||
const int2 glare_size = get_glare_size();
|
||||
Result accumulated_ghost_result = Result::Temporary(ResultType::Color, texture_pool());
|
||||
accumulated_ghost_result.allocate_texture(glare_size);
|
||||
GPU_texture_clear(accumulated_ghost_result.texture(), GPU_DATA_FLOAT, zero_color);
|
||||
Result accumulated_ghosts_result = Result::Temporary(ResultType::Color, texture_pool());
|
||||
accumulated_ghosts_result.allocate_texture(glare_size);
|
||||
GPU_texture_clear(accumulated_ghosts_result.texture(), GPU_DATA_FLOAT, zero_color);
|
||||
|
||||
/* For the given number of iterations, accumulate four ghosts with different scales and color
|
||||
* modulators. The result of the previous iteration is used as the input of the current
|
||||
|
@ -392,26 +533,26 @@ class GlareOperation : public NodeOperation {
|
|||
GPU_shader_uniform_4fv(shader, "scales", scales.data());
|
||||
|
||||
input_ghost_result.bind_as_texture(shader, "input_ghost_tx");
|
||||
accumulated_ghost_result.bind_as_image(shader, "accumulated_ghost_img", true);
|
||||
accumulated_ghosts_result.bind_as_image(shader, "accumulated_ghost_img", true);
|
||||
|
||||
compute_dispatch_threads_at_least(shader, glare_size);
|
||||
|
||||
input_ghost_result.unbind_as_texture();
|
||||
accumulated_ghost_result.unbind_as_image();
|
||||
accumulated_ghosts_result.unbind_as_image();
|
||||
|
||||
/* The accumulated result serves as the input for the next iteration, so copy the result to
|
||||
* the input result since it can't be used for reading and writing simultaneously. Skip
|
||||
* copying for the last iteration since it is not needed. */
|
||||
if (i != iterations_range.last()) {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
GPU_texture_copy(input_ghost_result.texture(), accumulated_ghost_result.texture());
|
||||
GPU_texture_copy(input_ghost_result.texture(), accumulated_ghosts_result.texture());
|
||||
}
|
||||
}
|
||||
|
||||
GPU_shader_unbind();
|
||||
input_ghost_result.release();
|
||||
|
||||
return accumulated_ghost_result;
|
||||
return accumulated_ghosts_result;
|
||||
}
|
||||
|
||||
/* Computes two ghosts by blurring the highlights with two different radii, then adds them into a
|
||||
|
@ -544,7 +685,18 @@ class GlareOperation : public NodeOperation {
|
|||
* subtract from one. */
|
||||
float get_ghost_color_modulation_factor()
|
||||
{
|
||||
return 1.0f - node_storage(bnode()).colmod;
|
||||
return 1.0f - get_color_modulation_factor();
|
||||
}
|
||||
|
||||
/* ---------------
|
||||
* Fog Glow Glare.
|
||||
* --------------- */
|
||||
|
||||
/* Not yet implemented. Unreachable code due to the is_identity method. */
|
||||
Result execute_fog_glow(Result & /*highlights_result*/)
|
||||
{
|
||||
BLI_assert_unreachable();
|
||||
return Result(ResultType::Color, texture_pool());
|
||||
}
|
||||
|
||||
/* ----------
|
||||
|
@ -595,6 +747,11 @@ class GlareOperation : public NodeOperation {
|
|||
return node_storage(bnode()).iter;
|
||||
}
|
||||
|
||||
float get_color_modulation_factor()
|
||||
{
|
||||
return node_storage(bnode()).colmod;
|
||||
}
|
||||
|
||||
/* The glare node can compute the glare on a fraction of the input image size to improve
|
||||
* performance. The quality values and their corresponding quality factors are as follows:
|
||||
*
|
||||
|
|
|
@ -47,8 +47,90 @@ class TestBlendFileSaveLoadBasic(TestHelper):
|
|||
assert orig_data == read_data
|
||||
|
||||
|
||||
# NOTE: Technically this should rather be in `bl_id_management.py` test, but that file uses `unittest` module,
|
||||
# which makes mixing it with tests system used here and passing extra parameters complicated.
|
||||
# Since the main effect of 'RUNTIME' ID tag is on file save, it can as well be here for now.
|
||||
class TestIdRuntimeTag(TestHelper):
|
||||
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
|
||||
def unique_blendfile_name(self, base_name):
|
||||
return base_name + self.__class__.__name__ + ".blend"
|
||||
|
||||
def test_basics(self):
|
||||
output_dir = self.args.output_dir
|
||||
self.ensure_path(output_dir)
|
||||
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
|
||||
|
||||
obj = bpy.data.objects['Cube']
|
||||
assert obj.is_runtime_data == False
|
||||
assert bpy.context.view_layer.depsgraph.ids['Cube'].is_runtime_data == True
|
||||
|
||||
output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile"))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
|
||||
obj = bpy.data.objects['Cube']
|
||||
assert obj.is_runtime_data == False
|
||||
|
||||
obj.is_runtime_data = True
|
||||
assert obj.is_runtime_data == True
|
||||
|
||||
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
|
||||
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
|
||||
|
||||
assert 'Cube' not in bpy.data.objects
|
||||
mesh = bpy.data.meshes['Cube']
|
||||
assert mesh.is_runtime_data == False
|
||||
assert mesh.users == 0
|
||||
|
||||
def test_linking(self):
|
||||
output_dir = self.args.output_dir
|
||||
self.ensure_path(output_dir)
|
||||
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
|
||||
|
||||
material = bpy.data.materials.new("LibMaterial")
|
||||
material.use_fake_user = True
|
||||
|
||||
output_lib_path = os.path.join(output_dir, self.unique_blendfile_name("blendlib_runtimetag_basic"))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=output_lib_path, check_existing=False, compress=False)
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=False, use_factory_startup=True)
|
||||
|
||||
obj = bpy.data.objects['Cube']
|
||||
assert obj.is_runtime_data == False
|
||||
obj.is_runtime_data = True
|
||||
|
||||
link_dir = os.path.join(output_lib_path, "Material")
|
||||
bpy.ops.wm.link(directory=link_dir, filename="LibMaterial")
|
||||
|
||||
linked_material = bpy.data.materials['LibMaterial']
|
||||
assert linked_material.is_library_indirect == False
|
||||
|
||||
obj.material_slots[0].link = 'OBJECT'
|
||||
obj.material_slots[0].material = linked_material
|
||||
|
||||
output_work_path = os.path.join(output_dir, self.unique_blendfile_name("blendfile"))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=output_work_path, check_existing=False, compress=False)
|
||||
|
||||
# Only usage of this linked material is a runtime ID (object),
|
||||
# so writing .blend file will have properly reset its tag to indirectly linked data.
|
||||
assert linked_material.is_library_indirect == True
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=output_work_path, load_ui=False)
|
||||
|
||||
assert 'Cube' not in bpy.data.objects
|
||||
assert 'LibMaterial' not in bpy.data.materials
|
||||
mesh = bpy.data.meshes['Cube']
|
||||
assert mesh.is_runtime_data == False
|
||||
assert mesh.users == 0
|
||||
|
||||
|
||||
TESTS = (
|
||||
TestBlendFileSaveLoadBasic,
|
||||
|
||||
TestIdRuntimeTag,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue