zink: replace image negotiation with candidate-based approach

Replace the nested retry loops (eval_ici, set_image_usage,
double_check_ici, suboptimal_check_ici, try_set_image_usage_or_EXTENDED)
with a flat candidate array that encodes the same fallback order.

Instead of mutating a shared VkImageCreateInfo through deeply nested
function calls and retrying with toggled flags, we now:
1. build_usage_candidates() generates an array of (tiling, usage,
   flags, has_format_list) tuples in preference order
2. try_image_config() applies each candidate and calls check_ici
3. negotiate_image_config() iterates tiling/extended combos, builds
   candidates for each, and takes the first passing one

The modifier path (find_good_mod) is kept separate since it iterates
modifiers and takes the last good one (max-by-position, matching
the GBM worst-to-best convention), which is fundamentally different
from the candidate model's first-match-from-fallback-chain.

Duplicate candidates from the old code's redundant retry paths are
eliminated via dedup_configs(). The pNext chain surgery in
double_check_ici (manually unlinking VkImageFormatListCreateInfo) is
replaced by try_image_config's explicit format list chain/unchain.

The cube-compatible post-pass is simplified to a single check_ici
call instead of re-running the full negotiation.

Assisted-by: Claude
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41734>
This commit is contained in:
Adam Jackson 2026-05-21 11:09:36 -04:00 committed by Marge Bot
parent f5943e9dbb
commit d305e6d7b1

View file

