zink: add automatic swapchain readback using heuristics

in cases where apps (stupidly) do swapbuffers->blitframebuffer, there's
no functional way to (legitimately) perform readback on the just-presented
vk image, which leads to the existing acquire+present loop dance

this adds a counter threshold which, when exceeded, begins copying the
scanout image for swapchains to provide local readback on images without
the massive perf penalty of roundtrips

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/25754>
This commit is contained in:
Mike Blumenkrantz 2023-10-16 15:45:41 -04:00 committed by Marge Bot
parent 5f16f52dfa
commit 10c2180a92
4 changed files with 84 additions and 21 deletions

View file

@ -52,6 +52,7 @@ blit_resolve(struct zink_context *ctx, const struct pipe_blit_info *info, bool *
return false;
struct zink_resource *src = zink_resource(info->src.resource);
struct zink_resource *use_src = src;
struct zink_resource *dst = zink_resource(info->dst.resource);
struct zink_screen *screen = zink_screen(ctx->base.screen);
@ -67,16 +68,16 @@ blit_resolve(struct zink_context *ctx, const struct pipe_blit_info *info, bool *
zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));
if (src->obj->dt)
*needs_present_readback = zink_kopper_acquire_readback(ctx, src);
*needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);
struct zink_batch *batch = &ctx->batch;
zink_resource_setup_transfer_layouts(ctx, src, dst);
zink_resource_setup_transfer_layouts(ctx, use_src, dst);
VkCommandBuffer cmdbuf = *needs_present_readback ?
ctx->batch.state->cmdbuf :
zink_get_cmdbuf(ctx, src, dst);
if (cmdbuf == ctx->batch.state->cmdbuf)
zink_flush_dgc_if_enabled(ctx);
zink_batch_reference_resource_rw(batch, src, false);
zink_batch_reference_resource_rw(batch, use_src, false);
zink_batch_reference_resource_rw(batch, dst, true);
bool marker = zink_cmd_debug_marker_begin(ctx, cmdbuf, "blit_resolve(%s->%s, %dx%d->%dx%d)",
@ -121,7 +122,7 @@ blit_resolve(struct zink_context *ctx, const struct pipe_blit_info *info, bool *
region.extent.width = info->dst.box.width;
region.extent.height = info->dst.box.height;
region.extent.depth = info->dst.box.depth;
VKCTX(CmdResolveImage)(cmdbuf, src->obj->image, src->layout,
VKCTX(CmdResolveImage)(cmdbuf, use_src->obj->image, src->layout,
dst->obj->image, dst->layout,
1, &region);
zink_cmd_debug_marker_end(ctx, cmdbuf, marker);
@ -151,6 +152,7 @@ blit_native(struct zink_context *ctx, const struct pipe_blit_info *info, bool *n
return false;
struct zink_resource *src = zink_resource(info->src.resource);
struct zink_resource *use_src = src;
struct zink_resource *dst = zink_resource(info->dst.resource);
struct zink_screen *screen = zink_screen(ctx->base.screen);
@ -261,16 +263,16 @@ blit_native(struct zink_context *ctx, const struct pipe_blit_info *info, bool *n
zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));
if (src->obj->dt)
*needs_present_readback = zink_kopper_acquire_readback(ctx, src);
*needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);
struct zink_batch *batch = &ctx->batch;
zink_resource_setup_transfer_layouts(ctx, src, dst);
zink_resource_setup_transfer_layouts(ctx, use_src, dst);
VkCommandBuffer cmdbuf = *needs_present_readback ?
ctx->batch.state->cmdbuf :
zink_get_cmdbuf(ctx, src, dst);
if (cmdbuf == ctx->batch.state->cmdbuf)
zink_flush_dgc_if_enabled(ctx);
zink_batch_reference_resource_rw(batch, src, false);
zink_batch_reference_resource_rw(batch, use_src, false);
zink_batch_reference_resource_rw(batch, dst, true);
bool marker = zink_cmd_debug_marker_begin(ctx, cmdbuf, "blit_native(%s->%s, %dx%d->%dx%d)",
@ -279,7 +281,7 @@ blit_native(struct zink_context *ctx, const struct pipe_blit_info *info, bool *n
info->src.box.width, info->src.box.height,
info->dst.box.width, info->dst.box.height);
VKCTX(CmdBlitImage)(cmdbuf, src->obj->image, src->layout,
VKCTX(CmdBlitImage)(cmdbuf, use_src->obj->image, src->layout,
dst->obj->image, dst->layout,
1, &region,
zink_filter(info->filter));
@ -317,6 +319,7 @@ zink_blit(struct pipe_context *pctx,
const struct util_format_description *dst_desc = util_format_description(info->dst.format);
struct zink_resource *src = zink_resource(info->src.resource);
struct zink_resource *use_src = src;
struct zink_resource *dst = zink_resource(info->dst.resource);
bool needs_present_readback = false;
if (zink_is_swapchain(dst)) {
@ -365,7 +368,7 @@ zink_blit(struct pipe_context *pctx,
if (src->obj->dt) {
zink_fb_clears_apply_region(ctx, info->src.resource, zink_rect_from_box(&info->src.box));
needs_present_readback = zink_kopper_acquire_readback(ctx, src);
needs_present_readback = zink_kopper_acquire_readback(ctx, src, &use_src);
}
/* this is discard_only because we're about to start a renderpass that will
@ -426,7 +429,7 @@ zink_blit(struct pipe_context *pctx,
zink_resource_object_init_mutable(ctx, src);
if (zink_format_needs_mutable(info->dst.format, info->dst.resource->format))
zink_resource_object_init_mutable(ctx, dst);
zink_blit_barriers(ctx, src, dst, whole);
zink_blit_barriers(ctx, use_src, dst, whole);
ctx->blitting = true;
if (stencil_blit) {
@ -449,7 +452,9 @@ zink_blit(struct pipe_context *pctx,
pipe_surface_release(pctx, &dst_view);
} else {
util_blitter_blit(ctx->blitter, info);
struct pipe_blit_info new_info = *info;
new_info.src.resource = &use_src->base.b;
util_blitter_blit(ctx->blitter, &new_info);
}
ctx->blitting = false;
ctx->rp_clears_enabled = rp_clears_enabled;

View file

@ -3777,8 +3777,10 @@ zink_flush(struct pipe_context *pctx,
p_atomic_inc(&screen->renderdoc_frame);
#endif
if (ctx->needs_present && ctx->needs_present->obj->image &&
zink_is_swapchain(ctx->needs_present))
zink_is_swapchain(ctx->needs_present)) {
zink_kopper_readback_update(ctx, ctx->needs_present);
screen->image_barrier(ctx, ctx->needs_present, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
ctx->needs_present = NULL;
}
@ -4062,6 +4064,7 @@ zink_flush_resource(struct pipe_context *pctx,
if (res->obj->dt) {
if (zink_kopper_acquired(res->obj->dt, res->obj->dt_idx)) {
zink_batch_no_rp_safe(ctx);
zink_kopper_readback_update(ctx, res);
zink_screen(ctx->base.screen)->image_barrier(ctx, res, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
zink_batch_reference_resource_rw(&ctx->batch, res, true);
} else {
@ -4447,6 +4450,7 @@ zink_copy_image_buffer(struct zink_context *ctx, struct zink_resource *dst, stru
unsigned src_level, const struct pipe_box *src_box, enum pipe_map_flags map_flags)
{
struct zink_resource *img = dst->base.b.target == PIPE_BUFFER ? src : dst;
struct zink_resource *use_img = img;
struct zink_resource *buf = dst->base.b.target == PIPE_BUFFER ? dst : src;
struct zink_batch *batch = &ctx->batch;
bool needs_present_readback = false;
@ -4466,8 +4470,8 @@ zink_copy_image_buffer(struct zink_context *ctx, struct zink_resource *dst, stru
zink_screen(ctx->base.screen)->buffer_barrier(ctx, buf, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
} else {
if (zink_is_swapchain(img))
needs_present_readback = zink_kopper_acquire_readback(ctx, img);
zink_screen(ctx->base.screen)->image_barrier(ctx, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, 0);
needs_present_readback = zink_kopper_acquire_readback(ctx, img, &use_img);
zink_screen(ctx->base.screen)->image_barrier(ctx, use_img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, 0);
zink_resource_buffer_transfer_dst_barrier(ctx, buf, dstx, src_box->width);
}
@ -4513,8 +4517,8 @@ zink_copy_image_buffer(struct zink_context *ctx, struct zink_resource *dst, stru
/* never promote to unordered if swapchain was acquired */
VkCommandBuffer cmdbuf = needs_present_readback ?
ctx->batch.state->cmdbuf :
buf2img ? zink_get_cmdbuf(ctx, buf, img) : zink_get_cmdbuf(ctx, img, buf);
zink_batch_reference_resource_rw(batch, img, buf2img);
buf2img ? zink_get_cmdbuf(ctx, buf, use_img) : zink_get_cmdbuf(ctx, use_img, buf);
zink_batch_reference_resource_rw(batch, use_img, buf2img);
zink_batch_reference_resource_rw(batch, buf, !buf2img);
/* we're using u_transfer_helper_deinterleave, which means we'll be getting PIPE_MAP_* usage
@ -4563,14 +4567,14 @@ zink_copy_image_buffer(struct zink_context *ctx, struct zink_resource *dst, stru
region.imageExtent.width,
region.imageExtent.height,
MAX2(region.imageSubresource.layerCount, region.imageExtent.depth));
VKCTX(CmdCopyBufferToImage)(cmdbuf, buf->obj->buffer, img->obj->image, img->layout, 1, &region);
VKCTX(CmdCopyBufferToImage)(cmdbuf, buf->obj->buffer, use_img->obj->image, use_img->layout, 1, &region);
} else {
marker = zink_cmd_debug_marker_begin(ctx, cmdbuf, "copy_image2buffer(%s, %dx%dx%d)",
util_format_short_name(src->base.b.format),
region.imageExtent.width,
region.imageExtent.height,
MAX2(region.imageSubresource.layerCount, region.imageExtent.depth));
VKCTX(CmdCopyImageToBuffer)(cmdbuf, img->obj->image, img->layout, buf->obj->buffer, 1, &region);
VKCTX(CmdCopyImageToBuffer)(cmdbuf, use_img->obj->image, use_img->layout, buf->obj->buffer, 1, &region);
}
zink_cmd_debug_marker_end(ctx, cmdbuf, marker);
}

View file

@ -151,6 +151,7 @@ destroy_swapchain(struct zink_screen *screen, struct kopper_swapchain *cswap)
simple_mtx_lock(&screen->semaphores_lock);
util_dynarray_append(&screen->semaphores, VkSemaphore, cswap->images[i].acquire);
simple_mtx_unlock(&screen->semaphores_lock);
pipe_resource_reference(&cswap->images[i].readback, NULL);
}
free(cswap->images);
hash_table_foreach(cswap->presents, he) {
@ -555,6 +556,8 @@ kopper_acquire(struct zink_screen *screen, struct zink_resource *res, uint64_t t
}
cdt->swapchain->images[res->obj->dt_idx].acquire = acquire;
if (cdt->swapchain->images[res->obj->dt_idx].readback)
zink_resource(cdt->swapchain->images[res->obj->dt_idx].readback)->valid = false;
res->obj->image = cdt->swapchain->images[res->obj->dt_idx].image;
cdt->swapchain->images[res->obj->dt_idx].acquired = false;
if (!cdt->swapchain->images[res->obj->dt_idx].init) {
@ -827,8 +830,23 @@ zink_kopper_present_queue(struct zink_screen *screen, struct zink_resource *res)
res->obj->dt_idx = UINT32_MAX;
}
static void
kopper_ensure_readback(struct zink_screen *screen, struct zink_resource *res)
{
struct kopper_displaytarget *cdt = res->obj->dt;
struct kopper_swapchain *cswap = cdt->swapchain;
for (unsigned i = 0; i < cswap->num_images; i++) {
if (cswap->images[i].readback)
return;
struct pipe_resource templ = res->base.b;
templ.bind = PIPE_BIND_RENDER_TARGET | PIPE_BIND_SAMPLER_VIEW;
cswap->images[i].readback = screen->base.resource_create(&screen->base, &templ);
}
}
bool
zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res)
zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res, struct zink_resource **readback)
{
struct zink_screen *screen = zink_screen(ctx->base.screen);
assert(res->obj->dt);
@ -836,10 +854,22 @@ zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res
const struct kopper_swapchain *cswap = cdt->swapchain;
uint32_t last_dt_idx = res->obj->last_dt_idx;
VkResult ret = VK_SUCCESS;
/* if this hasn't been presented or if it has data, use this as the readback target */
if (res->obj->last_dt_idx == UINT32_MAX ||
(zink_kopper_acquired(cdt, res->obj->dt_idx) && cdt->swapchain->images[res->obj->dt_idx].dt_has_data))
(zink_kopper_acquired(cdt, res->obj->dt_idx) && cdt->swapchain->images[res->obj->dt_idx].dt_has_data)) {
*readback = res;
return false;
}
if (cswap->images[last_dt_idx].readback) {
struct zink_resource *rb = zink_resource(cswap->images[res->obj->last_dt_idx].readback);
if (rb->valid) {
*readback = rb;
return false;
}
}
if (++cdt->readback_counter >= ZINK_READBACK_THRESHOLD)
kopper_ensure_readback(screen, res);
while (res->obj->dt_idx != last_dt_idx) {
if (res->obj->dt_idx != UINT32_MAX && !zink_kopper_present_readback(ctx, res))
break;
@ -848,6 +878,7 @@ zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res
} while (!is_swapchain_kill(ret) && (ret == VK_NOT_READY || ret == VK_TIMEOUT));
if (is_swapchain_kill(ret)) {
kill_swapchain(ctx, res);
*readback = NULL;
return false;
}
}
@ -857,6 +888,7 @@ zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res
res->base.b.height0 = ctx->swapchain_size.height;
}
zink_batch_usage_set(&cdt->swapchain->batch_uses, ctx->batch.state);
*readback = res;
return true;
}
@ -865,6 +897,7 @@ zink_kopper_present_readback(struct zink_context *ctx, struct zink_resource *res
{
struct zink_screen *screen = zink_screen(ctx->base.screen);
VkSubmitInfo si = {0};
assert(zink_is_swapchain(res));
if (res->obj->last_dt_idx == UINT32_MAX)
return true;
if (res->layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
@ -904,6 +937,20 @@ zink_kopper_present_readback(struct zink_context *ctx, struct zink_resource *res
return zink_screen_handle_vkresult(screen, error);
}
void
zink_kopper_readback_update(struct zink_context *ctx, struct zink_resource *res)
{
assert(res->obj->dt);
struct kopper_displaytarget *cdt = res->obj->dt;
struct kopper_swapchain *cswap = cdt->swapchain;
assert(res->obj->dt_idx != UINT32_MAX);
struct pipe_resource *readback = cswap->images[res->obj->dt_idx].readback;
struct pipe_box box = {0, 0, 0, res->base.b.width0, res->base.b.height0, res->base.b.depth0};
if (readback)
ctx->base.resource_copy_region(&ctx->base, readback, 0, 0, 0, 0, &res->base.b, 0, &box);
}
bool
zink_kopper_update(struct pipe_screen *pscreen, struct pipe_resource *pres, int *w, int *h)
{

View file

@ -36,12 +36,16 @@ extern "C" {
struct zink_batch_usage;
/* number of times a swapchain can be read without forcing readback mode */
#define ZINK_READBACK_THRESHOLD 3
struct kopper_swapchain_image {
bool init;
bool acquired;
bool dt_has_data;
int age;
VkImage image;
struct pipe_resource *readback;
VkSemaphore acquire;
VkImageLayout layout;
};
@ -90,6 +94,7 @@ struct kopper_displaytarget
enum kopper_type type;
bool is_kill;
VkPresentModeKHR present_mode;
unsigned readback_counter;
};
struct zink_context;
@ -132,10 +137,12 @@ zink_kopper_present(struct zink_screen *screen, struct zink_resource *res);
void
zink_kopper_present_queue(struct zink_screen *screen, struct zink_resource *res);
bool
zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res);
zink_kopper_acquire_readback(struct zink_context *ctx, struct zink_resource *res, struct zink_resource **readback);
bool
zink_kopper_present_readback(struct zink_context *ctx, struct zink_resource *res);
void
zink_kopper_readback_update(struct zink_context *ctx, struct zink_resource *res);
void
zink_kopper_deinit_displaytarget(struct zink_screen *screen, struct kopper_displaytarget *cdt);
bool
zink_kopper_update(struct pipe_screen *pscreen, struct pipe_resource *pres, int *w, int *h);