From 721aa39ad9090932f945c38ce1cd95502e81a4e5 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 22 Jul 2023 18:37:40 +0900 Subject: [PATCH] asahi: Impose limits on resource shadowing Apps can have pathological use cases where huge resources are shadowed repeatedly. An app that alternately writes to a resource and then uses it to draw can create an unbounded amount of shadow BOs. To fix this, introduce both a maximum resource size for shadowing, and a maximum cumulative size that resource may be shadowed before we start flushing readers. The flush path then clears the counter, as does the happy path where there are no readers left after flushing writers. Fixes massive memory bloating in Firefox and probably others. Signed-off-by: Asahi Lina Part-of: --- src/gallium/drivers/asahi/agx_pipe.c | 30 +++++++++++++++++++++++++++++- src/gallium/drivers/asahi/agx_state.h | 5 +++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/gallium/drivers/asahi/agx_pipe.c b/src/gallium/drivers/asahi/agx_pipe.c index bffb440..81c0c66 100644 --- a/src/gallium/drivers/asahi/agx_pipe.c +++ b/src/gallium/drivers/asahi/agx_pipe.c @@ -79,6 +79,20 @@ uint64_t agx_best_modifiers[] = { DRM_FORMAT_MOD_LINEAR, }; +/* These limits are arbitrarily chosen and subject to change as + * we discover more workloads with heavy shadowing. + * + * Maximum size of a shadowed object in bytes. + * Hint: 1024x1024xRGBA8 = 4 MiB. Go higher for compression. + */ +#define MAX_SHADOW_BYTES (6 * 1024 * 1024) + +/* Maximum cumulative size to shadow an object before we flush. + * Allows shadowing a 4MiB + meta object 8 times with the logic + * below (+1 shadow offset implied). + */ +#define MAX_TOTAL_SHADOW_BYTES (32 * 1024 * 1024) + void agx_init_state_functions(struct pipe_context *ctx); /* @@ -670,6 +684,16 @@ agx_shadow(struct agx_context *ctx, struct agx_resource *rsrc, bool needs_copy) if (flags & (AGX_BO_SHARED | AGX_BO_SHAREABLE)) return false; + /* Do not shadow resources that are too large */ + if (size > MAX_SHADOW_BYTES) + return false; + + /* Do not shadow resources too much */ + if (rsrc->shadowed_bytes >= MAX_TOTAL_SHADOW_BYTES) + return false; + + rsrc->shadowed_bytes += size; + /* 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 @@ -754,8 +778,10 @@ agx_prepare_for_map(struct agx_context *ctx, struct agx_resource *rsrc, /* If there are no readers, we're done. We check at the start to * avoid expensive shadowing paths or duplicated checks in this hapyp path. */ - if (!agx_any_batch_uses_resource(ctx, rsrc)) + if (!agx_any_batch_uses_resource(ctx, rsrc)) { + rsrc->shadowed_bytes = 0; return; + } /* There are readers. Try to shadow the resource to avoid a sync */ if (!(rsrc->base.flags & PIPE_RESOURCE_FLAG_MAP_PERSISTENT) && @@ -764,6 +790,8 @@ agx_prepare_for_map(struct agx_context *ctx, struct agx_resource *rsrc, /* Otherwise, we need to sync */ agx_sync_readers(ctx, rsrc, "Unsynchronized write"); + + rsrc->shadowed_bytes = 0; } /* diff --git a/src/gallium/drivers/asahi/agx_state.h b/src/gallium/drivers/asahi/agx_state.h index 9334e12..06c49ed 100644 --- a/src/gallium/drivers/asahi/agx_state.h +++ b/src/gallium/drivers/asahi/agx_state.h @@ -615,6 +615,11 @@ struct agx_resource { /* Valid buffer range tracking, to optimize buffer appends */ struct util_range valid_buffer_range; + + /* Cumulative shadowed byte count for this resource, that is, the number of + * times multiplied by the resource size. + */ + size_t shadowed_bytes; }; static inline struct agx_resource * -- 2.7.4