diff --git a/CHANGELOG.md b/CHANGELOG.md index 9201d2280..fe51792d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ Versioning](https://semver.org/spec/v2.0.0.html) for `libnopegl`. - `DrawTexture.texture` now accepts transformation nodes before the texture node to serve as a reframing mechanism (the transforms are applied to the texture coordinates in a centered `[-1,1]` space with `(-1,-1)` in the bottom left) +- `HexagonalBlur` node to apply a post processing hexagonal bokeh blur effect to a + scene ### Changed - `Text.font_files` text-based parameter is replaced with `Text.font_faces` node diff --git a/libnopegl/meson.build b/libnopegl/meson.build index c69f5745a..d572a2c28 100644 --- a/libnopegl/meson.build +++ b/libnopegl/meson.build @@ -208,6 +208,7 @@ lib_src = files( 'src/node_graphicconfig.c', 'src/node_gridlayout.c', 'src/node_group.c', + 'src/node_hblur.c', 'src/node_identity.c', 'src/node_io.c', 'src/node_eval.c', @@ -675,6 +676,9 @@ shaders = { 'blur_downsample.frag': 'blur_downsample_frag.h', 'blur_upsample.frag': 'blur_upsample_frag.h', 'blur_interpolate.frag': 'blur_interpolate_frag.h', + 'blur_hexagonal.vert': 'blur_hexagonal_vert.h', + 'blur_hexagonal_pass1.frag': 'blur_hexagonal_pass1_frag.h', + 'blur_hexagonal_pass2.frag': 'blur_hexagonal_pass2_frag.h', 'colorstats_init.comp': 'colorstats_init_comp.h', 'colorstats_sumscale.comp': 'colorstats_sumscale_comp.h', 'colorstats_waveform.comp': 'colorstats_waveform_comp.h', diff --git a/libnopegl/nodes.specs b/libnopegl/nodes.specs index b288d9d02..813c96558 100644 --- a/libnopegl/nodes.specs +++ b/libnopegl/nodes.specs @@ -2881,6 +2881,39 @@ } ] }, + "HexagonalBlur": { + "file": "src/node_hblur.c", + "params": [ + { + "name": "source", + "type": "node", + "node_types": ["Texture2D"], + "flags": ["nonull"], + "desc": "source to use for the blur" + }, + { + "name": "destination", + "type": "node", + "node_types": ["Texture2D"], + "flags": ["nonull"], + "desc": "destination to use for the blur" + }, + { + "name": "amount", + "type": "f32", + "default": 0.000000, + "flags": ["node"], + "desc": "amount of bluriness in the range [0,1]" + }, + { + "name": "map", + "type": "node", + "node_types": ["Texture2D"], + "flags": [], + "desc": "blur map providing the CoC (circle of confusion) for each pixels" + } + ] + }, "Identity": { "file": "src/node_identity.c", "params": [ diff --git a/libnopegl/src/glsl/blur_hexagonal.vert b/libnopegl/src/glsl/blur_hexagonal.vert new file mode 100644 index 000000000..46bcbae20 --- /dev/null +++ b/libnopegl/src/glsl/blur_hexagonal.vert @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Matthieu Bouron + * Copyright 2023 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const vec2 positions[] = vec2[](vec2(0.0, 0.0), vec2(2.0, 0.0), vec2(0.0, 2.0)); + +void main() +{ + vec2 uv = positions[ngl_vertex_index]; + ngl_out_pos = vec4(uv * 2.0 - 1.0, 0.0, 1.0); + tex_coord = uv; + map_coord = uv; +} diff --git a/libnopegl/src/glsl/blur_hexagonal_pass1.frag b/libnopegl/src/glsl/blur_hexagonal_pass1.frag new file mode 100644 index 000000000..f1cfb9447 --- /dev/null +++ b/libnopegl/src/glsl/blur_hexagonal_pass1.frag @@ -0,0 +1,48 @@ +/* + * Copyright 2023-2024 Matthieu Bouron + * Copyright 2023-2024 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include helper_srgb.glsl +#include helper_blur.glsl + +const vec2 up = vec2(0.0, 1.0); // cos(PI/2), sin(PI/2) +const vec2 down_left = vec2(0.8660254037844387, -0.5); // cos(-PI/6), sin(-PI/6) + +void main() +{ + float coc = texture(map, map_coord).r; + int radius = int(float(blur.radius) * coc); + + float lod = 0.0; + float scale = 1.0; + int nb_samples = max(radius, 1); + if (radius > blur.nb_samples) { + scale = float(radius) / float(blur.nb_samples); + nb_samples = blur.nb_samples; + lod = log(scale); + } + + vec4 color = ngli_blur_hexagonal(tex, tex_coord, map, map_coord, up, scale, lod, nb_samples); + vec4 color2 = ngli_blur_hexagonal(tex, tex_coord, map, map_coord, down_left, scale, lod, nb_samples); + + ngl_out_color[0] = vec4(ngli_linear2srgb(color.rgb), color.a); + ngl_out_color[1] = vec4(ngli_linear2srgb(color.rgb + color2.rgb), color.a + color2.a); +} diff --git a/libnopegl/src/glsl/blur_hexagonal_pass2.frag b/libnopegl/src/glsl/blur_hexagonal_pass2.frag new file mode 100644 index 000000000..cd33d4587 --- /dev/null +++ b/libnopegl/src/glsl/blur_hexagonal_pass2.frag @@ -0,0 +1,55 @@ +/* + * Copyright 2023-2024 Matthieu Bouron + * Copyright 2023-2024 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include helper_srgb.glsl +#include helper_blur.glsl + +const vec2 down_left = vec2(0.8660254037844387, -0.5); // cos(-PI/6), sin(-PI/6) +const vec2 down_right = vec2(-0.8660254037844387, -0.5); // cos(-5*PI/6), sin(-5*PI/6) + +/* + * Hexagonal blur pass 2 (down-left, down-right + combine) + * Adapted from WHITE, John, and BARRÉ-BRISEBOIS, Colin. More Performance! Five + * Rendering Ideas From Battlefield 3 and Need For Speed: The Run, Advances in + * Real-Time Rendering in Games, SIGGRAPH 2011: + * https://www.slideshare.net/DICEStudio/five-rendering-ideas-from-battlefield-3-need-for-speed-the-run + */ +void main() +{ + float coc = texture(map, map_coord).r; + int radius = int(float(blur.radius) * coc); + + float lod = 0.0; + float scale = 1.0; + int nb_samples = max(radius, 1); + if (radius > blur.nb_samples) { + scale = float(radius) / float(blur.nb_samples); + nb_samples = blur.nb_samples; + lod = log(scale); + } + + vec4 color = ngli_blur_hexagonal(tex0, tex_coord, map, map_coord, down_left, scale, lod, nb_samples); + vec4 color2 = ngli_blur_hexagonal(tex1, tex_coord, map, map_coord, down_right, scale, lod, nb_samples); + + vec4 out_color = mix(color, color2, 0.5); + ngl_out_color = vec4(ngli_linear2srgb(out_color.rgb), out_color.a); +} diff --git a/libnopegl/src/glsl/helper_blur.glsl b/libnopegl/src/glsl/helper_blur.glsl new file mode 100644 index 000000000..17194a374 --- /dev/null +++ b/libnopegl/src/glsl/helper_blur.glsl @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Matthieu Bouron + * Copyright 2024 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +vec4 ngli_blur_hexagonal(sampler2D tex, vec2 tex_coord, sampler2D map, vec2 map_coord, vec2 direction, float scale, float lod, int nb_samples) +{ + nb_samples = max(nb_samples, 1); + + float use_coc = nb_samples > 1 ? 1.0 : 0.0; + float offset = 0.5 * use_coc; + + vec2 tex_step = 1.0 / vec2(textureSize(tex, 0).xy); + vec2 tex_direction = tex_step * direction; + + tex_coord += tex_direction * offset; + tex_direction *= scale; + + vec4 color = vec4(0.0); + float amount = 0.0; + for (int i = 0; i < nb_samples; i++) { + vec2 coord_offset = float(i) * tex_direction; + vec4 value = textureLod(tex, tex_coord + coord_offset, lod); + float coc = texture(map, map_coord + coord_offset).r; + coc = mix(1.0, coc, use_coc); + color += vec4(ngli_srgb2linear(value.rgb), value.a) * coc; + amount += coc; + } + return color / amount; +} diff --git a/libnopegl/src/node_hblur.c b/libnopegl/src/node_hblur.c new file mode 100644 index 000000000..7cd40069f --- /dev/null +++ b/libnopegl/src/node_hblur.c @@ -0,0 +1,674 @@ +/* + * Copyright 2023-2024 Matthieu Bouron + * Copyright 2023-2024 Nope Forge + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#include "gpu_block.h" +#include "gpu_ctx.h" +#include "graphics_state.h" +#include "internal.h" +#include "log.h" +#include "math_utils.h" +#include "nopegl.h" +#include "rendertarget.h" +#include "rtt.h" +#include "topology.h" +#include "utils.h" + +/* GLSL shaders */ +#include "blur_hexagonal_vert.h" +#include "blur_hexagonal_pass1_frag.h" +#include "blur_hexagonal_pass2_frag.h" + +struct blur_params_block { + int32_t radius; + int32_t nb_samples; +}; + +struct hblur_opts { + struct ngl_node *source; + struct ngl_node *destination; + struct ngl_node *amount_node; + float amount; + struct ngl_node *map; +}; + +struct hblur_priv { + int32_t width; + int32_t height; + + struct image *image; + size_t image_rev; + + struct texture *dummy_map; + struct image dummy_map_image; + + struct image *map_image; + size_t map_rev; + + struct gpu_block blur_params_block; + + int prefered_format; + struct texture *tex0; + struct texture *tex1; + + struct { + struct rendertarget_layout layout; + struct rtt_ctx *rtt_ctx; + struct pgcraft *crafter; + struct pipeline_compat *pl; + } pass1; + + int dst_is_resizeable; + + struct { + struct rendertarget_layout layout; + struct rtt_ctx *rtt_ctx; + struct pgcraft *crafter; + struct pipeline_compat *pl; + } pass2; +}; + +#define OFFSET(x) offsetof(struct hblur_opts, x) +static const struct node_param hblur_params[] = { + {"source", NGLI_PARAM_TYPE_NODE, OFFSET(source), + .node_types=(const uint32_t[]){NGL_NODE_TEXTURE2D, NGLI_NODE_NONE}, + .flags=NGLI_PARAM_FLAG_NON_NULL | NGLI_PARAM_FLAG_DOT_DISPLAY_FIELDNAME, + .desc=NGLI_DOCSTRING("source to use for the blur")}, + {"destination", NGLI_PARAM_TYPE_NODE, OFFSET(destination), + .node_types=(const uint32_t[]){NGL_NODE_TEXTURE2D, NGLI_NODE_NONE}, + .flags=NGLI_PARAM_FLAG_NON_NULL | NGLI_PARAM_FLAG_DOT_DISPLAY_FIELDNAME, + .desc=NGLI_DOCSTRING("destination to use for the blur")}, + {"amount", NGLI_PARAM_TYPE_F32, OFFSET(amount_node), + .flags=NGLI_PARAM_FLAG_ALLOW_NODE, + .desc=NGLI_DOCSTRING("amount of bluriness in the range [0,1]")}, + {"map", NGLI_PARAM_TYPE_NODE, OFFSET(map), + .node_types=(const uint32_t[]){NGL_NODE_TEXTURE2D, NGLI_NODE_NONE}, + .flags=NGLI_PARAM_FLAG_DOT_DISPLAY_FIELDNAME, + .desc=NGLI_DOCSTRING("blur map providing the CoC (circle of confusion) for each pixels")}, + {NULL} +}; + +#define RENDER_TEXTURE_FEATURES (NGLI_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | \ + NGLI_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | \ + NGLI_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) + +static int get_prefered_format(struct gpu_ctx *gpu_ctx) +{ + static const int formats[] = { + NGLI_FORMAT_R32G32B32A32_SFLOAT, + NGLI_FORMAT_R16G16B16A16_SFLOAT, + NGLI_FORMAT_R8G8B8A8_UNORM, + }; + for (size_t i = 0; i < NGLI_ARRAY_NB(formats); i++) { + const uint32_t features = ngli_gpu_ctx_get_format_features(gpu_ctx, formats[i]); + if (NGLI_HAS_ALL_FLAGS(features, RENDER_TEXTURE_FEATURES)) + return formats[i]; + } + ngli_assert(0); +} + +#define DUMMY_MAP_SIZE 2 + +static int setup_dummy_map(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gpu_ctx *gpu_ctx = ctx->gpu_ctx; + struct hblur_priv *s = node->priv_data; + + s->dummy_map = ngli_texture_create(gpu_ctx); + if (!s->dummy_map) + return NGL_ERROR_MEMORY; + + const struct texture_params params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8_UNORM, + .width = DUMMY_MAP_SIZE, + .height = DUMMY_MAP_SIZE, + .usage = NGLI_TEXTURE_USAGE_SAMPLED_BIT | + NGLI_TEXTURE_USAGE_TRANSFER_DST_BIT, + }; + + int ret = ngli_texture_init(s->dummy_map, ¶ms); + if (ret < 0) + return ret; + + uint8_t buf[DUMMY_MAP_SIZE*DUMMY_MAP_SIZE]; + memset(buf, 255, sizeof(buf)); + ret = ngli_texture_upload(s->dummy_map, buf, 0); + if (ret < 0) + return ret; + + const struct image_params image_params = { + .width = DUMMY_MAP_SIZE, + .height = DUMMY_MAP_SIZE, + .layout = NGLI_IMAGE_LAYOUT_DEFAULT, + .color_scale = 1.f, + .color_info = { + .space = NMD_COL_SPC_BT709, + .range = NMD_COL_RNG_UNSPECIFIED, + .primaries = NMD_COL_PRI_BT709, + .transfer = NMD_COL_TRC_IEC61966_2_1, // sRGB + }, + }; + ngli_image_init(&s->dummy_map_image, &image_params, &s->dummy_map); + + return 0; +} + +static int setup_pass1_pipeline(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gpu_ctx *gpu_ctx = ctx->gpu_ctx; + struct hblur_priv *s = node->priv_data; + + static const struct pgcraft_iovar vert_out_vars[] = { + {.name = "tex_coord", .type = NGLI_TYPE_VEC2}, + {.name = "map_coord", .type = NGLI_TYPE_VEC2}, + }; + + static const struct pgcraft_texture textures[] = { + { + .name = "tex", + .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D, + .stage = NGLI_PROGRAM_SHADER_FRAG, + }, { + .name = "map", + .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D, + .stage = NGLI_PROGRAM_SHADER_FRAG, + } + }; + + const struct pgcraft_block blocks[] = { + { + .name = "blur", + .type = NGLI_TYPE_UNIFORM_BUFFER, + .stage = NGLI_PROGRAM_SHADER_FRAG, + .block = &s->blur_params_block.block, + .buffer = { + .buffer = s->blur_params_block.buffer, + .size = s->blur_params_block.block_size, + }, + } + }; + + s->pass1.crafter = ngli_pgcraft_create(ctx); + if (!s->pass1.crafter) + return NGL_ERROR_MEMORY; + + const struct pgcraft_params crafter_params = { + .program_label = "nopegl/hexagonal-blur-pass1", + .vert_base = blur_hexagonal_vert, + .frag_base = blur_hexagonal_pass1_frag, + .textures = textures, + .nb_textures = NGLI_ARRAY_NB(textures), + .blocks = blocks, + .nb_blocks = NGLI_ARRAY_NB(blocks), + .vert_out_vars = vert_out_vars, + .nb_vert_out_vars = NGLI_ARRAY_NB(vert_out_vars), + .nb_frag_output = 2, + }; + + int ret = ngli_pgcraft_craft(s->pass1.crafter, &crafter_params); + if (ret < 0) + return ret; + + s->pass1.layout = (struct rendertarget_layout) { + .nb_colors = 2, + .colors[0].format = s->prefered_format, + .colors[1].format = s->prefered_format, + }; + + const struct pipeline_compat_params params = { + .type = NGLI_PIPELINE_TYPE_GRAPHICS, + .graphics = { + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .state = NGLI_GRAPHICS_STATE_DEFAULTS, + .rt_layout = s->pass1.layout, + .vertex_state = ngli_pgcraft_get_vertex_state(s->pass1.crafter), + }, + .program = ngli_pgcraft_get_program(s->pass1.crafter), + .layout = ngli_pgcraft_get_pipeline_layout(s->pass1.crafter), + .resources = ngli_pgcraft_get_pipeline_resources(s->pass1.crafter), + .compat_info = ngli_pgcraft_get_compat_info(s->pass1.crafter), + }; + + s->pass1.pl = ngli_pipeline_compat_create(gpu_ctx); + if (!s->pass1.pl) + return NGL_ERROR_MEMORY; + + ret = ngli_pipeline_compat_init(s->pass1.pl, ¶ms); + if (ret < 0) + return ret; + + ngli_pipeline_compat_update_texture(s->pass1.pl, 1, s->dummy_map); + + return 0; +} + +static int setup_pass2_pipeline(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gpu_ctx *gpu_ctx = ctx->gpu_ctx; + struct hblur_priv *s = node->priv_data; + + static const struct pgcraft_iovar vert_out_vars[] = { + {.name = "tex_coord", .type = NGLI_TYPE_VEC2}, + {.name = "map_coord", .type = NGLI_TYPE_VEC2}, + }; + + static const struct pgcraft_texture textures[] = { + { + .name = "tex0", + .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D, + .stage = NGLI_PROGRAM_SHADER_FRAG, + }, { + .name = "tex1", + .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D, + .stage = NGLI_PROGRAM_SHADER_FRAG, + }, { + .name = "map", + .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_2D, + .stage = NGLI_PROGRAM_SHADER_FRAG, + } + }; + + const struct pgcraft_block crafter_blocks[] = { + { + .name = "blur", + .type = NGLI_TYPE_UNIFORM_BUFFER, + .stage = NGLI_PROGRAM_SHADER_FRAG, + .block = &s->blur_params_block.block, + .buffer = { + .buffer = s->blur_params_block.buffer, + .size = s->blur_params_block.block_size, + }, + }, + }; + + const struct pgcraft_params crafter_params = { + .program_label = "nopegl/hexagonal-blur-pass2", + .vert_base = blur_hexagonal_vert, + .frag_base = blur_hexagonal_pass2_frag, + .textures = textures, + .nb_textures = NGLI_ARRAY_NB(textures), + .blocks = crafter_blocks, + .nb_blocks = NGLI_ARRAY_NB(crafter_blocks), + .vert_out_vars = vert_out_vars, + .nb_vert_out_vars = NGLI_ARRAY_NB(vert_out_vars), + }; + + s->pass2.crafter = ngli_pgcraft_create(ctx); + if (!s->pass2.crafter) + return NGL_ERROR_MEMORY; + + int ret = ngli_pgcraft_craft(s->pass2.crafter, &crafter_params); + if (ret < 0) + return ret; + + s->pass2.pl = ngli_pipeline_compat_create(gpu_ctx); + if (!s->pass2.pl) + return NGL_ERROR_MEMORY; + + const struct pipeline_compat_params params = { + .type = NGLI_PIPELINE_TYPE_GRAPHICS, + .graphics = { + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .state = NGLI_GRAPHICS_STATE_DEFAULTS, + .rt_layout = s->pass2.layout, + .vertex_state = ngli_pgcraft_get_vertex_state(s->pass2.crafter), + }, + .program = ngli_pgcraft_get_program(s->pass2.crafter), + .layout = ngli_pgcraft_get_pipeline_layout(s->pass2.crafter), + .resources = ngli_pgcraft_get_pipeline_resources(s->pass2.crafter), + .compat_info = ngli_pgcraft_get_compat_info(s->pass2.crafter), + }; + + ret = ngli_pipeline_compat_init(s->pass2.pl, ¶ms); + if (ret < 0) + return ret; + + const int32_t tex0_coord_matrix_index = ngli_pgcraft_get_uniform_index(s->pass2.crafter, "tex0_coord_matrix", NGLI_PROGRAM_SHADER_VERT); + ngli_assert(tex0_coord_matrix_index >= 0); + + const int32_t tex1_coord_matrix_index = ngli_pgcraft_get_uniform_index(s->pass2.crafter, "tex1_coord_matrix", NGLI_PROGRAM_SHADER_VERT); + ngli_assert(tex1_coord_matrix_index >= 0); + + NGLI_ALIGNED_MAT(tmp_coord_matrix) = NGLI_MAT4_IDENTITY; + ngli_pipeline_compat_update_uniform(s->pass2.pl, tex0_coord_matrix_index, tmp_coord_matrix); + ngli_pipeline_compat_update_uniform(s->pass2.pl, tex1_coord_matrix_index, tmp_coord_matrix); + + ngli_pipeline_compat_update_texture(s->pass2.pl, 2, s->dummy_map); + + return 0; +} + +static int hblur_init(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gpu_ctx *gpu_ctx = ctx->gpu_ctx; + struct hblur_priv *s = node->priv_data; + struct hblur_opts *o = node->opts; + + struct texture_priv *src_priv = o->source->priv_data; + s->image = &src_priv->image; + s->image_rev = SIZE_MAX; + + /* Disable direct rendering */ + src_priv->supported_image_layouts = 1U << NGLI_IMAGE_LAYOUT_DEFAULT; + + /* Override texture params */ + src_priv->params.min_filter = NGLI_FILTER_LINEAR; + src_priv->params.mag_filter = NGLI_FILTER_LINEAR; + src_priv->params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; + + s->map_image = &s->dummy_map_image; + s->map_rev = SIZE_MAX; + if (o->map) { + struct texture_priv *map_priv = (struct texture_priv *)o->map->priv_data; + + /* Disable direct rendering */ + map_priv->supported_image_layouts = 1U << NGLI_IMAGE_LAYOUT_DEFAULT; + + /* Override texture params */ + map_priv->params.min_filter = NGLI_FILTER_LINEAR; + map_priv->params.mag_filter = NGLI_FILTER_LINEAR; + s->map_image = &map_priv->image; + } + + s->prefered_format = get_prefered_format(ctx->gpu_ctx); + + struct texture_priv *dst_priv = o->destination->priv_data; + dst_priv->params.usage |= NGLI_TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + + s->dst_is_resizeable = (dst_priv->params.width == 0 && dst_priv->params.height == 0); + s->pass2.layout.colors[0].format = dst_priv->params.format; + s->pass2.layout.nb_colors = 1; + + const struct gpu_block_field block_fields[] = { + NGLI_GPU_BLOCK_FIELD(struct blur_params_block, radius, NGLI_TYPE_I32, 0), + NGLI_GPU_BLOCK_FIELD(struct blur_params_block, nb_samples, NGLI_TYPE_I32, 0), + }; + + const struct gpu_block_params block_params = { + .fields = block_fields, + .nb_fields = NGLI_ARRAY_NB(block_fields), + }; + + int ret = ngli_gpu_block_init(gpu_ctx, &s->blur_params_block, &block_params); + if (ret < 0) + return ret; + + if ((ret = setup_dummy_map(node)) < 0 || + (ret = setup_pass1_pipeline(node)) < 0 || + (ret = setup_pass2_pipeline(node)) < 0) + return ret; + + return 0; +} + +static int resize(struct ngl_node *node) +{ + int ret = 0; + struct ngl_ctx *ctx = node->ctx; + struct hblur_priv *s = node->priv_data; + struct hblur_opts *o = node->opts; + + ngli_node_draw(o->source); + if (o->map) + ngli_node_draw(o->map); + + struct texture_priv *src_priv = o->source->priv_data; + const int32_t width = src_priv->image.params.width; + const int32_t height = src_priv->image.params.height; + if (s->width == width && s->height == height) + return 0; + + struct texture *dst = NULL; + struct texture *tex0 = ngli_texture_create(ctx->gpu_ctx); + struct texture *tex1 = ngli_texture_create(ctx->gpu_ctx); + struct rtt_ctx *pass1_rtt_ctx = ngli_rtt_create(ctx); + struct rtt_ctx *pass2_rtt_ctx = ngli_rtt_create(ctx); + if (!tex0 || !tex1 || !pass1_rtt_ctx || !pass2_rtt_ctx) { + ret = NGL_ERROR_MEMORY; + goto fail; + } + + struct texture_params texture_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = s->prefered_format, + .width = width, + .height = height, + .min_filter = NGLI_FILTER_LINEAR, + .mag_filter = NGLI_FILTER_LINEAR, + .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, + .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, + .usage = NGLI_TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | + NGLI_TEXTURE_USAGE_SAMPLED_BIT, + }; + + ret = ngli_texture_init(tex0, &texture_params); + if (ret < 0) + goto fail; + + ret = ngli_texture_init(tex1, &texture_params); + if (ret < 0) + goto fail; + + struct rtt_params pass1_rtt_params = { + .width = width, + .height = height, + .nb_colors = 2, + .colors[0] = { + .attachment = tex0, + .store_op = NGLI_STORE_OP_STORE + }, + .colors[1] = { + .attachment = tex1, + .store_op = NGLI_STORE_OP_STORE + }, + }; + + ret = ngli_rtt_init(pass1_rtt_ctx, &pass1_rtt_params); + if (ret < 0) + goto fail; + + /* Assert that the destination texture format does not change */ + struct texture_priv *dst_priv = o->destination->priv_data; + ngli_assert(dst_priv->params.format == s->pass2.layout.colors[0].format); + + dst = dst_priv->texture; + if (s->dst_is_resizeable) { + dst = ngli_texture_create(ctx->gpu_ctx); + if (!dst) { + ret = NGL_ERROR_MEMORY; + goto fail; + } + + struct texture_params params = dst_priv->params; + params.width = width; + params.height = height; + ret = ngli_texture_init(dst, ¶ms); + if (ret < 0) + goto fail; + } + + pass2_rtt_ctx = ngli_rtt_create(ctx); + if (!pass2_rtt_ctx) { + ret = NGL_ERROR_MEMORY; + goto fail; + } + + const struct rtt_params pass2_rtt_params = { + .width = dst->params.width, + .height = dst->params.height, + .nb_colors = 1, + .colors[0] = { + .attachment = dst, + .load_op = NGLI_LOAD_OP_CLEAR, + .store_op = NGLI_STORE_OP_STORE, + }, + }; + + ret = ngli_rtt_init(pass2_rtt_ctx, &pass2_rtt_params); + if (ret < 0) + goto fail; + + ngli_rtt_freep(&s->pass1.rtt_ctx); + s->pass1.rtt_ctx = pass1_rtt_ctx; + + ngli_texture_freep(&s->tex0); + s->tex0 = tex0; + + ngli_texture_freep(&s->tex1); + s->tex1 = tex1; + + ngli_rtt_freep(&s->pass2.rtt_ctx); + s->pass2.rtt_ctx = pass2_rtt_ctx; + + ngli_pipeline_compat_update_texture(s->pass2.pl, 0, s->tex0); + ngli_pipeline_compat_update_texture(s->pass2.pl, 1, s->tex1); + + if (s->dst_is_resizeable) { + ngli_texture_freep(&dst_priv->texture); + dst_priv->texture = dst; + dst_priv->image.params.width = dst->params.width; + dst_priv->image.params.height = dst->params.height; + dst_priv->image.planes[0] = dst; + dst_priv->image.rev = dst_priv->image_rev++; + } + + s->width = width; + s->height = height; + + return 0; + +fail: + ngli_texture_freep(&tex0); + ngli_texture_freep(&tex1); + ngli_rtt_freep(&pass1_rtt_ctx); + + ngli_rtt_freep(&pass2_rtt_ctx); + if (s->dst_is_resizeable) + ngli_texture_freep(&dst); + + LOG(ERROR, "failed to resize blur: %dx%d", width, height); + return ret; +} + +#define MAX_SAMPLES 32 + +static void hblur_draw(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gpu_ctx *gpu_ctx = ctx->gpu_ctx; + struct hblur_priv *s = node->priv_data; + struct hblur_opts *o = node->opts; + + int ret = resize(node); + if (ret < 0) + return; + + const float amount_raw = *(float *)ngli_node_get_data_ptr(o->amount_node, &o->amount); + float amount = NGLI_CLAMP(amount_raw, 0.f, 1.f); + const float diagonal = hypotf((float)s->width, (float)s->height); + const int32_t radius = (int32_t)(amount * (float)(diagonal) * 0.05f); + const int32_t nb_samples = NGLI_MIN(radius, MAX_SAMPLES); + + ngli_gpu_block_update(&s->blur_params_block, 0, &(struct blur_params_block) { + .radius = radius, + .nb_samples = nb_samples, + }); + + ngli_rtt_begin(s->pass1.rtt_ctx); + ngli_gpu_ctx_begin_render_pass(gpu_ctx, ctx->current_rendertarget); + ctx->render_pass_started = 1; + if (s->image_rev != s->image->rev) { + ngli_pipeline_compat_update_image(s->pass1.pl, 0, s->image); + s->image_rev = s->image->rev; + } + if (s->map_rev != s->map_image->rev) { + ngli_pipeline_compat_update_image(s->pass1.pl, 1, s->map_image); + s->image_rev = s->map_image->rev; + } + ngli_pipeline_compat_draw(s->pass1.pl, 3, 1); + ngli_rtt_end(s->pass1.rtt_ctx); + + ngli_rtt_begin(s->pass2.rtt_ctx); + ngli_gpu_ctx_begin_render_pass(gpu_ctx, ctx->current_rendertarget); + ctx->render_pass_started = 1; + if (s->map_rev != s->map_image->rev) { + ngli_pipeline_compat_update_image(s->pass2.pl, 2, s->map_image); + s->image_rev = s->map_image->rev; + } + ngli_pipeline_compat_draw(s->pass2.pl, 3, 1); + ngli_rtt_end(s->pass2.rtt_ctx); + + /* + * The blur render passes do not deal with the texture coordinates at all, + * thus we need to forward the source coordinates matrix to the + * destination. + */ + struct texture_priv *dst_priv = (struct texture_priv *)o->destination->priv_data; + struct image *dst_image = &dst_priv->image; + memcpy(dst_image->coordinates_matrix, s->image->coordinates_matrix, sizeof(s->image->coordinates_matrix)); +} + +static void hblur_release(struct ngl_node *node) +{ + struct hblur_priv *s = node->priv_data; + + ngli_texture_freep(&s->tex0); + ngli_texture_freep(&s->tex1); + ngli_rtt_freep(&s->pass1.rtt_ctx); + ngli_rtt_freep(&s->pass2.rtt_ctx); +} + +static void hblur_uninit(struct ngl_node *node) +{ + struct hblur_priv *s = node->priv_data; + + ngli_gpu_block_reset(&s->blur_params_block); + ngli_texture_freep(&s->dummy_map); + ngli_pipeline_compat_freep(&s->pass2.pl); + ngli_pipeline_compat_freep(&s->pass1.pl); + ngli_pgcraft_freep(&s->pass1.crafter); + ngli_pgcraft_freep(&s->pass2.crafter); +} + +const struct node_class ngli_hblur_class = { + .id = NGL_NODE_HEXAGONALBLUR, + .name = "HexagonalBlur", + .init = hblur_init, + .prepare = ngli_node_prepare_children, + .update = ngli_node_update_children, + .draw = hblur_draw, + .release = hblur_release, + .uninit = hblur_uninit, + .opts_size = sizeof(struct hblur_opts), + .priv_size = sizeof(struct hblur_priv), + .params = hblur_params, + .file = __FILE__, +}; diff --git a/libnopegl/src/nodes_register.h b/libnopegl/src/nodes_register.h index 532d03e12..552cdb8dd 100644 --- a/libnopegl/src/nodes_register.h +++ b/libnopegl/src/nodes_register.h @@ -108,6 +108,7 @@ action(NGL_NODE_GRAPHICCONFIG, ngli_graphicconfig_class) \ action(NGL_NODE_GRIDLAYOUT, ngli_gridlayout_class) \ action(NGL_NODE_GROUP, ngli_group_class) \ + action(NGL_NODE_HEXAGONALBLUR, ngli_hblur_class) \ action(NGL_NODE_IDENTITY, ngli_identity_class) \ action(NGL_NODE_IOINT, ngli_ioint_class) \ action(NGL_NODE_IOIVEC2, ngli_ioivec2_class) \ diff --git a/libnopegl/src/nopegl.h.in b/libnopegl/src/nopegl.h.in index 372d89d86..b4d205736 100644 --- a/libnopegl/src/nopegl.h.in +++ b/libnopegl/src/nopegl.h.in @@ -345,6 +345,7 @@ NGL_API void ngl_scene_unrefp(struct ngl_scene **sp); #define NGL_NODE_GRAPHICCONFIG NGLI_FOURCC('G','r','C','f') #define NGL_NODE_GRIDLAYOUT NGLI_FOURCC('G','r','d','L') #define NGL_NODE_GROUP NGLI_FOURCC('G','r','p',' ') +#define NGL_NODE_HEXAGONALBLUR NGLI_FOURCC('H','G','B','l') #define NGL_NODE_IDENTITY NGLI_FOURCC('I','d',' ',' ') #define NGL_NODE_IOINT NGLI_FOURCC('I','O','i','1') #define NGL_NODE_IOIVEC2 NGLI_FOURCC('I','O','i','2') diff --git a/tests/blur.py b/tests/blur.py index a0577c175..1940158d0 100644 --- a/tests/blur.py +++ b/tests/blur.py @@ -19,7 +19,12 @@ # under the License. # +import textwrap + +from pynopegl_utils.misc import load_media +from pynopegl_utils.tests.cmp_cuepoints import test_cuepoints from pynopegl_utils.tests.cmp_fingerprint import test_fingerprint +from pynopegl_utils.tests.cuepoints_utils import get_grid_points, get_points_nodes import pynopegl as ngl @@ -66,3 +71,100 @@ def blur_fast_gaussian(cfg: ngl.SceneCfg): ), ) return ngl.Group(children=(blur, ngl.DrawTexture(blurred_texture))) + + +_BLUR_HEXAGONAL_CUEPOINTS = get_grid_points(10, 10) + + +@test_cuepoints(width=540, height=808, points=_BLUR_HEXAGONAL_CUEPOINTS, keyframes=5, tolerance=5) +@ngl.scene(controls=dict(show_dbg_points=ngl.scene.Bool())) +def blur_hexagonal(cfg: ngl.SceneCfg, show_dbg_points=False): + mi = load_media("city") + cfg.aspect_ratio = (mi.width, mi.height) + cfg.duration = 5 + + source_texture = ngl.Texture2D(data_src=ngl.Media(filename=mi.filename)) + blurred_texture = ngl.Texture2D() + blur = ngl.HexagonalBlur( + source=source_texture, + destination=blurred_texture, + amount=ngl.AnimatedFloat( + [ + ngl.AnimKeyFrameFloat(0, 0), + ngl.AnimKeyFrameFloat(cfg.duration, 1), + ] + ), + ) + + group = ngl.Group(children=(blur, ngl.DrawTexture(blurred_texture))) + if show_dbg_points: + group.add_children(get_points_nodes(cfg, _BLUR_HEXAGONAL_CUEPOINTS)) + + return group + + +_BLUR_HEXAGONAL_MAP_VERTEX = textwrap.dedent( + """ + void main() + { + vec4 position = ngl_projection_matrix * ngl_modelview_matrix * vec4(ngl_position, 1.0); + uv = position.xy; + ngl_out_pos = position; + } + """ +) + +_BLUR_HEXAGONAL_MAP_FRAGMENT = textwrap.dedent( + """ + #define ngli_sat(x) clamp(x, 0.0, 1.0) + #define ngli_linear(a, b, x) (((x) - (a)) / ((b) - (a))) + #define ngli_linearstep(a, b, x) ngli_sat(ngli_linear(a, b, x)) + + float sd_rounded_box(vec2 position, vec2 size, float radius) + { + position = abs(position) - size + radius; + return length(max(position, 0.0)) + min(max(position.x, position.y), 0.0) - radius; + } + + void main() + { + float sd = sd_rounded_box(uv + vec2(-0.016, 0.11), vec2(0.19, 0.19), 0.13); + float value = ngli_linearstep(0.0, 0.8, sd); + ngl_out_color = vec4(value); + } + """ +) + + +@test_cuepoints(width=540, height=808, points=_BLUR_HEXAGONAL_CUEPOINTS, keyframes=5, tolerance=5) +@ngl.scene(controls=dict(show_dbg_points=ngl.scene.Bool())) +def blur_hexagonal_with_map(cfg: ngl.SceneCfg, show_dbg_points=False): + mi = load_media("city") + cfg.aspect_ratio = (mi.width, mi.height) + cfg.duration = 5 + + quad = ngl.Quad(corner=(-1, -1, 0), width=(2, 0, 0), height=(0, 2, 0)) + program = ngl.Program(vertex=_BLUR_HEXAGONAL_MAP_VERTEX, fragment=_BLUR_HEXAGONAL_MAP_FRAGMENT) + program.update_vert_out_vars(uv=ngl.IOVec2()) + render = ngl.Draw(quad, program) + map_texture = ngl.Texture2D(width=mi.width // 2, height=mi.height // 2, format="r8_unorm", data_src=render) + + source_texture = ngl.Texture2D(data_src=ngl.Media(filename=mi.filename)) + blurred_texture = ngl.Texture2D() + blur = ngl.HexagonalBlur( + source=source_texture, + destination=blurred_texture, + amount=ngl.AnimatedFloat( + [ + ngl.AnimKeyFrameFloat(0, 0), + ngl.AnimKeyFrameFloat(cfg.duration, 1), + ] + ), + map=map_texture, + ) + + group = ngl.Group(children=(blur, ngl.DrawTexture(blurred_texture))) + if show_dbg_points: + group.add_children(get_points_nodes(cfg, _BLUR_HEXAGONAL_CUEPOINTS)) + + return group diff --git a/tests/meson.build b/tests/meson.build index 6b5877fa8..b1376894f 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -92,6 +92,8 @@ foreach backend : backends tests_blur = [ 'gaussian', 'fast_gaussian', + 'hexagonal', + 'hexagonal_with_map', ] tests_color = [ diff --git a/tests/refs/blur_hexagonal.ref b/tests/refs/blur_hexagonal.ref new file mode 100644 index 000000000..c1453e728 --- /dev/null +++ b/tests/refs/blur_hexagonal.ref @@ -0,0 +1,5 @@ +00:EC8F26FF 01:E2861CFF 02:CD6C05FF 03:8A4D1AFF 04:745A42FF 05:2B271EFF 06:2C2820FF 07:1F211EFF 08:1A2121FF 09:162424FF 10:162120FF 11:20211BFF 12:956134FF 13:DCA980FF 14:A35517FF 15:734520FF 16:42301EFF 17:20241EFF 18:172321FF 19:162424FF 20:19201EFF 21:A59377FF 22:412916FF 23:694013FF 24:C18346FF 25:C38016FF 26:B7741EFF 27:D7D3DCFF 28:6B3716FF 29:182425FF 30:3C2D1DFF 31:EADAC5FF 32:99491DFF 33:944911FF 34:B95102FF 35:F4B860FF 36:7B4117FF 37:FF9D16FF 38:192524FF 39:362D1DFF 40:1D2122FF 41:2A2524FF 42:936A41FF 43:DE701EFF 44:D37520FF 45:FFFFFFFF 46:D46C09FF 47:35281EFF 48:9D540AFF 49:292721FF 50:1B2222FF 51:17241FFF 52:181F1EFF 53:181E1AFF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:916330FF 58:392B1BFF 59:1B2321FF 60:1A2224FF 61:171E1EFF 62:505238FF 63:E9D6C2FF 64:A59175FF 65:FFFFFFFF 66:008C9FFF 67:013E49FF 68:1B2024FF 69:192527FF 70:162425FF 71:015955FF 72:106A6AFF 73:96A795FF 74:A59B51FF 75:5CA5A6FF 76:FFFFFFFF 77:073643FF 78:102E26FF 79:192526FF 80:172324FF 81:022B2FFF 82:0B3844FF 83:03474AFF 84:17261FFF 85:02B1DAFF 86:77D8DFFF 87:2C534AFF 88:233D37FF 89:1A2125FF 90:172324FF 91:1A2024FF 92:1A2024FF 93:0C2627FF 94:093139FF 95:003B4BFF 96:132424FF 97:1D2424FF 98:1A2024FF 99:1B2125FF +00:E68925FF 01:D87B15FF 02:C86A08FF 03:975720FF 04:745A42FF 05:4E3F30FF 06:332D22FF 07:222520FF 08:1B2221FF 09:182423FF 10:192221FF 11:615748FF 12:A68C73FF 13:DCAA80FF 14:A85C1DFF 15:7D512AFF 16:563922FF 17:352A1FFF 18:1F2421FF 19:192424FF 20:1A2120FF 21:857560FF 22:675340FF 23:9D6C3DFF 24:C68A4DFF 25:CC8922FF 26:C08128FF 27:B79C90FF 28:703B18FF 29:182325FF 30:3A2C1DFF 31:B19F8EFF 32:824623FF 33:964912FF 34:BE632AFF 35:DEB587FF 36:B16929FF 37:FFA326FF 38:192423FF 39:4E3821FF 40:1D2321FF 41:4D3723FF 42:785232FF 43:D76B1AFF 44:F5B067FF 45:FFF7E8FF 46:DC9B60FF 47:874C1BFF 48:924A14FF 49:2C2A20FF 50:1A2323FF 51:1B2724FF 52:1D221FFF 53:353128FF 54:FFFFD8FF 55:FFFAEEFF 56:DABB89FF 57:9A6735FF 58:644323FF 59:272A22FF 60:1A2323FF 61:2B332AFF 62:50513BFF 63:D6C7B2FF 64:B4B1A3FF 65:FFFFFFFF 66:057288FF 67:04404DFF 68:182526FF 69:192525FF 70:1B2B2AFF 71:216461FF 72:116464FF 73:98AC9CFF 74:D1C39AFF 75:5C9EA0FF 76:FFFFFFFF 77:075862FF 78:132A26FF 79:1B2427FF 80:172324FF 81:0D2E30FF 82:074D5CFF 83:105E60FF 84:3D493BFF 85:81CBE3FF 86:74D9DFFF 87:35615AFF 88:255755FF 89:1A2426FF 90:172324FF 91:1B2124FF 92:182224FF 93:0F2A29FF 94:05373DFF 95:023F4DFF 96:162526FF 97:1C2324FF 98:1B2426FF 99:1C2225FF +00:D17B21FF 01:CB7317FF 02:B6610DFF 03:8A4F1FFF 04:66513CFF 05:564634FF 06:453B2EFF 07:373228FF 08:302F27FF 09:292C25FF 10:222520FF 11:655A49FF 12:99846DFF 13:CD9D73FF 14:AF6E39FF 15:84582FFF 16:5E3F25FF 17:413021FF 18:282621FF 19:1A2424FF 20:1F2421FF 21:8F7E68FF 22:856F5AFF 23:9F6C3CFF 24:BC824AFF 25:C48327FF 26:D0994AFF 27:816254FF 28:80441AFF 29:1A2324FF 30:503D2AFF 31:AD9685FF 32:7E4C2BFF 33:9A4C16FF 34:D5905EFF 35:DFBD91FF 36:C7822EFF 37:E9881CFF 38:2F2921FF 39:583B20FF 40:1F2320FF 41:4F3A25FF 42:714D2EFF 43:D46A1BFF 44:F7BE7EFF 45:FFFFF2FF 46:D4A774FF 47:844A1CFF 48:7B4119FF 49:282821FF 50:192324FF 51:1D2723FF 52:242520FF 53:564535FF 54:FFFFD5FF 55:FFFBECFF 56:CFAD76FF 57:905D30FF 58:6A4928FF 59:272A22FF 60:1C2425FF 61:333D31FF 62:51533FFF 63:BCB19DFF 64:88887BFF 65:D5DFDBFF 66:136E81FF 67:074B59FF 68:192525FF 69:1A2624FF 70:1B2A28FF 71:226260FF 72:16605EFF 73:8BA596FF 74:A69F78FF 75:559497FF 76:F3FFFFFF 77:076771FF 78:142E2CFF 79:1B2526FF 80:172425FF 81:0F2D2FFF 82:096B7EFF 83:177176FF 84:5E6755FF 85:ACD2E2FF 86:5CC5CDFF 87:576F66FF 88:3E6D6BFF 89:1A2426FF 90:172324FF 91:1B2124FF 92:182224FF 93:122928FF 94:0A383DFF 95:084755FF 96:284B4EFF 97:1B2626FF 98:1C2928FF 99:1A2326FF +00:C77722FF 01:B96917FF 02:A55810FF 03:7A4720FF 04:5C4936FF 05:564635FF 06:4E4234FF 07:413B2FFF 08:38352BFF 09:2C2E26FF 10:232520FF 11:695C49FF 12:907D68FF 13:B48861FF 14:A0683AFF 15:82542BFF 16:583B23FF 17:3D2E21FF 18:262621FF 19:1A2424FF 20:292A25FF 21:8E7D68FF 22:8B755EFF 23:9B6839FF 24:B67E47FF 25:C08129FF 26:CC9546FF 27:744F3CFF 28:7A421CFF 29:202524FF 30:553E2BFF 31:A18C7BFF 32:805132FF 33:9B4F1CFF 34:D4905BFF 35:D7B080FF 36:C18235FF 37:D2791FFF 38:452E20FF 39:523820FF 40:1F2320FF 41:4A3724FF 42:674529FF 43:D0681BFF 44:F4BF80FF 45:FFF7E1FF 46:CFA775FF 47:8A4D1CFF 48:6D3B1BFF 49:2D2A21FF 50:1A2324FF 51:1C2422FF 52:2C2820FF 53:694F3DFF 54:FFF8D1FF 55:FFF1DCFF 56:CEAC76FF 57:8B5A30FF 58:704C29FF 59:2B2B22FF 60:1C2525FF 61:324236FF 62:515541FF 63:AFA792FF 64:8A8A7BFF 65:AEB7B1FF 66:68959EFF 67:134A56FF 68:32312AFF 69:1A2624FF 70:1B2928FF 71:215E5AFF 72:1C5F5DFF 73:7B9B8FFF 74:7C7E61FF 75:488488FF 76:EDFFFFFF 77:337780FF 78:132F2EFF 79:1B2426FF 80:172425FF 81:102C2EFF 82:086F84FF 83:1C767FFF 84:667261FF 85:AED2E0FF 86:51B4BDFF 87:4C655FFF 88:3B6360FF 89:1A2427FF 90:172224FF 91:1B2124FF 92:17262AFF 93:132B29FF 94:0B383EFF 95:235B67FF 96:244748FF 97:202E2CFF 98:1D2C29FF 99:192326FF +00:C47623FF 01:B26416FF 02:9D5312FF 03:744624FF 04:5C4936FF 05:584838FF 06:504436FF 07:423B30FF 08:36342BFF 09:2B2D26FF 10:222520FF 11:675A48FF 12:8D7963FF 13:9D7653FF 14:926137FF 15:805229FF 16:5A3B22FF 17:3C2D21FF 18:272621FF 19:1A2424FF 20:34332BFF 21:92816CFF 22:927A62FF 23:936338FF 24:B27B44FF 25:C28331FF 26:C28A3DFF 27:7A4C32FF 28:6A3B1CFF 29:252724FF 30:5D4C3DFF 31:9C8877FF 32:81583FFF 33:A25622FF 34:D1905BFF 35:D6AD78FF 36:BA7F36FF 37:BD6B1DFF 38:56341EFF 39:4B3421FF 40:242521FF 41:473524FF 42:644127FF 43:C66219FF 44:ECB97EFF 45:FBE9D0FF 46:CDA572FF 47:904F1CFF 48:6D3B1BFF 49:3D3021FF 50:1A2324FF 51:212622FF 52:322A20FF 53:765742FF 54:FFEBC4FF 55:FDECD4FF 56:D3B383FF 57:8A5D36FF 58:6E4C2AFF 59:2D2B22FF 60:1C2625FF 61:31453BFF 62:525844FF 63:A6A08CFF 64:9C9A8CFF 65:B4B7ABFF 66:93B2B6FF 67:445A5EFF 68:3A362AFF 69:1F2724FF 70:1B2828FF 71:225853FF 72:23605DFF 73:709389FF 74:68715AFF 75:508689FF 76:E5FBFEFF 77:57838AFF 78:132F2FFF 79:1B2426FF 80:172425FF 81:102C2EFF 82:086B80FF 83:1E727BFF 84:5B6C5EFF 85:A3C6D3FF 86:47A4ADFF 87:415C57FF 88:385954FF 89:192527FF 90:172324FF 91:1A2224FF 92:172F39FF 93:153334FF 94:193D40FF 95:5D7880FF 96:2C5051FF 97:223330FF 98:1E2E2BFF 99:192325FF diff --git a/tests/refs/blur_hexagonal_with_map.ref b/tests/refs/blur_hexagonal_with_map.ref new file mode 100644 index 000000000..6ae1687a2 --- /dev/null +++ b/tests/refs/blur_hexagonal_with_map.ref @@ -0,0 +1,5 @@ +00:EC8F26FF 01:E2861CFF 02:CD6C05FF 03:8A4D1AFF 04:745A42FF 05:2B271EFF 06:2C2820FF 07:1F211EFF 08:1A2121FF 09:162424FF 10:162120FF 11:20211BFF 12:956134FF 13:DCA980FF 14:A35517FF 15:734520FF 16:42301EFF 17:20241EFF 18:172321FF 19:162424FF 20:19201EFF 21:A59377FF 22:412916FF 23:694013FF 24:C18346FF 25:C38016FF 26:B7741EFF 27:D7D3DCFF 28:6B3716FF 29:182425FF 30:3C2D1DFF 31:EADAC5FF 32:99491DFF 33:944911FF 34:B95102FF 35:F4B860FF 36:7B4117FF 37:FF9D16FF 38:192524FF 39:362D1DFF 40:1D2122FF 41:2A2524FF 42:936A41FF 43:DE701EFF 44:D37520FF 45:FFFFFFFF 46:D46C09FF 47:35281EFF 48:9D540AFF 49:292721FF 50:1B2222FF 51:17241FFF 52:181F1EFF 53:181E1AFF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:916330FF 58:392B1BFF 59:1B2321FF 60:1A2224FF 61:171E1EFF 62:505238FF 63:E9D6C2FF 64:A59175FF 65:FFFFFFFF 66:008C9FFF 67:013E49FF 68:1B2024FF 69:192527FF 70:162425FF 71:015955FF 72:106A6AFF 73:96A795FF 74:A59B51FF 75:5CA5A6FF 76:FFFFFFFF 77:073643FF 78:102E26FF 79:192526FF 80:172324FF 81:022B2FFF 82:0B3844FF 83:03474AFF 84:17261FFF 85:02B1DAFF 86:77D8DFFF 87:2C534AFF 88:233D37FF 89:1A2125FF 90:172324FF 91:1A2024FF 92:1A2024FF 93:0C2627FF 94:093139FF 95:003B4BFF 96:132424FF 97:1D2424FF 98:1A2024FF 99:1B2125FF +00:E68925FF 01:D87B15FF 02:C86A08FF 03:985721FF 04:735942FF 05:4A3C2DFF 06:312C21FF 07:212420FF 08:1B2221FF 09:182423FF 10:192221FF 11:615748FF 12:A68B72FF 13:DDAA80FF 14:A85B1DFF 15:724621FF 16:4B331FFF 17:2E271EFF 18:1D2321FF 19:192424FF 20:1A2120FF 21:83735EFF 22:47321FFF 23:906337FF 24:C6894BFF 25:C28018FF 26:BA7821FF 27:DACBC4FF 28:693817FF 29:182325FF 30:3A2C1DFF 31:B6A696FF 32:8A441DFF 33:954913FF 34:BA5104FF 35:F9BC67FF 36:7B3F15FF 37:FFA31EFF 38:192423FF 39:493620FF 40:1D2321FF 41:4C3724FF 42:8C643EFF 43:DC6E1CFF 44:D37520FF 45:FFFFFFFF 46:D46C09FF 47:39291EFF 48:9C4F11FF 49:2D2A20FF 50:1A2323FF 51:192522FF 52:1B221FFF 53:1A211EFF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:9D6C39FF 58:654424FF 59:282A23FF 60:1A2323FF 61:242C25FF 62:52533DFF 63:E4D1BDFF 64:AE9C82FF 65:FFFFFFFF 66:038B9FFF 67:03414CFF 68:182425FF 69:192525FF 70:1B2B2AFF 71:1E6461FF 72:0E6364FF 73:9BAD9CFF 74:BCB07AFF 75:5CA2A3FF 76:FFFFFFFF 77:054953FF 78:132A25FF 79:1B2427FF 80:172324FF 81:0D2E30FF 82:074A58FF 83:105759FF 84:334035FF 85:31BBDEFF 86:73D7DEFF 87:305A51FF 88:274F4CFF 89:1A2426FF 90:172324FF 91:1B2124FF 92:182224FF 93:0F2A2AFF 94:04373DFF 95:03404FFF 96:162425FF 97:1C2324FF 98:1B2426FF 99:1C2225FF +00:D17B21FF 01:CB7317FF 02:B6610DFF 03:8C501FFF 04:69533DFF 05:564534FF 06:42382BFF 07:363228FF 08:302F27FF 09:292C25FF 10:222520FF 11:655A49FF 12:9B866FFF 13:D6A57BFF 14:AB6126FF 15:80532BFF 16:5C3D24FF 17:402F21FF 18:272621FF 19:1A2424FF 20:1F2421FF 21:8E7D67FF 22:7A6651FF 23:9F6D3EFF 24:C88B4EFF 25:CB8821FF 26:BC7C23FF 27:B4988CFF 28:7A4019FF 29:192324FF 30:503D2AFF 31:B29D8CFF 32:804523FF 33:964812FF 34:BA5205FF 35:F2B361FF 36:814011FF 37:FFA625FF 38:1B2423FF 39:553A21FF 40:1F2320FF 41:513B26FF 42:785231FF 43:D86B1AFF 44:D37520FF 45:FFFFFFFF 46:D46C09FF 47:623C1EFF 48:924A14FF 49:2A2921FF 50:192324FF 51:1D2824FF 52:1D221FFF 53:2A2E2AFF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:A67039FF 58:644323FF 59:272A22FF 60:1C2425FF 61:323B30FF 62:50513BFF 63:E1CFBBFF 64:B8A890FF 65:FFFFFFFF 66:078BA0FF 67:05424EFF 68:182525FF 69:1A2625FF 70:1B2A28FF 71:21625FFF 72:126261FF 73:98AC9BFF 74:CFC29AFF 75:5B9EA0FF 76:FFFFFFFF 77:075962FF 78:142B26FF 79:1B2526FF 80:172425FF 81:0F2D2FFF 82:096578FF 83:136A6DFF 84:424E3DFF 85:95CFE5FF 86:70D6DDFF 87:516D65FF 88:3B6B69FF 89:1A2426FF 90:172324FF 91:1B2124FF 92:182224FF 93:112928FF 94:0A373CFF 95:064452FF 96:264547FF 97:1A2525FF 98:1C2928FF 99:1A2326FF +00:C77722FF 01:B96917FF 02:A55810FF 03:7C4820FF 04:5D4A37FF 05:564635FF 06:4C4132FF 07:403A2FFF 08:38352BFF 09:2C2E26FF 10:232520FF 11:695C49FF 12:928069FF 13:C7976EFF 14:AE6E3AFF 15:84582FFF 16:5E3F26FF 17:413021FF 18:262621FF 19:1A2424FF 20:292A25FF 21:8E7D68FF 22:87725CFF 23:A26F3FFF 24:C2874CFF 25:CC8824FF 26:C7882EFF 27:927365FF 28:81441AFF 29:1E2424FF 30:553E2BFF 31:A69080FF 32:7E4B29FF 33:964912FF 34:B85206FF 35:E1A356FF 36:904A16FF 37:FFA125FF 38:2E2822FF 39:563A20FF 40:1F2320FF 41:4D3924FF 42:755030FF 43:D76A19FF 44:D67924FF 45:FFFFFFFF 46:D46C09FF 47:814A1CFF 48:864616FF 49:292821FF 50:1A2324FF 51:1C2623FF 52:212420FF 53:33312AFF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:A36D38FF 58:664726FF 59:282A22FF 60:1C2525FF 61:323E31FF 62:52543EFF 63:D5C6B1FF 64:C0B5A0FF 65:FFFFFFFF 66:07879DFF 67:04404DFF 68:182525FF 69:1A2624FF 70:1B2928FF 71:22605DFF 72:17605EFF 73:92A899FF 74:CFC29CFF 75:5A9C9EFF 76:FFFFFFFF 77:07626CFF 78:132F2DFF 79:1B2426FF 80:172425FF 81:102C2EFF 82:097185FF 83:197378FF 84:5C6553FF 85:ADD4E4FF 86:5EC8D0FF 87:556E66FF 88:3D6866FF 89:1A2427FF 90:172224FF 91:1B2124FF 92:17262AFF 93:132B29FF 94:0A393EFF 95:094C5BFF 96:25494BFF 97:212E2BFF 98:1D2C29FF 99:192326FF +00:C47623FF 01:B26416FF 02:9D5312FF 03:754623FF 04:5B4936FF 05:584838FF 06:504436FF 07:423B30FF 08:36342BFF 09:2B2D26FF 10:222520FF 11:685B49FF 12:8E7A64FF 13:B48761FF 14:A56A3BFF 15:82552DFF 16:593D24FF 17:3D2E21FF 18:262621FF 19:1A2424FF 20:34332BFF 21:90806AFF 22:8B755FFF 23:9C6A3AFF 24:BE834AFF 25:C88626FF 26:CE923AFF 27:7E5F50FF 28:7C421BFF 29:232624FF 30:5D4C3DFF 31:A08C7BFF 32:7E4C2BFF 33:984912FF 34:B75207FF 35:DBA35DFF 36:A35E27FF 37:F9941FFF 38:342A21FF 39:4F3720FF 40:232521FF 41:4A3624FF 42:704C2EFF 43:D76B1BFF 44:D67824FF 45:FFFFFFFF 46:D46C09FF 47:874C1BFF 48:794019FF 49:2D2A21FF 50:1A2324FF 51:1D2422FF 52:242520FF 53:373229FF 54:FFFFE9FF 55:FFFFFBFF 56:6F4E27FF 57:9B6735FF 58:6A4928FF 59:2C2B22FF 60:1C2625FF 61:324338FF 62:51533FFF 63:CABDA8FF 64:BFB8A7FF 65:FFFFFFFF 66:078297FF 67:074B59FF 68:1E2726FF 69:1A2524FF 70:1B2828FF 71:225B56FF 72:1B5F5DFF 73:8AA496FF 74:BDB189FF 75:59999CFF 76:FAFFFFFF 77:076771FF 78:13302FFF 79:1B2426FF 80:172425FF 81:102C2DFF 82:086C82FF 83:1C777EFF 84:646F5DFF 85:B0D6E5FF 86:53BBC3FF 87:4C665FFF 88:3A5F5BFF 89:192527FF 90:172324FF 91:1A2224FF 92:172F38FF 93:143030FF 94:143B3FFF 95:456973FF 96:264849FF 97:21322FFF 98:1E2E2BFF 99:192325FF