Borders
Extensions Extensions Extensions
Once more, I’ve spun the wheel of topics to blog about and landed on something extension-related. Such is the life of those working with Vulkan.
In this case, the extension is VK_EXT_custom_border_color, the underlying implementation for which I had the luxury of testing on ANV as written by the memelord himself, Ivan Briano.
Let’s get to it.
The setup for this (in zink_screen.c
) is the same as for every extension, and I’ve blogged about this previously, so there’s no new ground to cover here.
Border Coloring
Fortunately, this is one of the simpler extensions to implement as well. This code is completely exclusive to the struct pipe_context::create_sampler_state
hook, which is what gallium uses to have drivers setup their samplers (not samplerviews, as that’s separate).
Here’s the current implementation:
static void *
zink_create_sampler_state(struct pipe_context *pctx,
const struct pipe_sampler_state *state)
{
struct zink_screen *screen = zink_screen(pctx->screen);
VkSamplerCreateInfo sci = {};
sci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sci.magFilter = zink_filter(state->mag_img_filter);
sci.minFilter = zink_filter(state->min_img_filter);
if (state->min_mip_filter != PIPE_TEX_MIPFILTER_NONE) {
sci.mipmapMode = sampler_mipmap_mode(state->min_mip_filter);
sci.minLod = state->min_lod;
sci.maxLod = state->max_lod;
} else {
sci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sci.minLod = 0;
sci.maxLod = 0;
}
sci.addressModeU = sampler_address_mode(state->wrap_s);
sci.addressModeV = sampler_address_mode(state->wrap_t);
sci.addressModeW = sampler_address_mode(state->wrap_r);
sci.mipLodBias = state->lod_bias;
if (state->compare_mode == PIPE_TEX_COMPARE_NONE)
sci.compareOp = VK_COMPARE_OP_NEVER;
else {
sci.compareOp = compare_op(state->compare_func);
sci.compareEnable = VK_TRUE;
}
sci.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; // TODO
sci.unnormalizedCoordinates = !state->normalized_coords;
if (state->max_anisotropy > 1) {
sci.maxAnisotropy = state->max_anisotropy;
sci.anisotropyEnable = VK_TRUE;
}
struct zink_sampler_state *sampler = CALLOC(1, sizeof(struct zink_sampler_state));
if (!sampler)
return NULL;
if (vkCreateSampler(screen->dev, &sci, NULL, &sampler->sampler) != VK_SUCCESS) {
FREE(sampler);
return NULL;
}
return sampler;
}
It’s just populating a VkSamplerCreateInfo struct from struct pipe_sampler_state
. Nothing too unusual.
Looking at the VK_EXT_custom_border_color
spec, the only change required for a sampler to handle a custom border color is to add a populated VkSamplerCustomBorderColorCreateInfoEXT struct into the pNext
chain for the VkSamplerCreateInfo
.
The struct populating is simple enough:
VkSamplerCustomBorderColorCreateInfoEXT cbci = {};
cbci.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT;
assert(screen->border_color_feats.customBorderColorWithoutFormat);
cbci.format = VK_FORMAT_UNDEFINED;
/* these are identical unions */
memcpy(&cbci.customBorderColor, &state->border_color, sizeof(union pipe_color_union));
sci.pNext = &cbci;
sci.borderColor = VK_BORDER_COLOR_INT_CUSTOM_EXT;
Helpfully here, the layouts of union pipe_color_union
and the customBorderColor
member of the Vulkan struct are the same, so this can just be copied directly. I added an assert to handle the case where the customBorderColorWithoutFormat
feature isn’t provided by the driver, since currently I’m just being a bit lazy and ANV handles it just fine, but this shouldn’t be too much work to fix up if zink is ever run on a driver that doesn’t support it.
With this set, border colors will be drawn as expected.
Conditional
It’s not always the case that border coloring is necessary, however, and there’s also limits that must be obeyed.
For the former, the wrap values (struct pipe_sampler_state::wrap_(s|t|r)
, which are all enum pipe_tex_wrap
) can be checked from the sampler state to determine whether specified border colors are actually going to be used, allowing them to be omitted when they’re unnecessary. Specifically, a small helper function like this can be used:
static inline bool
wrap_needs_border_color(unsigned wrap)
{
return wrap == PIPE_TEX_WRAP_CLAMP || wrap == PIPE_TEX_WRAP_CLAMP_TO_BORDER ||
wrap == PIPE_TEX_WRAP_MIRROR_CLAMP || wrap == PIPE_TEX_WRAP_MIRROR_CLAMP_TO_BORDER;
}
For other wrap modes, the border color can be ignored.
Looking now to limits, VK_EXT_custom_border_color
provides the maxCustomBorderColorSamplers
limit, which is the maximum number of samplers using custom border colors that can exist simultaneously. To avoid potential issues with this, a counter is atomically managed on the screen object based on the lifetimes of samplers that use custom border colors—another reason to avoid unnecessarily including custom border color structs here.
Altogether, the changed bits of the new function look like this:
static void *
zink_create_sampler_state(struct pipe_context *pctx,
const struct pipe_sampler_state *state)
{
...
bool need_custom = false;
need_custom |= wrap_needs_border_color(state->wrap_s);
need_custom |= wrap_needs_border_color(state->wrap_t);
need_custom |= wrap_needs_border_color(state->wrap_r);
VkSamplerCustomBorderColorCreateInfoEXT cbci = {};
if (screen->have_EXT_custom_border_color && need_custom) {
cbci.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT;
assert(screen->border_color_feats.customBorderColorWithoutFormat);
cbci.format = VK_FORMAT_UNDEFINED;
/* these are identical unions */
memcpy(&cbci.customBorderColor, &state->border_color, sizeof(union pipe_color_union));
sci.pNext = &cbci;
sci.borderColor = VK_BORDER_COLOR_INT_CUSTOM_EXT;
uint32_t check = p_atomic_inc_return(&screen->cur_custom_border_color_samplers);
assert(check <= screen->max_custom_border_color_samplers);
} else
sci.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; // TODO with custom shader if we're super interested?
...
}
Borders colored.