asahi: Copy resources if needed to shadow
authorAlyssa Rosenzweig <alyssa@rosenzweig.io>
Sat, 11 Mar 2023 22:37:19 +0000 (17:37 -0500)
committerAlyssa Rosenzweig <alyssa@rosenzweig.io>
Sun, 7 May 2023 13:05:39 +0000 (09:05 -0400)
This lets us shadow textures updated in the middle of rendering in Quake3.
They're big memcpys, but as long as the texture memory is cached it's ok. We use
a heuristic to avoid too many memcpys from uncached memory, which would cause
slideshow performance in quake.

We need to be careful to avoid shadowing shared resources, though, that's
invalid and would break WSI pretty hard.

It would be better to blit on the GPU for large shadowing, but that's more
involved and left for future work.

Reduces stuttering in Quake3.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/22891>

src/gallium/drivers/asahi/agx_pipe.c

index 3149d9d..dfbb405 100644 (file)
@@ -642,16 +642,40 @@ agx_transfer_flush_region(struct pipe_context *pipe,
 
 /* Reallocate the backing buffer of a resource, returns true if successful */
 static bool
-agx_shadow(struct agx_context *ctx, struct agx_resource *rsrc)
+agx_shadow(struct agx_context *ctx, struct agx_resource *rsrc, bool needs_copy)
 {
    struct agx_device *dev = agx_device(ctx->base.screen);
    struct agx_bo *old = rsrc->bo;
-   struct agx_bo *new_ = agx_bo_create(dev, old->size, old->flags, old->label);
+   unsigned flags = old->flags;
+
+   /* If a resource is (or could be) shared, shadowing would desync across
+    * processes. (It's also not what this path is for.)
+    */
+   if (flags & (AGX_BO_SHARED | AGX_BO_SHAREABLE))
+      return false;
+
+   /* If we need to copy, we reallocate the resource with cached-coherent
+    * memory. This is a heuristic: it assumes that if the app needs a shadows
+    * (with a copy) now, it will again need to shadow-and-copy the same resource
+    * in the future. This accelerates the later copies, since otherwise the copy
+    * involves reading uncached memory.
+    */
+   if (needs_copy)
+      flags |= AGX_BO_WRITEBACK;
+
+   struct agx_bo *new_ = agx_bo_create(dev, old->size, flags, old->label);
 
    /* If allocation failed, we can fallback on a flush gracefully*/
    if (new_ == NULL)
       return false;
 
+   if (needs_copy) {
+      perf_debug_ctx(ctx, "Shadowing %zu bytes on the CPU (%s)", old->size,
+                     (old->flags & AGX_BO_WRITEBACK) ? "cached" : "uncached");
+
+      memcpy(new_->ptr.cpu, old->ptr.cpu, old->size);
+   }
+
    /* Swap the pointers, dropping a reference */
    agx_bo_unreference(rsrc->bo);
    rsrc->bo = new_;
@@ -717,7 +741,8 @@ agx_prepare_for_map(struct agx_context *ctx, struct agx_resource *rsrc,
       return;
 
    /* There are readers. Try to shadow the resource to avoid a sync */
-   if ((usage & PIPE_MAP_DISCARD_WHOLE_RESOURCE) && agx_shadow(ctx, rsrc))
+   if (!(rsrc->base.flags & PIPE_RESOURCE_FLAG_MAP_PERSISTENT) &&
+       agx_shadow(ctx, rsrc, !(usage & PIPE_MAP_DISCARD_WHOLE_RESOURCE)))
       return;
 
    /* Otherwise, we need to sync */