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
17 changed files with 174 additions and 53 deletions

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