Kaynağa Gözat

CocoaSpice: enable GL rendering

osy 4 yıl önce
ebeveyn
işleme
148efde136

+ 103 - 8
CocoaSpice/CSDisplayMetal.m

@@ -22,6 +22,7 @@
 #import <glib.h>
 #import <spice-client.h>
 #import <spice/protocol.h>
+#import <IOSurface/IOSurfaceRef.h>
 
 #ifdef DISPLAY_DEBUG
 #undef DISPLAY_DEBUG
@@ -43,10 +44,14 @@
 @property (nonatomic, readwrite) CGPoint cursorHotspot;
 @property (nonatomic, readwrite) BOOL cursorHidden;
 @property (nonatomic, readwrite) BOOL hasCursor;
+@property (nonatomic, readwrite) BOOL isGLEnabled;
+@property (nonatomic, readwrite) BOOL hasGLDrawAck;
+@property (nonatomic) SpiceGlScanout scanout;
 
 // UTMRenderSource properties
 @property (nonatomic) dispatch_queue_t renderQueue;
-@property (nonatomic, nullable, readwrite) id<MTLTexture> displayTexture;
+@property (nonatomic, nullable, readwrite) id<MTLTexture> canvasTexture;
+@property (nonatomic, nullable, readwrite) id<MTLTexture> glTexture;
 @property (nonatomic, nullable, readwrite) id<MTLTexture> cursorTexture;
 @property (nonatomic, readwrite) NSUInteger displayNumVertices;
 @property (nonatomic, readwrite) NSUInteger cursorNumVertices;
@@ -96,6 +101,7 @@ static void cs_invalidate(SpiceChannel *channel,
                        gint x, gint y, gint w, gint h, gpointer data) {
     CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
     CGRect rect = CGRectIntersection(CGRectMake(x, y, w, h), self->_visibleArea);
+    self.isGLEnabled = NO;
     if (!CGRectIsEmpty(rect)) {
         [self drawRegion:rect];
     }
@@ -151,7 +157,7 @@ static void cs_update_monitor_area(SpiceChannel *channel, GParamSpec *pspec, gpo
     }
     
     /* If only one head on this monitor, update the whole area */
-    if (monitors->len == 1) {
+    if (monitors->len == 1 && !self.isGLEnabled) {
         [self updateVisibleAreaWithRect:CGRectMake(0, 0, c->width, c->height)];
     } else {
         [self updateVisibleAreaWithRect:CGRectMake(c->x, c->y, c->width, c->height)];
@@ -249,6 +255,39 @@ static void cs_cursor_reset(SpiceCursorChannel *channel, gpointer data)
     cs_cursor_invalidate(self);
 }
 
+#pragma mark - GL
+
+static void cs_gl_scanout(SpiceDisplayChannel *channel, GParamSpec *pspec, gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+
+    DISPLAY_DEBUG(self, "%s: got scanout",  __FUNCTION__);
+
+    const SpiceGlScanout *scanout;
+
+    scanout = spice_display_channel_get_gl_scanout(self.display);
+    /* should only be called when the display has a scanout */
+    g_return_if_fail(scanout != NULL);
+    
+    self.isGLEnabled = YES;
+    self.hasGLDrawAck = YES;
+    self.scanout = *scanout;
+
+    [self rebuildScanoutTexture];
+}
+
+static void cs_gl_draw(SpiceDisplayChannel *channel,
+                       guint32 x, guint32 y, guint32 w, guint32 h,
+                       gpointer data)
+{
+    CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
+
+    DISPLAY_DEBUG(self, "%s",  __FUNCTION__);
+
+    self.isGLEnabled = YES;
+    self.hasGLDrawAck = NO;
+}
+
 #pragma mark - Channel events
 
 static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) {
@@ -276,6 +315,10 @@ static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data
                                G_CALLBACK(cs_update_monitor_area), GLIB_OBJC_RETAIN(self));
         g_signal_connect_after(channel, "gst-video-overlay",
                                G_CALLBACK(cs_set_overlay), GLIB_OBJC_RETAIN(self));
+        g_signal_connect(channel, "notify::gl-scanout",
+                         G_CALLBACK(cs_gl_scanout), GLIB_OBJC_RETAIN(self));
+        g_signal_connect(channel, "gl-draw",
+                         G_CALLBACK(cs_gl_draw), GLIB_OBJC_RETAIN(self));
         if (spice_display_channel_get_primary(channel, 0, &primary)) {
             cs_primary_create(channel, primary.format, primary.width, primary.height,
                               primary.stride, primary.shmid, primary.data, (__bridge void *)self);
@@ -339,6 +382,8 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_mark), GLIB_OBJC_RELEASE(self));
         g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_update_monitor_area), GLIB_OBJC_RELEASE(self));
         g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_set_overlay), GLIB_OBJC_RELEASE(self));
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_gl_scanout), GLIB_OBJC_RELEASE(self));
+        g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_gl_draw), GLIB_OBJC_RELEASE(self));
         return;
     }
     
@@ -365,8 +410,12 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
 
 - (void)setDevice:(id<MTLDevice>)device {
     _device = device;
-    [self rebuildDisplayTexture];
     [self rebuildDisplayVertices];
+    if (self.isGLEnabled) {
+        [self rebuildScanoutTexture];
+    } else {
+        [self rebuildCanvasTexture];
+    }
 }
 
 - (UTMScreenshot *)screenshot {
@@ -377,6 +426,9 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(NULL, _canvasData, _canvasStride * _canvasArea.size.height, nil);
         img = CGImageCreate(_canvasArea.size.width, _canvasArea.size.height, 8, 32, _canvasStride, colorSpaceRef, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, dataProviderRef, NULL, NO, kCGRenderingIntentDefault);
         CGDataProviderRelease(dataProviderRef);
+    } else if (_glTexture) {
+        // TODO: make screenshot from IOSurface
+        img = NULL;
     } else {
         img = NULL;
     }
