From: Phil Dennis-Jordan <phil@philjordan.eu>
To: qemu-devel@nongnu.org, pbonzini@redhat.com, agraf@csgraf.de,
graf@amazon.com, marcandre.lureau@redhat.com,
berrange@redhat.com, thuth@redhat.com, philmd@linaro.org,
peter.maydell@linaro.org, akihiko.odaki@daynix.com,
phil@philjordan.eu, lists@philjordan.eu
Subject: [PATCH 17/26] hw/display/apple-gfx: Asynchronous rendering and graphics update
Date: Mon, 15 Jul 2024 23:06:56 +0200 [thread overview]
Message-ID: <20240715210705.32365-18-phil@philjordan.eu> (raw)
In-Reply-To: <20240715210705.32365-1-phil@philjordan.eu>
This change avoids doing expensive rendering while holding the BQL.
Rendering with the lock held is not only inefficient, it can also
cause deadlocks when the PV Graphics framework’s encode... method
causes a (synchronous) call to a callback, which in turn tries to
acquire the BQL.
Signed-off-by: Phil Dennis-Jordan <phil@philjordan.eu>
---
hw/display/apple-gfx.h | 15 +++-
hw/display/apple-gfx.m | 193 +++++++++++++++++++++++++++--------------
2 files changed, 138 insertions(+), 70 deletions(-)
diff --git a/hw/display/apple-gfx.h b/hw/display/apple-gfx.h
index fa7fea6368..9d6d40795e 100644
--- a/hw/display/apple-gfx.h
+++ b/hw/display/apple-gfx.h
@@ -15,12 +15,14 @@ void apple_gfx_common_init(Object *obj, AppleGFXState *s, const char* obj_name);
#include "qemu/osdep.h"
#include "exec/memory.h"
#include "ui/surface.h"
+#include <dispatch/dispatch.h>
@class PGDeviceDescriptor;
@protocol PGDevice;
@protocol PGDisplay;
@protocol MTLDevice;
@protocol MTLTexture;
+@protocol MTLCommandQueue;
typedef QTAILQ_HEAD(, PGTask_s) AppleGFXTaskList;
@@ -30,14 +32,21 @@ struct AppleGFXState {
id<PGDisplay> pgdisp;
AppleGFXTaskList tasks;
QemuConsole *con;
- void *vram;
id<MTLDevice> mtl;
- id<MTLTexture> texture;
+ id<MTLCommandQueue> mtl_queue;
bool handles_frames;
bool new_frame;
bool cursor_show;
- DisplaySurface *surface;
QEMUCursor *cursor;
+
+ dispatch_queue_t render_queue;
+ /* The following fields should only be accessed from render_queue: */
+ bool gfx_update_requested;
+ bool new_frame_ready;
+ int32_t pending_frames;
+ void *vram;
+ DisplaySurface *surface;
+ id<MTLTexture> texture;
};
void apple_gfx_common_realize(AppleGFXState *s, PGDeviceDescriptor *desc);
diff --git a/hw/display/apple-gfx.m b/hw/display/apple-gfx.m
index 48463e5a1f..5855d1d7f5 100644
--- a/hw/display/apple-gfx.m
+++ b/hw/display/apple-gfx.m
@@ -35,6 +35,9 @@
static Error *apple_gfx_mig_blocker;
+static void apple_gfx_render_frame_completed(AppleGFXState *s, void *vram,
+ id<MTLTexture> texture);
+
static AppleGFXTask *apple_gfx_new_task(AppleGFXState *s, uint64_t len)
{
mach_vm_address_t task_mem;
@@ -105,41 +108,63 @@ static void apple_gfx_write(void *opaque, hwaddr offset, uint64_t val,
},
};
-static void apple_gfx_fb_update_display(void *opaque)
+static void apple_gfx_render_new_frame(AppleGFXState *s)
{
- AppleGFXState *s = opaque;
-
- if (!s->new_frame || !s->handles_frames) {
+ BOOL r;
+ void *vram = s->vram;
+ uint32_t width = surface_width(s->surface);
+ uint32_t height = surface_height(s->surface);
+ MTLRegion region = MTLRegionMake2D(0, 0, width, height);
+ id<MTLCommandBuffer> command_buffer = [s->mtl_queue commandBuffer];
+ id<MTLTexture> texture = s->texture;
+ r = [s->pgdisp encodeCurrentFrameToCommandBuffer:command_buffer
+ texture:texture
+ region:region];
+ if (!r) {
return;
}
+ [texture retain];
+
+ [command_buffer retain];
+ [command_buffer addCompletedHandler:
+ ^(id<MTLCommandBuffer> cb)
+ {
+ dispatch_async(s->render_queue, ^{
+ apple_gfx_render_frame_completed(s, vram, texture);
+ [texture release];
+ });
+ [command_buffer release];
+ }];
+ [command_buffer commit];
+}
- @autoreleasepool {
- s->new_frame = false;
-
- BOOL r;
- uint32_t width = surface_width(s->surface);
- uint32_t height = surface_height(s->surface);
- MTLRegion region = MTLRegionMake2D(0, 0, width, height);
- id<MTLCommandQueue> commandQueue = [s->mtl newCommandQueue];
- id<MTLCommandBuffer> mipmapCommandBuffer = [commandQueue commandBuffer];
-
- r = [s->pgdisp encodeCurrentFrameToCommandBuffer:mipmapCommandBuffer
- texture:s->texture
- region:region];
+static void copy_mtl_texture_to_surface_mem(id<MTLTexture> texture, void *vram)
+{
+ /* TODO: Skip this entirely on a pure Metal or headless/guest-only
+ * rendering path, else use a blit command encoder? Needs careful
+ * (double?) buffering design. */
+ size_t width = texture.width, height = texture.height;
+ MTLRegion region = MTLRegionMake2D(0, 0, width, height);
+ [texture getBytes:vram
+ bytesPerRow:(width * 4)
+ bytesPerImage:(width * height * 4)
+ fromRegion:region
+ mipmapLevel:0
+ slice:0];
+}
- if (r != YES) {
- return;
- }
+static void apple_gfx_render_frame_completed(AppleGFXState *s, void *vram,
+ id<MTLTexture> texture)
+{
+ --s->pending_frames;
+ assert(s->pending_frames >= 0);
- id<MTLBlitCommandEncoder> blitCommandEncoder = [mipmapCommandBuffer blitCommandEncoder];
- [blitCommandEncoder endEncoding];
- [mipmapCommandBuffer commit];
- [mipmapCommandBuffer waitUntilCompleted];
- [s->texture getBytes:s->vram bytesPerRow:(width * 4)
- bytesPerImage: (width * height * 4)
- fromRegion: region
- mipmapLevel: 0
- slice: 0];
+ if (vram != s->vram) {
+ /* Display mode has changed, drop this old frame. */
+ assert(texture != s->texture);
+ g_free(vram);
+ } else {
+ copy_mtl_texture_to_surface_mem(texture, vram);
/* Need to render cursor manually if not supported by backend */
if (!dpy_cursor_define_supported(s->con) && s->cursor && s->cursor_show) {
@@ -159,14 +184,40 @@ static void apple_gfx_fb_update_display(void *opaque)
pixman_image_unref(image);
}
- dpy_gfx_update_full(s->con);
-
- [commandQueue release];
+ if (s->gfx_update_requested) {
+ s->gfx_update_requested = false;
+ dpy_gfx_update_full(s->con);
+ graphic_hw_update_done(s->con);
+ s->new_frame_ready = false;
+ } else {
+ s->new_frame_ready = true;
+ }
+ }
+ if (s->pending_frames > 0) {
+ apple_gfx_render_new_frame(s);
}
}
+static void apple_gfx_fb_update_display(void *opaque)
+{
+ AppleGFXState *s = opaque;
+
+ dispatch_async(s->render_queue, ^{
+ if (s->pending_frames > 0) {
+ s->gfx_update_requested = true;
+ } else {
+ if (s->new_frame_ready) {
+ dpy_gfx_update_full(s->con);
+ s->new_frame_ready = false;
+ }
+ graphic_hw_update_done(s->con);
+ }
+ });
+}
+
static const GraphicHwOps apple_gfx_fb_ops = {
.gfx_update = apple_gfx_fb_update_display,
+ .gfx_update_async = true,
};
static void update_cursor(AppleGFXState *s)
@@ -182,28 +233,23 @@ static void update_cursor(AppleGFXState *s)
static void set_mode(AppleGFXState *s, uint32_t width, uint32_t height)
{
void *vram = NULL;
- void *old_vram = s->vram;
DisplaySurface *surface;
MTLTextureDescriptor *textureDescriptor;
- id<MTLTexture> old_texture = nil;
id<MTLTexture> texture = nil;
- bool locking_required = false;
-
- locking_required = !bql_locked();
- if (locking_required) {
- bql_lock();
- }
- if (s->surface &&
- width == surface_width(s->surface) &&
- height == surface_height(s->surface)) {
- if (locking_required) {
- bql_unlock();
- }
+ __block bool no_change = false;
+
+ dispatch_sync(s->render_queue,
+ ^{
+ if (s->surface &&
+ width == surface_width(s->surface) &&
+ height == surface_height(s->surface)) {
+ no_change = true;
+ }
+ });
+
+ if (no_change) {
return;
}
- if (locking_required) {
- bql_unlock();
- }
vram = g_malloc0(width * height * 4);
surface = qemu_create_displaysurface_from(width, height, PIXMAN_LE_a8r8g8b8,
@@ -220,21 +266,23 @@ static void set_mode(AppleGFXState *s, uint32_t width, uint32_t height)
texture = [s->mtl newTextureWithDescriptor:textureDescriptor];
}
- if (locking_required) {
- bql_lock();
- }
- old_vram = s->vram;
- s->vram = vram;
- s->surface = surface;
- dpy_gfx_replace_surface(s->con, surface);
- old_texture = s->texture;
- s->texture = texture;
- if (locking_required) {
- bql_unlock();
- }
+ dispatch_sync(s->render_queue,
+ ^{
+ id<MTLTexture> old_texture = nil;
+ void *old_vram = s->vram;
+ s->vram = vram;
+ s->surface = surface;
- g_free(old_vram);
- [old_texture release];
+ dpy_gfx_replace_surface(s->con, surface);
+
+ old_texture = s->texture;
+ s->texture = texture;
+ [old_texture release];
+
+ if (s->pending_frames == 0) {
+ g_free(old_vram);
+ }
+ });
}
static void create_fb(AppleGFXState *s)
@@ -354,10 +402,18 @@ static void apple_gfx_register_task_mapping_handlers(AppleGFXState *s,
disp_desc.queue = dispatch_get_main_queue();
disp_desc.newFrameEventHandler = ^(void) {
trace_apple_gfx_new_frame();
-
- /* Tell QEMU gfx stack that a new frame arrived */
- s->handles_frames = true;
- s->new_frame = true;
+ dispatch_async(s->render_queue, ^{
+ /* Drop frames if we get too far ahead. */
+ if (s->pending_frames >= 2)
+ return;
+ ++s->pending_frames;
+ if (s->pending_frames > 1) {
+ return;
+ }
+ @autoreleasepool {
+ apple_gfx_render_new_frame(s);
+ }
+ });
};
disp_desc.modeChangeHandler = ^(PGDisplayCoord_t sizeInPixels,
OSType pixelFormat) {
@@ -422,7 +478,10 @@ void apple_gfx_common_realize(AppleGFXState *s, PGDeviceDescriptor *desc)
PGDisplayDescriptor *disp_desc = nil;
QTAILQ_INIT(&s->tasks);
+ s->render_queue = dispatch_queue_create("apple-gfx.render",
+ DISPATCH_QUEUE_SERIAL);
s->mtl = MTLCreateSystemDefaultDevice();
+ s->mtl_queue = [s->mtl newCommandQueue];
desc.device = s->mtl;
--
2.39.3 (Apple Git-146)
next prev parent reply other threads:[~2024-07-17 11:06 UTC|newest]
Thread overview: 36+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-07-15 21:06 Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 01/26] hw/vmapple/apple-gfx: Introduce ParavirtualizedGraphics.Framework support Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 02/26] hw/vmapple/apple-gfx: BQL renaming update Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 03/26] hw/display/apple-gfx: Moved from hw/vmapple/ Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 04/26] hw/display/apple-gfx: uses DEFINE_TYPES macro Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 05/26] hw/display/apple-gfx: native -> little endian memory ops Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 06/26] hw/display/apple-gfx: Removes dead/superfluous code Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 07/26] hw/display/apple-gfx: Makes set_mode thread & memory safe Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 08/26] hw/display/apple-gfx: Adds migration blocker Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 09/26] hw/display/apple-gfx: Wraps ObjC autorelease code in pool Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 10/26] hw/display/apple-gfx: Fixes ObjC new/init misuse, plugs leaks Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 11/26] hw/display/apple-gfx: Uses ObjC category extension for private property Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 12/26] hw/display/apple-gfx: Task memory mapping cleanup Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 13/26] hw/display/apple-gfx: Defines PGTask_s struct instead of casting Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 14/26] hw/display/apple-gfx: Refactoring of realize function Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 15/26] hw/display/apple-gfx: Separates generic & vmapple-specific functionality Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 16/26] hw/display/apple-gfx: Asynchronous MMIO writes on x86-64 Phil Dennis-Jordan
2024-07-15 21:26 ` Philippe Mathieu-Daudé
2024-07-16 14:29 ` Phil Dennis-Jordan
2024-07-16 14:48 ` BALATON Zoltan
2024-07-17 11:09 ` Phil Dennis-Jordan
2024-07-15 21:06 ` Phil Dennis-Jordan [this message]
2024-07-15 21:06 ` [PATCH 18/26] hw/display/apple-gfx: Adds PCI implementation Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 19/26] ui/cocoa: Adds non-app runloop on main thread mode Phil Dennis-Jordan
2024-07-15 21:06 ` [PATCH 20/26] hw/display/apple-gfx: Fixes cursor hotspot handling Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 21/26] hw/display/apple-gfx: Implements texture syncing for non-UMA GPUs Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 22/26] hw/display/apple-gfx: Replaces magic number with queried MMIO length Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 23/26] hw/display/apple-gfx: Host GPU picking improvements Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 24/26] hw/display/apple-gfx: Adds configurable mode list Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 25/26] MAINTAINERS: Add myself as maintainer for apple-gfx, reviewer for HVF Phil Dennis-Jordan
2024-07-15 21:07 ` [PATCH 26/26] hw/display/apple-gfx: Removes UI pointer support check Phil Dennis-Jordan
2024-07-16 6:07 ` Akihiko Odaki
2024-07-16 6:38 ` hw/display/apple-gfx Philippe Mathieu-Daudé
2024-07-16 6:47 ` hw/display/apple-gfx Akihiko Odaki
2024-07-17 11:12 ` hw/display/apple-gfx Phil Dennis-Jordan
2024-07-17 11:16 ` Phil Dennis-Jordan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240715210705.32365-18-phil@philjordan.eu \
--to=phil@philjordan.eu \
--cc=agraf@csgraf.de \
--cc=akihiko.odaki@daynix.com \
--cc=berrange@redhat.com \
--cc=graf@amazon.com \
--cc=lists@philjordan.eu \
--cc=marcandre.lureau@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=qemu-devel@nongnu.org \
--cc=thuth@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).