@ -491,67 +491,144 @@ find_modifier_feats(const struct zink_modifier_props *prop, uint64_t modifier)
return 0;
}
/* check HIC optimalness */
/* per-attempt max: ~12 candidates before dedup */
#define MAX_IMAGE_CONFIGS 24
struct image_config {
VkImageTiling tiling;
VkImageUsageFlags usage;
VkImageCreateFlags flags;
bool has_format_list;
};
static bool
suboptimal_check_ici(struct zink_screen *screen, VkImageCreateInfo *ici, uint64_t mod)
configs_equal(const struct image_config *a, const struct image_config *b)
{
if (check_ici(screen, ici, mod))
return true;
ici->usage &= ~VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
if (check_ici(screen, ici, mod))
return true;
ici->usage |= VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
return false;
return a->tiling == b->tiling && a->usage == b->usage &&
a->flags == b->flags && a->has_format_list == b->has_format_list;
}
/* If the driver can't do mutable with this ICI, then try again after removing mutable (and
* thus also the list of formats we might might mutate to)
static unsigned
emit_hic_variants(struct image_config *c, unsigned n, VkImageTiling tiling, VkImageUsageFlags usage,
VkImageCreateFlags flags, bool fmtlist, unsigned max)
{
if ((usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) && n < max)
c[n++] = (struct image_config){tiling, usage, flags, fmtlist};
if (n < max) {
c[n++] = (struct image_config){
tiling,
usage & ~VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT,
flags, fmtlist
};
}
return n;
}
/*
* With require_mutable, only emit format-list variants. Without,
* also try without the format list (dropping MUTABLE_FORMAT_BIT).
*/
static unsigned
emit_usage_candidates(struct image_config *c, unsigned n, VkImageTiling tiling, VkImageUsageFlags usage,
VkImageCreateFlags base_flags, bool require_mutable, bool have_fmtlist,
bool always_mutable, unsigned max)
{
if (!usage)
return n;
VkImageCreateFlags flags = base_flags;
if (have_fmtlist || always_mutable)
flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
bool cand_fmtlist = have_fmtlist && !always_mutable;
n = emit_hic_variants(c, n, tiling, usage, flags, cand_fmtlist, max);
if (require_mutable || !cand_fmtlist)
return n;
n = emit_hic_variants(c, n, tiling, usage, base_flags, false, max);
return n;
}
static unsigned
build_usage_candidates(struct zink_screen *screen, const struct pipe_resource *templ, unsigned bind,
VkImageTiling tiling, VkImageCreateFlags base_flags, bool extended, bool have_fmtlist,
bool always_mutable, struct image_config *c, unsigned max)
{
unsigned n = 0;
VkImageCreateFlags flags = base_flags;
if (extended)
flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT |
VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
const struct zink_format_props *props = zink_get_format_props(screen, templ->format);
VkFormatFeatureFlags2 feats = tiling == VK_IMAGE_TILING_LINEAR ?
props->linearTilingFeatures : props->optimalTilingFeatures;
if (feats & VK_FORMAT_FEATURE_DISJOINT_BIT && util_format_get_num_planes(templ->format) > 1)
flags |= VK_IMAGE_CREATE_DISJOINT_BIT;
if (flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT)
feats = UINT32_MAX;
bool need_extended = false;
VkImageUsageFlags usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
if (need_extended) {
flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
feats = UINT32_MAX;
usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
}
/* full usage, mutable required */
n = emit_usage_candidates(c, n, tiling, usage, flags, true, have_fmtlist, always_mutable, max);
if (util_format_is_depth_or_stencil(templ->format)) {
if (!(templ->bind & PIPE_BIND_DEPTH_STENCIL)) {
VkImageUsageFlags u = usage & ~VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
n = emit_usage_candidates(c, n, tiling, u, flags, true, have_fmtlist, always_mutable, max);
}
} else if (!(templ->bind & PIPE_BIND_RENDER_TARGET)) {
VkImageUsageFlags no_ca = usage & ~VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
n = emit_usage_candidates(c, n, tiling, no_ca, flags, true, have_fmtlist, always_mutable, max);
n = emit_usage_candidates(c, n, tiling, usage, flags, false, have_fmtlist, always_mutable, max);
n = emit_usage_candidates(c, n, tiling, no_ca, flags, false, have_fmtlist, always_mutable, max);
} else {
n = emit_usage_candidates(c, n, tiling, usage, flags, false, have_fmtlist, always_mutable, max);
}
return n;
}
/* Apply a candidate config and test it with check_ici. */
static bool
double_check_ici(struct zink_screen *screen, VkImageCreateInfo *ici, VkImageUsageFlags usage, uint64_t mod, bool require_mutable)
try_image_config(struct zink_screen *screen, VkImageCreateInfo *ici, const struct image_config *config,
VkImageFormatListCreateInfo *format_list, uint64_t modifier)
{
ici->tiling = config->tiling;
ici->usage = config->usage;
ici->flags = config->flags;
if (config->has_format_list && format_list) {
format_list->pNext = NULL;
ici->pNext = format_list;
} else {
ici->pNext = NULL;
}
return check_ici(screen, ici, modifier);
}
/* Try a usage, falling back to without HOST_TRANSFER. */
static bool
try_usage_mutable(struct zink_screen *screen, VkImageCreateInfo *ici, VkImageUsageFlags usage, uint64_t mod)
{
if (!usage)
return false;
ici->usage = usage;
if (ici->usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) {
if (suboptimal_check_ici(screen, ici, mod))
return true;
}
if (check_ici(screen, ici, mod))
if ((usage & VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT) &&
check_ici(screen, ici, mod))
return true;
if (require_mutable)
return false;
const void *pNext = ici->pNext;
if (pNext) {
VkBaseOutStructure *prev = NULL;
VkBaseOutStructure *fmt_list = NULL;
vk_foreach_struct(strct, (void*)ici->pNext) {
if (strct->sType == VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO) {
fmt_list = strct;
if (prev) {
prev->pNext = strct->pNext;
} else {
ici->pNext = strct->pNext;
}
fmt_list->pNext = NULL;
break;
}
prev = strct;
}
if (!fmt_list)
return false;
ici->flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
if (suboptimal_check_ici(screen, ici, mod))
return true;
fmt_list->pNext = (void*)ici->pNext;
ici->pNext = fmt_list;
ici->flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
}
return false;
ici->usage = usage & ~VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
return check_ici(screen, ici, mod);
}
static bool
@ -574,8 +651,7 @@ find_good_mod(struct zink_screen *screen, VkImageCreateInfo *ici, const struct p
ici->flags |= VK_IMAGE_CREATE_DISJOINT_BIT;
VkImageUsageFlags usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
assert(!need_extended);
if (double_check_ici(screen, ici, usage, modifiers[i], true)) {
/* assume "best" modifiers are last in array; just return last good modifier */
if (try_usage_mutable(screen, ici, usage, modifiers[i])) {
found = true;
*good_mod = modifiers[i];
good_usage = usage;
@ -586,133 +662,140 @@ find_good_mod(struct zink_screen *screen, VkImageCreateInfo *ici, const struct p
return found;
}
/* subfunctions of this call must set ici->usage on success */
static bool
set_image_usage(struct zink_screen *screen, VkImageCreateInfo *ici, const struct pipe_resource *templ, unsigned bind, unsigned modifiers_count, uint64_t *modifiers, uint64_t *mod)
/* Remove duplicate candidates that would produce redundant queries. */
static unsigned
dedup_configs(struct image_config *c, unsigned n)
{
VkImageTiling tiling = ici->tiling;
bool need_extended = false;
*mod = DRM_FORMAT_MOD_INVALID;
if (modifiers_count) {
assert(tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT);
uint64_t good_mod = 0;
if (screen->info.have_EXT_image_drm_format_modifier &&
find_good_mod(screen, ici, templ, bind, modifiers_count, modifiers, &good_mod)) {
*mod = good_mod;
return true;
}
/* only try linear if no other options available */
const struct zink_modifier_props *prop = zink_get_modifier_props(screen, templ->format);
VkFormatFeatureFlags feats = find_modifier_feats(prop, DRM_FORMAT_MOD_LINEAR);
if (feats) {
if (feats & VK_FORMAT_FEATURE_DISJOINT_BIT && util_format_get_num_planes(templ->format) > 1)
ici->flags |= VK_IMAGE_CREATE_DISJOINT_BIT;
VkImageUsageFlags usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
assert(!need_extended);
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_LINEAR, true)) {
*mod = DRM_FORMAT_MOD_LINEAR;
return true;
unsigned out = 0;
for (unsigned i = 0; i < n; i++) {
bool dup = false;
for (unsigned j = 0; j < out; j++) {
if (configs_equal(&c[i], &c[j])) {
dup = true;
break;
}
}
} else {
const struct zink_format_props *props = zink_get_format_props(screen, templ->format);
VkFormatFeatureFlags2 feats = tiling == VK_IMAGE_TILING_LINEAR ? props->linearTilingFeatures : props->optimalTilingFeatures;
if (feats & VK_FORMAT_FEATURE_DISJOINT_BIT && util_format_get_num_planes(templ->format) > 1)
ici->flags |= VK_IMAGE_CREATE_DISJOINT_BIT;
if (ici->flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT)
feats = UINT32_MAX;
VkImageUsageFlags usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
if (need_extended) {
ici->flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
feats = UINT32_MAX;
usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
}
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, true))
return true;
if (util_format_is_depth_or_stencil(templ->format)) {
if (!(templ->bind & PIPE_BIND_DEPTH_STENCIL)) {
usage &= ~VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
/* mutable doesn't apply to depth/stencil formats */
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, true))
return true;
}
} else if (!(templ->bind & PIPE_BIND_RENDER_TARGET)) {
usage &= ~VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, true))
return true;
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, false))
return true;
usage &= ~VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, false))
return true;
} else {
if (double_check_ici(screen, ici, usage, DRM_FORMAT_MOD_INVALID, false))
return true;
}
if (!dup)
c[out++] = c[i];
}
ici->usage = 0;
*mod = DRM_FORMAT_MOD_INVALID;
return false;
}
static bool
try_set_image_usage_or_EXTENDED(struct zink_screen *screen, VkImageCreateInfo *ici, const struct pipe_resource *templ, unsigned bind, unsigned modifiers_count, uint64_t *modifiers, uint64_t *mod)
{
VkImageCreateFlags flags = ici->flags;
/* retry with EXTENDED: trust that the frontend isn't giving us anything insane and pray */
ici->flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT | VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
bool success = set_image_usage(screen, ici, templ, bind, modifiers_count, modifiers, mod);
if (!success)
ici->flags = flags;
return success;
return out;
}
static uint64_t
eval_ici(struct zink_screen *screen, VkImageCreateInfo *ici, const struct pipe_resource *templ, unsigned bind, unsigned modifiers_count, uint64_t *modifiers, bool *success)
negotiate_image_config(struct zink_screen *screen, VkImageCreateInfo *ici, const struct pipe_resource *templ,
unsigned bind, unsigned modifiers_count, uint64_t *modifiers,
VkImageFormatListCreateInfo *format_list, bool *out_success)
{
/* sampleCounts will be set to VK_SAMPLE_COUNT_1_BIT if at least one of the following conditions is true:
* - flags contains VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT
*
* 44.1.1. Supported Sample Counts
*/
bool want_cube = ici->samples == 1 &&
(templ->target == PIPE_TEXTURE_CUBE ||
templ->target == PIPE_TEXTURE_CUBE_ARRAY ||
(templ->target == PIPE_TEXTURE_2D_ARRAY && ici->extent.width == ici->extent.height && ici->arrayLayers >= 6));
(templ->target == PIPE_TEXTURE_CUBE ||
templ->target == PIPE_TEXTURE_CUBE_ARRAY ||
(templ->target == PIPE_TEXTURE_2D_ARRAY &&
ici->extent.width == ici->extent.height &&
ici->arrayLayers >= 6));
if (ici->tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT)
modifiers_count = 0;
uint64_t mod = DRM_FORMAT_MOD_INVALID;
/* this should work most of the time */
if (!set_image_usage(screen, ici, templ, bind, modifiers_count, modifiers, &mod)) {
bool success = false;
/* dmabuf doesn't retry with EXTENDED */
if (ici->tiling == VK_IMAGE_TILING_OPTIMAL)
success = try_set_image_usage_or_EXTENDED(screen, ici, templ, bind, modifiers_count, modifiers, &mod);
if (!success) {
ici->tiling = VK_IMAGE_TILING_LINEAR;
modifiers_count = 0;
/* in theory LINEAR should work */
if (!set_image_usage(screen, ici, templ, bind, modifiers_count, modifiers, &mod)) {
/* ...in theory LINEAR + EXTENDED should definitely work */
if (!try_set_image_usage_or_EXTENDED(screen, ici, templ, bind, modifiers_count, modifiers, &mod))
/* ...so that was a lie */
return DRM_FORMAT_MOD_INVALID;
bool have_fmtlist = format_list && format_list->viewFormatCount > 0;
bool always_mutable = (templ->bind & ZINK_BIND_MUTABLE) != 0;
/* modifier path */
if (modifiers_count) {
uint64_t good_mod = 0;
if (screen->info.have_EXT_image_drm_format_modifier &&
find_good_mod(screen, ici, templ, bind, modifiers_count, modifiers, &good_mod)) {
mod = good_mod;
goto found;
}
/* try LINEAR modifier as last resort */
const struct zink_modifier_props *prop = zink_get_modifier_props(screen, templ->format);
VkFormatFeatureFlags feats = find_modifier_feats(prop, DRM_FORMAT_MOD_LINEAR);
if (feats) {
bool need_extended = false;
if (feats & VK_FORMAT_FEATURE_DISJOINT_BIT && util_format_get_num_planes(templ->format) > 1)
ici->flags |= VK_IMAGE_CREATE_DISJOINT_BIT;
VkImageUsageFlags usage = get_image_usage_for_feats(screen, feats, templ, bind, &need_extended);
assert(!need_extended);
if (try_usage_mutable(screen, ici, usage, DRM_FORMAT_MOD_LINEAR)) {
mod = DRM_FORMAT_MOD_LINEAR;
goto found;
}
}
/* fall through to non-modifier LINEAR path */
ici->tiling = VK_IMAGE_TILING_LINEAR;
}
/* non-modifier path: build candidates and try them */
{
VkImageCreateFlags base_flags = ici->flags &
~(VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT);
VkImageTiling orig_tiling = ici->tiling;
struct image_config configs[MAX_IMAGE_CONFIGS];
unsigned count = 0;
/* tiling attempts: orig, orig+EXT, LINEAR, LINEAR+EXT */
for (int attempt = 0; attempt < 4; attempt++) {
VkImageTiling tiling;
bool extended;
switch (attempt) {
case 0:
tiling = orig_tiling;
extended = false;
break;
case 1:
if (orig_tiling != VK_IMAGE_TILING_OPTIMAL)
continue;
tiling = orig_tiling;
extended = true;
break;
case 2:
if (orig_tiling == VK_IMAGE_TILING_LINEAR)
continue;
tiling = VK_IMAGE_TILING_LINEAR;
extended = false;
break;
case 3:
tiling = VK_IMAGE_TILING_LINEAR;
extended = true;
break;
default:
UNREACHABLE("bad attempt");
}
count = build_usage_candidates(screen, templ, bind, tiling, base_flags,
extended, have_fmtlist, always_mutable, configs, MAX_IMAGE_CONFIGS);
count = dedup_configs(configs, count);
for (unsigned i = 0; i < count; i++) {
if (try_image_config(screen, ici, &configs[i], format_list, DRM_FORMAT_MOD_INVALID))
goto found;
}
}
*out_success = false;
return DRM_FORMAT_MOD_INVALID;
}
found:
/* want_cube already requires samples==1, so CUBE_COMPATIBLE
* should be free per spec 44.1.1.
*/
if (want_cube) {
VkImageUsageFlags saved_usage = ici->usage;
VkImageCreateFlags saved_flags = ici->flags;
ici->flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
VkImageUsageFlags usage = ici->usage;
if (!set_image_usage(screen, ici, templ, bind, modifiers_count, modifiers, &mod)) {
ici->flags &= ~VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
ici->usage = usage;
if (!check_ici(screen, ici, mod)) {
ici->flags = saved_flags;
ici->usage = saved_usage;
}
}
*success = true;
*out_success = true;
return mod;
}
@ -1406,16 +1489,16 @@ create_image(struct zink_screen *screen, struct zink_resource_object *obj,
init_ici(screen, &ici, templ, templ->bind, ici_modifier_count);
bool success = false;
uint64_t mod = eval_ici(screen, &ici, templ, templ->bind, ici_modifier_count, ici_modifiers, &success);
VkImageFormatListCreateInfo *fmtlist = (formats[0] && formats[1]) ? &format_list : NULL;
uint64_t mod = negotiate_image_config(screen, &ici, templ, templ->bind, ici_modifier_count, ici_modifiers, fmtlist, &success);
if (ici.format == VK_FORMAT_A8_UNORM_KHR && !success) {
ici.format = zink_get_format(screen, zink_format_get_emulated_alpha(templ->format));
mod = eval_ici(screen, &ici, templ, templ->bind, ici_modifier_count, ici_modifiers, &success);
mod = negotiate_image_config(screen, &ici, templ, templ->bind, ici_modifier_count, ici_modifiers, fmtlist, &success);
}
/* images with modifiers MUST have a format list if they are mutable */
if (modifiers_count && ici.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) {
const VkImageFormatListCreateInfo *format_list = vk_find_struct_const(&ici, IMAGE_FORMAT_LIST_CREATE_INFO);
assert(format_list);
if (!format_list)
assert(ici.pNext != NULL);
if (!ici.pNext)
return roc_fail_and_free_object;
}
if (!success)