@@ -396,6 +448,14 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     }
 }
 
+- (id<MTLTexture>)displayTexture {
+    if (self.isGLEnabled) {
+        return self.glTexture;
+    } else {
+        return self.canvasTexture;
+    }
+}
+
 #pragma mark - Methods
 
 - (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
@@ -452,7 +512,13 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
 }
 
 - (void)updateVisibleAreaWithRect:(CGRect)rect {
-    CGRect visible = CGRectIntersection(_canvasArea, rect);
+    CGRect primary;
+    if (self.isGLEnabled) {
+        primary = CGRectMake(0, 0, self.scanout.width, self.scanout.height);
+    } else {
+        primary = _canvasArea;
+    }
+    CGRect visible = CGRectIntersection(primary, rect);
     if (CGRectIsNull(visible)) {
         DISPLAY_DEBUG(self, "The monitor area is not intersecting primary surface");
         self.ready = NO;
@@ -461,11 +527,33 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
         _visibleArea = visible;
     }
     self.displaySize = _visibleArea.size;
-    [self rebuildDisplayTexture];
     [self rebuildDisplayVertices];
+    if (!self.isGLEnabled) {
+        [self rebuildCanvasTexture];
+    }
 }
 
-- (void)rebuildDisplayTexture {
+- (void)rebuildScanoutTexture {
+    if (!self.device) {
+        return; // not ready
+    }
+    IOSurfaceRef iosurface = NULL;
+    if (read(self.scanout.fd, &iosurface, sizeof(iosurface)) != sizeof(iosurface)) {
+        UTMLog(@"Failed to read scanout fd: %d", self.scanout.fd);
+        perror("read");
+        return;
+    }
+    //close(self.scanout.fd);
+
+    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
+    textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
+    textureDescriptor.width = self.scanout.width;
+    textureDescriptor.height = self.scanout.height;
+    self.glTexture = [self.device newTextureWithDescriptor:textureDescriptor iosurface:iosurface plane:0];
+    CFRelease(iosurface);
+}
+
+- (void)rebuildCanvasTexture {
     if (CGRectIsEmpty(_canvasArea) || !self.device) {
         return;
     }
@@ -474,7 +562,7 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     textureDescriptor.pixelFormat = (_canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? MTLPixelFormatBGRA8Unorm : (MTLPixelFormat)43;// FIXME: MTLPixelFormatBGR5A1Unorm is supposed to be available.
     textureDescriptor.width = _visibleArea.size.width;
     textureDescriptor.height = _visibleArea.size.height;
-    self.displayTexture = [self.device newTextureWithDescriptor:textureDescriptor];
+    self.canvasTexture = [self.device newTextureWithDescriptor:textureDescriptor];
     [self drawRegion:_visibleArea];
 }
 
@@ -512,7 +600,7 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     gint canvasStride = _canvasStride;
     if (canvasData) {
         dispatch_async(self.renderQueue, ^{
-            [self.displayTexture replaceRegion:region
+            [self.canvasTexture  replaceRegion:region
                                    mipmapLevel:0
                                      withBytes:(const char *)canvasData + (NSUInteger)(rect.origin.y*canvasStride + rect.origin.x*pixelSize)
                                    bytesPerRow:canvasStride];
@@ -540,6 +628,13 @@ static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer
     spice_main_channel_send_monitor_config(self.main);
 }
 
+- (void)rendererFrameHasRendered {
+    if (self.isGLEnabled && !self.hasGLDrawAck) {
+        spice_display_channel_gl_draw_done(self.display);
+        self.hasGLDrawAck = YES;
+    }
+}
+
 #pragma mark - Cursor drawing
 
 - (void)rebuildCursorWithSize:(CGSize)size center:(CGPoint)hotspot {

+ 2 - 1
Managers/UTMQemuSystem.m

@@ -581,8 +581,9 @@ static size_t sysctl_read(const char *name) {
         [self pushArgv: @"chardev:term0"];
     } else {
         NSURL *spiceSocketURL = self.configuration.spiceSocketURL;
+        BOOL isGLOn = [self.configuration.displayCard hasPrefix:@"virtio"];
         [self pushArgv:@"-spice"];
-        [self pushArgv:[NSString stringWithFormat:@"unix=on,addr=%@,disable-ticketing,image-compression=off,playback-compression=off,streaming-video=off", spiceSocketURL.path]];
+        [self pushArgv:[NSString stringWithFormat:@"unix=on,addr=%@,disable-ticketing=on,image-compression=off,playback-compression=off,streaming-video=off,gl=%@", spiceSocketURL.path, isGLOn ? @"on" : @"off"]];
         [self pushArgv:@"-device"];
         [self pushArgv:self.configuration.displayCard];
     }

+ 2 - 0
Renderer/UTMRenderSource.h

@@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, nullable, readonly) id<MTLBuffer> displayVertices;
 @property (nonatomic, nullable, readonly) id<MTLBuffer> cursorVertices;
 
+- (void)rendererFrameHasRendered;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 1 - 0
Renderer/UTMRenderer.m

@@ -256,6 +256,7 @@ static matrix_float4x4 matrix_scale_translate(CGFloat scale, CGPoint translate)
             // GPU work is complete
             // Signal the semaphore to start the CPU work
             dispatch_semaphore_signal(renderDoneEvent);
+            [source rendererFrameHasRendered];
         }];
     }