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
This commit is contained in:
Hallam Roberts 2022-12-14 19:19:52 +01:00 committed by Jacques Lucke
parent 8c14992db2
commit a501a2dbff
Notes: blender-bot 2023-02-14 18:10:37 +01:00
Referenced by commit 8afcecdf1f, Cycles: update Intel Graphics compiler to 101.4032 on Windows
Referenced by issue blender/blender-addons#103359, Images as Planes: texture extension setting does not work
17 changed files with 174 additions and 53 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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 */

View File

@ -202,6 +202,14 @@ template<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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<typename TexT, typename OutT = float4> 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();

View File

@ -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<sampler, SamplerCount> 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),
};

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -97,11 +97,13 @@ BLI_INLINE void workbench_material_get_image(
case SH_NODE_TEX_IMAGE: {
const NodeTexImage *storage = static_cast<NodeTexImage *>(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: {

View File

@ -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;

View File

@ -1595,14 +1595,17 @@ id<MTLSamplerState> 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) ?

View File

@ -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<eGPUSamplerState>(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<eGPUSamplerState>(i);
if ((state & GPU_SAMPLER_ANISO) && (state & GPU_SAMPLER_MIPMAP)) {
glSamplerParameterf(samplers_[i], GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso_filter);

View File

@ -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

View File

@ -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");

View File

@ -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);
}

View File

@ -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;
}