From a501a2dbff797829b61f21c5aeb9d19dba3e3874 Mon Sep 17 00:00:00 2001 From: Hallam Roberts Date: Wed, 14 Dec 2022 19:19:52 +0100 Subject: [PATCH] Images: add mirror extension type This adds a new mirror image extension type for shaders and geometry nodes (next to the existing repeat, extend and clip options). See D16432 for a more detailed explanation of `wrap_mirror`. This also adds a new sampler flag `GPU_SAMPLER_MIRROR_REPEAT`. It acts as a modifier to `GPU_SAMPLER_REPEAT`, so any `REPEAT` flag must be set for the `MIRROR` flag to have an effect. Differential Revision: https://developer.blender.org/D16432 --- intern/cycles/device/cuda/device_impl.cpp | 3 + intern/cycles/device/hip/device_impl.cpp | 3 + intern/cycles/device/metal/device_impl.mm | 2 +- intern/cycles/kernel/device/cpu/image.h | 60 ++++++++++++++++++ intern/cycles/kernel/device/metal/compat.h | 4 ++ .../kernel/device/metal/context_begin.h | 8 ++- intern/cycles/kernel/device/oneapi/image.h | 17 +++++ intern/cycles/scene/shader_nodes.cpp | 1 + intern/cycles/util/texture.h | 2 + .../engines/workbench/workbench_materials.cc | 4 +- source/blender/gpu/GPU_texture.h | 3 +- source/blender/gpu/metal/mtl_context.mm | 9 ++- source/blender/gpu/opengl/gl_texture.cc | 14 +++-- source/blender/makesdna/DNA_node_types.h | 1 + source/blender/makesrna/intern/rna_nodetree.c | 62 +++++++------------ .../geometry/nodes/node_geo_image_texture.cc | 31 ++++++++++ .../shader/nodes/node_shader_tex_image.cc | 3 + 17 files changed, 174 insertions(+), 53 deletions(-) diff --git a/intern/cycles/device/cuda/device_impl.cpp b/intern/cycles/device/cuda/device_impl.cpp index c9764d1c21b..f354ba6aee1 100644 --- a/intern/cycles/device/cuda/device_impl.cpp +++ b/intern/cycles/device/cuda/device_impl.cpp @@ -952,6 +952,9 @@ void CUDADevice::tex_alloc(device_texture &mem) case EXTENSION_CLIP: address_mode = CU_TR_ADDRESS_MODE_BORDER; break; + case EXTENSION_MIRROR: + address_mode = CU_TR_ADDRESS_MODE_MIRROR; + break; default: assert(0); break; diff --git a/intern/cycles/device/hip/device_impl.cpp b/intern/cycles/device/hip/device_impl.cpp index a84f1edd70e..04de8619697 100644 --- a/intern/cycles/device/hip/device_impl.cpp +++ b/intern/cycles/device/hip/device_impl.cpp @@ -909,6 +909,9 @@ void HIPDevice::tex_alloc(device_texture &mem) * because it's unsupported in HIP. */ address_mode = hipAddressModeClamp; break; + case EXTENSION_MIRROR: + address_mode = hipAddressModeMirror; + break; default: assert(0); break; diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm index 24836e88755..95935ce2a3a 100644 --- a/intern/cycles/device/metal/device_impl.mm +++ b/intern/cycles/device/metal/device_impl.mm @@ -856,7 +856,7 @@ void MetalDevice::tex_alloc(device_texture &mem) /* sampler_index maps into the GPU's constant 'metal_samplers' array */ uint64_t sampler_index = mem.info.extension; if (mem.info.interpolation != INTERPOLATION_CLOSEST) { - sampler_index += 3; + sampler_index += 4; } /* Image Texture Storage */ diff --git a/intern/cycles/kernel/device/cpu/image.h b/intern/cycles/kernel/device/cpu/image.h index 320e6309128..eb50ac8217f 100644 --- a/intern/cycles/kernel/device/cpu/image.h +++ b/intern/cycles/kernel/device/cpu/image.h @@ -202,6 +202,14 @@ template struct TextureInterpolator { return clamp(x, 0, width - 1); } + static ccl_always_inline int wrap_mirror(int x, int width) + { + const int m = abs(x + (x < 0)) % (2 * width); + if (m >= width) + return 2 * width - m - 1; + return m; + } + /* ******** 2D interpolation ******** */ static ccl_always_inline OutT interp_closest(const TextureInfo &info, float x, float y) @@ -226,6 +234,10 @@ template struct TextureInterpolator { ix = wrap_clamp(ix, width); iy = wrap_clamp(iy, height); break; + case EXTENSION_MIRROR: + ix = wrap_mirror(ix, width); + iy = wrap_mirror(iy, height); + break; default: kernel_assert(0); return zero(); @@ -268,6 +280,12 @@ template struct TextureInterpolator { niy = wrap_clamp(iy + 1, height); iy = wrap_clamp(iy, height); break; + case EXTENSION_MIRROR: + nix = wrap_mirror(ix + 1, width); + ix = wrap_mirror(ix, width); + niy = wrap_mirror(iy + 1, height); + iy = wrap_mirror(iy, height); + break; default: kernel_assert(0); return zero(); @@ -331,6 +349,17 @@ template struct TextureInterpolator { nniy = wrap_clamp(iy + 2, height); iy = wrap_clamp(iy, height); break; + case EXTENSION_MIRROR: + pix = wrap_mirror(ix - 1, width); + nix = wrap_mirror(ix + 1, width); + nnix = wrap_mirror(ix + 2, width); + ix = wrap_mirror(ix, width); + + piy = wrap_mirror(iy - 1, height); + niy = wrap_mirror(iy + 1, height); + nniy = wrap_mirror(iy + 2, height); + iy = wrap_mirror(iy, height); + break; default: kernel_assert(0); return zero(); @@ -403,6 +432,11 @@ template struct TextureInterpolator { iy = wrap_clamp(iy, height); iz = wrap_clamp(iz, depth); break; + case EXTENSION_MIRROR: + ix = wrap_mirror(ix, width); + iy = wrap_mirror(iy, height); + iz = wrap_mirror(iz, depth); + break; default: kernel_assert(0); return zero(); @@ -480,6 +514,16 @@ template struct TextureInterpolator { niz = wrap_clamp(iz + 1, depth); iz = wrap_clamp(iz, depth); break; + case EXTENSION_MIRROR: + nix = wrap_mirror(ix + 1, width); + ix = wrap_mirror(ix, width); + + niy = wrap_mirror(iy + 1, height); + iy = wrap_mirror(iy, height); + + niz = wrap_mirror(iz + 1, depth); + iz = wrap_mirror(iz, depth); + break; default: kernel_assert(0); return zero(); @@ -595,6 +639,22 @@ template struct TextureInterpolator { nniz = wrap_clamp(iz + 2, depth); iz = wrap_clamp(iz, depth); break; + case EXTENSION_MIRROR: + pix = wrap_mirror(ix - 1, width); + nix = wrap_mirror(ix + 1, width); + nnix = wrap_mirror(ix + 2, width); + ix = wrap_mirror(ix, width); + + piy = wrap_mirror(iy - 1, height); + niy = wrap_mirror(iy + 1, height); + nniy = wrap_mirror(iy + 2, height); + iy = wrap_mirror(iy, height); + + piz = wrap_mirror(iz - 1, depth); + niz = wrap_mirror(iz + 1, depth); + nniz = wrap_mirror(iz + 2, depth); + iz = wrap_mirror(iz, depth); + break; default: kernel_assert(0); return zero(); diff --git a/intern/cycles/kernel/device/metal/compat.h b/intern/cycles/kernel/device/metal/compat.h index 2dd6cc98b59..3913931b4fb 100644 --- a/intern/cycles/kernel/device/metal/compat.h +++ b/intern/cycles/kernel/device/metal/compat.h @@ -301,10 +301,12 @@ enum SamplerType { SamplerFilterNearest_AddressRepeat, SamplerFilterNearest_AddressClampEdge, SamplerFilterNearest_AddressClampZero, + SamplerFilterNearest_AddressMirroredRepeat, SamplerFilterLinear_AddressRepeat, SamplerFilterLinear_AddressClampEdge, SamplerFilterLinear_AddressClampZero, + SamplerFilterLinear_AddressMirroredRepeat, SamplerCount }; @@ -313,7 +315,9 @@ constant constexpr array metal_samplers = { sampler(address::repeat, filter::nearest), sampler(address::clamp_to_edge, filter::nearest), sampler(address::clamp_to_zero, filter::nearest), + sampler(address::mirrored_repeat, filter::nearest), sampler(address::repeat, filter::linear), sampler(address::clamp_to_edge, filter::linear), sampler(address::clamp_to_zero, filter::linear), + sampler(address::mirrored_repeat, filter::linear), }; diff --git a/intern/cycles/kernel/device/metal/context_begin.h b/intern/cycles/kernel/device/metal/context_begin.h index e75ec9cadec..4bde9be455a 100644 --- a/intern/cycles/kernel/device/metal/context_begin.h +++ b/intern/cycles/kernel/device/metal/context_begin.h @@ -47,9 +47,11 @@ class MetalKernelContext { case 0: return texture_array[tid].tex.sample(sampler(address::repeat, filter::nearest), coords); case 1: return texture_array[tid].tex.sample(sampler(address::clamp_to_edge, filter::nearest), coords); case 2: return texture_array[tid].tex.sample(sampler(address::clamp_to_zero, filter::nearest), coords); - case 3: return texture_array[tid].tex.sample(sampler(address::repeat, filter::linear), coords); - case 4: return texture_array[tid].tex.sample(sampler(address::clamp_to_edge, filter::linear), coords); - case 5: return texture_array[tid].tex.sample(sampler(address::clamp_to_zero, filter::linear), coords); + case 3: return texture_array[tid].tex.sample(sampler(address::mirrored_repeat, filter::nearest), coords); + case 4: return texture_array[tid].tex.sample(sampler(address::repeat, filter::linear), coords); + case 5: return texture_array[tid].tex.sample(sampler(address::clamp_to_edge, filter::linear), coords); + case 6: return texture_array[tid].tex.sample(sampler(address::clamp_to_zero, filter::linear), coords); + case 7: return texture_array[tid].tex.sample(sampler(address::mirrored_repeat, filter::linear), coords); } } #endif diff --git a/intern/cycles/kernel/device/oneapi/image.h b/intern/cycles/kernel/device/oneapi/image.h index 2417b8eac3b..bdb81bb8645 100644 --- a/intern/cycles/kernel/device/oneapi/image.h +++ b/intern/cycles/kernel/device/oneapi/image.h @@ -24,6 +24,14 @@ ccl_device_inline int svm_image_texture_wrap_clamp(int x, int width) return clamp(x, 0, width - 1); } +ccl_device_inline int svm_image_texture_wrap_mirror(int x, int width) +{ + const int m = abs(x + (x < 0)) % (2 * width); + if (m >= width) + return 2 * width - m - 1; + return m; +} + ccl_device_inline float4 svm_image_texture_read(const TextureInfo &info, int x, int y, int z) { const int data_offset = x + info.width * y + info.width * info.height * z; @@ -85,6 +93,10 @@ ccl_device_inline float4 svm_image_texture_read_2d(int id, int x, int y) x = svm_image_texture_wrap_clamp(x, info.width); y = svm_image_texture_wrap_clamp(y, info.height); } + else if (info.extension == EXTENSION_MIRROR) { + x = svm_image_texture_wrap_mirror(x, info.width); + y = svm_image_texture_wrap_mirror(y, info.height); + } else { if (x < 0 || x >= info.width || y < 0 || y >= info.height) { return make_float4(0.0f, 0.0f, 0.0f, 0.0f); @@ -109,6 +121,11 @@ ccl_device_inline float4 svm_image_texture_read_3d(int id, int x, int y, int z) y = svm_image_texture_wrap_clamp(y, info.height); z = svm_image_texture_wrap_clamp(z, info.depth); } + else if (info.extension == EXTENSION_MIRROR) { + x = svm_image_texture_wrap_mirror(x, info.width); + y = svm_image_texture_wrap_mirror(y, info.height); + z = svm_image_texture_wrap_mirror(z, info.depth); + } else { if (x < 0 || x >= info.width || y < 0 || y >= info.height || z < 0 || z >= info.depth) { return make_float4(0.0f, 0.0f, 0.0f, 0.0f); diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index a64953c03ec..e95f362793f 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -226,6 +226,7 @@ NODE_DEFINE(ImageTextureNode) extension_enum.insert("periodic", EXTENSION_REPEAT); extension_enum.insert("clamp", EXTENSION_EXTEND); extension_enum.insert("black", EXTENSION_CLIP); + extension_enum.insert("mirror", EXTENSION_MIRROR); SOCKET_ENUM(extension, "Extension", extension_enum, EXTENSION_REPEAT); static NodeEnum projection_enum; diff --git a/intern/cycles/util/texture.h b/intern/cycles/util/texture.h index 90e842933c2..6820cb9be0a 100644 --- a/intern/cycles/util/texture.h +++ b/intern/cycles/util/texture.h @@ -65,6 +65,8 @@ typedef enum ExtensionType { EXTENSION_EXTEND = 1, /* Clip to image size and set exterior pixels as transparent. */ EXTENSION_CLIP = 2, + /* Repeatedly flip the image horizontally and vertically. */ + EXTENSION_MIRROR = 3, EXTENSION_NUM_TYPES, } ExtensionType; diff --git a/source/blender/draw/engines/workbench/workbench_materials.cc b/source/blender/draw/engines/workbench/workbench_materials.cc index c95bebb61e8..296f8c53f2e 100644 --- a/source/blender/draw/engines/workbench/workbench_materials.cc +++ b/source/blender/draw/engines/workbench/workbench_materials.cc @@ -97,11 +97,13 @@ BLI_INLINE void workbench_material_get_image( case SH_NODE_TEX_IMAGE: { const NodeTexImage *storage = static_cast(node->storage); const bool use_filter = (storage->interpolation != SHD_INTERP_CLOSEST); - const bool use_repeat = (storage->extension == SHD_IMAGE_EXTENSION_REPEAT); + const bool use_mirror = (storage->extension == SHD_IMAGE_EXTENSION_MIRROR); + const bool use_repeat = use_mirror || (storage->extension == SHD_IMAGE_EXTENSION_REPEAT); const bool use_clip = (storage->extension == SHD_IMAGE_EXTENSION_CLIP); SET_FLAG_FROM_TEST(*r_sampler, use_filter, GPU_SAMPLER_FILTER); SET_FLAG_FROM_TEST(*r_sampler, use_repeat, GPU_SAMPLER_REPEAT); SET_FLAG_FROM_TEST(*r_sampler, use_clip, GPU_SAMPLER_CLAMP_BORDER); + SET_FLAG_FROM_TEST(*r_sampler, use_mirror, GPU_SAMPLER_MIRROR_REPEAT); break; } case SH_NODE_TEX_ENVIRONMENT: { diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h index 6cbdda4cd4c..17639d19e61 100644 --- a/source/blender/gpu/GPU_texture.h +++ b/source/blender/gpu/GPU_texture.h @@ -35,7 +35,8 @@ typedef enum eGPUSamplerState { GPU_SAMPLER_CLAMP_BORDER = (1 << 5), /* Clamp to border color instead of border texel. */ GPU_SAMPLER_COMPARE = (1 << 6), GPU_SAMPLER_ANISO = (1 << 7), - GPU_SAMPLER_ICON = (1 << 8), + GPU_SAMPLER_MIRROR_REPEAT = (1 << 8), /* Requires any REPEAT flag to be set. */ + GPU_SAMPLER_ICON = (1 << 9), GPU_SAMPLER_REPEAT = (GPU_SAMPLER_REPEAT_S | GPU_SAMPLER_REPEAT_T | GPU_SAMPLER_REPEAT_R), } eGPUSamplerState; diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm index b20a0f7faa8..9e03f926787 100644 --- a/source/blender/gpu/metal/mtl_context.mm +++ b/source/blender/gpu/metal/mtl_context.mm @@ -1595,14 +1595,17 @@ id MTLContext::generate_sampler_from_state(MTLSamplerState samp MTLSamplerAddressMode clamp_type = (sampler_state.state & GPU_SAMPLER_CLAMP_BORDER) ? MTLSamplerAddressModeClampToBorderColor : MTLSamplerAddressModeClampToEdge; + MTLSamplerAddressMode repeat_type = (sampler_state.state & GPU_SAMPLER_MIRROR_REPEAT) ? + MTLSamplerAddressModeMirrorRepeat : + MTLSamplerAddressModeRepeat; descriptor.rAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_R) ? - MTLSamplerAddressModeRepeat : + repeat_type : clamp_type; descriptor.sAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_S) ? - MTLSamplerAddressModeRepeat : + repeat_type : clamp_type; descriptor.tAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_T) ? - MTLSamplerAddressModeRepeat : + repeat_type : clamp_type; descriptor.borderColor = MTLSamplerBorderColorTransparentBlack; descriptor.minFilter = (sampler_state.state & GPU_SAMPLER_FILTER) ? diff --git a/source/blender/gpu/opengl/gl_texture.cc b/source/blender/gpu/opengl/gl_texture.cc index 6db73345d5f..6ec079f44b8 100644 --- a/source/blender/gpu/opengl/gl_texture.cc +++ b/source/blender/gpu/opengl/gl_texture.cc @@ -548,12 +548,13 @@ GLuint GLTexture::samplers_[GPU_SAMPLER_MAX] = {0}; void GLTexture::samplers_init() { glGenSamplers(GPU_SAMPLER_MAX, samplers_); - for (int i = 0; i <= GPU_SAMPLER_ICON - 1; i++) { + for (int i = 0; i < GPU_SAMPLER_ICON; i++) { eGPUSamplerState state = static_cast(i); GLenum clamp_type = (state & GPU_SAMPLER_CLAMP_BORDER) ? GL_CLAMP_TO_BORDER : GL_CLAMP_TO_EDGE; - GLenum wrap_s = (state & GPU_SAMPLER_REPEAT_S) ? GL_REPEAT : clamp_type; - GLenum wrap_t = (state & GPU_SAMPLER_REPEAT_T) ? GL_REPEAT : clamp_type; - GLenum wrap_r = (state & GPU_SAMPLER_REPEAT_R) ? GL_REPEAT : clamp_type; + GLenum repeat_type = (state & GPU_SAMPLER_MIRROR_REPEAT) ? GL_MIRRORED_REPEAT : GL_REPEAT; + GLenum wrap_s = (state & GPU_SAMPLER_REPEAT_S) ? repeat_type : clamp_type; + GLenum wrap_t = (state & GPU_SAMPLER_REPEAT_T) ? repeat_type : clamp_type; + GLenum wrap_r = (state & GPU_SAMPLER_REPEAT_R) ? repeat_type : clamp_type; GLenum mag_filter = (state & GPU_SAMPLER_FILTER) ? GL_LINEAR : GL_NEAREST; GLenum min_filter = (state & GPU_SAMPLER_FILTER) ? ((state & GPU_SAMPLER_MIPMAP) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR) : @@ -577,7 +578,7 @@ void GLTexture::samplers_init() char sampler_name[128] = "\0\0"; SNPRINTF(sampler_name, - "%s%s%s%s%s%s%s%s%s%s", + "%s%s%s%s%s%s%s%s%s%s%s", (state == GPU_SAMPLER_DEFAULT) ? "_default" : "", (state & GPU_SAMPLER_FILTER) ? "_filter" : "", (state & GPU_SAMPLER_MIPMAP) ? "_mipmap" : "", @@ -585,6 +586,7 @@ void GLTexture::samplers_init() (state & GPU_SAMPLER_REPEAT_S) ? "S" : "", (state & GPU_SAMPLER_REPEAT_T) ? "T" : "", (state & GPU_SAMPLER_REPEAT_R) ? "R" : "", + (state & GPU_SAMPLER_MIRROR_REPEAT) ? "-mirror" : "", (state & GPU_SAMPLER_CLAMP_BORDER) ? "_clamp_border" : "", (state & GPU_SAMPLER_COMPARE) ? "_compare" : "", (state & GPU_SAMPLER_ANISO) ? "_aniso" : ""); @@ -612,7 +614,7 @@ void GLTexture::samplers_update() float aniso_filter = min_ff(max_anisotropy, U.anisotropic_filter); - for (int i = 0; i <= GPU_SAMPLER_ICON - 1; i++) { + for (int i = 0; i < GPU_SAMPLER_ICON; i++) { eGPUSamplerState state = static_cast(i); if ((state & GPU_SAMPLER_ANISO) && (state & GPU_SAMPLER_MIPMAP)) { glSamplerParameterf(samplers_[i], GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso_filter); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 6313791bd3c..eddc26fe867 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1754,6 +1754,7 @@ enum { #define SHD_IMAGE_EXTENSION_REPEAT 0 #define SHD_IMAGE_EXTENSION_EXTEND 1 #define SHD_IMAGE_EXTENSION_CLIP 2 +#define SHD_IMAGE_EXTENSION_MIRROR 3 /* image texture */ #define SHD_PROJ_FLAT 0 diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 4a977f09884..7e12483a543 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -4690,6 +4690,30 @@ static const EnumPropertyItem node_subsurface_method_items[] = { "automatically adjusted to match color textures"}, {0, NULL, 0, NULL, NULL}}; +static const EnumPropertyItem prop_image_extension[] = { + {SHD_IMAGE_EXTENSION_REPEAT, + "REPEAT", + 0, + "Repeat", + "Cause the image to repeat horizontally and vertically"}, + {SHD_IMAGE_EXTENSION_EXTEND, + "EXTEND", + 0, + "Extend", + "Extend by repeating edge pixels of the image"}, + {SHD_IMAGE_EXTENSION_CLIP, + "CLIP", + 0, + "Clip", + "Clip to image size and set exterior pixels as transparent"}, + {SHD_IMAGE_EXTENSION_MIRROR, + "MIRROR", + 0, + "Mirror", + "Repeatedly flip the image horizontally and vertically"}, + {0, NULL, 0, NULL, NULL}, +}; + /* -- Common nodes ---------------------------------------------------------- */ static void def_group_input(StructRNA *UNUSED(srna)) @@ -5431,25 +5455,6 @@ static void def_sh_tex_image(StructRNA *srna) {0, NULL, 0, NULL, NULL}, }; - static const EnumPropertyItem prop_image_extension[] = { - {SHD_IMAGE_EXTENSION_REPEAT, - "REPEAT", - 0, - "Repeat", - "Cause the image to repeat horizontally and vertically"}, - {SHD_IMAGE_EXTENSION_EXTEND, - "EXTEND", - 0, - "Extend", - "Extend by repeating edge pixels of the image"}, - {SHD_IMAGE_EXTENSION_CLIP, - "CLIP", - 0, - "Clip", - "Clip to image size and set exterior pixels as transparent"}, - {0, NULL, 0, NULL, NULL}, - }; - PropertyRNA *prop; prop = RNA_def_property(srna, "image", PROP_POINTER, PROP_NONE); @@ -5516,25 +5521,6 @@ static void def_geo_image_texture(StructRNA *srna) {0, NULL, 0, NULL, NULL}, }; - static const EnumPropertyItem prop_image_extension[] = { - {SHD_IMAGE_EXTENSION_REPEAT, - "REPEAT", - 0, - "Repeat", - "Cause the image to repeat horizontally and vertically"}, - {SHD_IMAGE_EXTENSION_EXTEND, - "EXTEND", - 0, - "Extend", - "Extend by repeating edge pixels of the image"}, - {SHD_IMAGE_EXTENSION_CLIP, - "CLIP", - 0, - "Clip", - "Clip to image size and set exterior pixels as transparent"}, - {0, NULL, 0, NULL, NULL}, - }; - PropertyRNA *prop; RNA_def_struct_sdna_from(srna, "NodeGeometryImageTexture", "storage"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc index 3c0f3af4ed2..6a2ff2e1ead 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc @@ -114,6 +114,15 @@ class ImageFieldsFunction : public fn::MultiFunction { return std::clamp(x, 0, width - 1); } + static int wrap_mirror(const int x, const int width) + { + const int m = std::abs(x + (x < 0)) % (2 * width); + if (m >= width) { + return 2 * width - m - 1; + } + return m; + } + static float4 image_pixel_lookup(const ImBuf &ibuf, const int px, const int py) { if (px < 0 || py < 0 || px >= ibuf.x || py >= ibuf.y) { @@ -173,6 +182,17 @@ class ImageFieldsFunction : public fn::MultiFunction { piy = wrap_clamp(piy, height); break; } + case SHD_IMAGE_EXTENSION_MIRROR: { + ppix = wrap_mirror(pix - 1, width); + ppiy = wrap_mirror(piy - 1, height); + nix = wrap_mirror(pix + 1, width); + niy = wrap_mirror(piy + 1, height); + nnix = wrap_mirror(pix + 2, width); + nniy = wrap_mirror(piy + 2, height); + pix = wrap_mirror(pix, width); + piy = wrap_mirror(piy, height); + break; + } default: return float4(0.0f, 0.0f, 0.0f, 0.0f); } @@ -233,6 +253,12 @@ class ImageFieldsFunction : public fn::MultiFunction { piy = wrap_clamp(piy, height); break; } + case SHD_IMAGE_EXTENSION_MIRROR: + nix = wrap_mirror(pix + 1, width); + niy = wrap_mirror(piy + 1, height); + pix = wrap_mirror(pix, width); + piy = wrap_mirror(piy, height); + break; default: case SHD_IMAGE_EXTENSION_REPEAT: pix = wrap_periodic(pix, width); @@ -282,6 +308,11 @@ class ImageFieldsFunction : public fn::MultiFunction { iy = wrap_clamp(iy, height); return image_pixel_lookup(ibuf, ix, iy); } + case SHD_IMAGE_EXTENSION_MIRROR: { + ix = wrap_mirror(ix, width); + iy = wrap_mirror(iy, height); + return image_pixel_lookup(ibuf, ix, iy); + } default: return float4(0.0f, 0.0f, 0.0f, 0.0f); } diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_image.cc b/source/blender/nodes/shader/nodes/node_shader_tex_image.cc index ab7d65020ed..d8fb30396a3 100644 --- a/source/blender/nodes/shader/nodes/node_shader_tex_image.cc +++ b/source/blender/nodes/shader/nodes/node_shader_tex_image.cc @@ -61,6 +61,9 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat, case SHD_IMAGE_EXTENSION_CLIP: sampler_state |= GPU_SAMPLER_CLAMP_BORDER; break; + case SHD_IMAGE_EXTENSION_MIRROR: + sampler_state |= GPU_SAMPLER_REPEAT | GPU_SAMPLER_MIRROR_REPEAT; + break; default: break; }