CSDisplayMetal.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. //
  2. // Copyright © 2019 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. @import CoreImage;
  17. #import "TargetConditionals.h"
  18. #import "UTMScreenshot.h"
  19. #import "UTMShaderTypes.h"
  20. #import "CocoaSpice.h"
  21. #import "UTMLogging.h"
  22. #import <glib.h>
  23. #import <spice-client.h>
  24. #import <spice/protocol.h>
  25. #import <IOSurface/IOSurfaceRef.h>
  26. #ifdef DISPLAY_DEBUG
  27. #undef DISPLAY_DEBUG
  28. #endif
  29. #define DISPLAY_DEBUG(display, fmt, ...) \
  30. SPICE_DEBUG("%d:%d " fmt, \
  31. (int)display.channelID, \
  32. (int)display.monitorID, \
  33. ## __VA_ARGS__)
  34. @interface CSDisplayMetal ()
  35. @property (nonatomic, readwrite, nullable) SpiceSession *session;
  36. @property (nonatomic, readwrite, assign) NSInteger channelID;
  37. @property (nonatomic, readwrite, assign) NSInteger monitorID;
  38. @property (nonatomic, nullable) SpiceDisplayChannel *display;
  39. @property (nonatomic, nullable) SpiceMainChannel *main;
  40. @property (nonatomic, nullable) SpiceCursorChannel *cursor;
  41. @property (nonatomic, readwrite) CGPoint cursorHotspot;
  42. @property (nonatomic, readwrite) BOOL cursorHidden;
  43. @property (nonatomic, readwrite) BOOL hasCursor;
  44. @property (nonatomic, readwrite) BOOL isGLEnabled;
  45. @property (nonatomic, readwrite) BOOL hasGLDrawAck;
  46. @property (nonatomic) SpiceGlScanout scanout;
  47. // Non-GL Canvas
  48. @property (nonatomic) dispatch_semaphore_t canvasLock;
  49. @property (nonatomic) gint canvasFormat;
  50. @property (nonatomic) gint canvasStride;
  51. @property (nonatomic) const void *canvasData;
  52. @property (nonatomic) CGRect canvasArea;
  53. // Other Drawing
  54. @property (nonatomic) CGRect visibleArea;
  55. @property (nonatomic) CGPoint mouseGuest;
  56. // UTMRenderSource properties
  57. @property (nonatomic, nullable, readwrite) id<MTLTexture> canvasTexture;
  58. @property (nonatomic, nullable, readwrite) id<MTLTexture> glTexture;
  59. @property (nonatomic, nullable, readwrite) id<MTLTexture> cursorTexture;
  60. @property (nonatomic, readwrite) NSUInteger displayNumVertices;
  61. @property (nonatomic, readwrite) NSUInteger cursorNumVertices;
  62. @property (nonatomic, nullable, readwrite) id<MTLBuffer> displayVertices;
  63. @property (nonatomic, nullable, readwrite) id<MTLBuffer> cursorVertices;
  64. @end
  65. @implementation CSDisplayMetal
  66. #pragma mark - Display events
  67. static void cs_primary_create(SpiceChannel *channel, gint format,
  68. gint width, gint height, gint stride,
  69. gint shmid, gpointer imgdata, gpointer data) {
  70. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  71. g_assert(format == SPICE_SURFACE_FMT_32_xRGB || format == SPICE_SURFACE_FMT_16_555);
  72. dispatch_semaphore_wait(self.canvasLock, DISPATCH_TIME_FOREVER);
  73. self.canvasArea = CGRectMake(0, 0, width, height);
  74. self.canvasFormat = format;
  75. self.canvasStride = stride;
  76. self.canvasData = imgdata;
  77. dispatch_semaphore_signal(self.canvasLock);
  78. cs_update_monitor_area(channel, NULL, data);
  79. }
  80. static void cs_primary_destroy(SpiceDisplayChannel *channel, gpointer data) {
  81. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  82. self.ready = NO;
  83. dispatch_semaphore_wait(self.canvasLock, DISPATCH_TIME_FOREVER);
  84. self.canvasArea = CGRectZero;
  85. self.canvasFormat = 0;
  86. self.canvasStride = 0;
  87. self.canvasData = NULL;
  88. dispatch_semaphore_signal(self.canvasLock);
  89. }
  90. static void cs_invalidate(SpiceChannel *channel,
  91. gint x, gint y, gint w, gint h, gpointer data) {
  92. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  93. CGRect rect = CGRectIntersection(CGRectMake(x, y, w, h), self.visibleArea);
  94. self.isGLEnabled = NO;
  95. if (!CGRectIsEmpty(rect)) {
  96. [self drawRegion:rect];
  97. }
  98. }
  99. static void cs_mark(SpiceChannel *channel, gint mark, gpointer data) {
  100. //CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  101. //@synchronized (self) {
  102. // self->_mark = mark; // currently this does nothing for us
  103. //}
  104. }
  105. static gboolean cs_set_overlay(SpiceChannel *channel, void* pipeline_ptr, gpointer data) {
  106. //FIXME: implement overlay
  107. //CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  108. return false;
  109. }
  110. static void cs_update_monitor_area(SpiceChannel *channel, GParamSpec *pspec, gpointer data) {
  111. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  112. SpiceDisplayMonitorConfig *cfg, *c = NULL;
  113. GArray *monitors = NULL;
  114. int i;
  115. DISPLAY_DEBUG(self, "update monitor area");
  116. if (self.monitorID < 0)
  117. goto whole;
  118. g_object_get(self.display, "monitors", &monitors, NULL);
  119. for (i = 0; monitors != NULL && i < monitors->len; i++) {
  120. cfg = &g_array_index(monitors, SpiceDisplayMonitorConfig, i);
  121. if (cfg->id == self.monitorID) {
  122. c = cfg;
  123. break;
  124. }
  125. }
  126. if (c == NULL) {
  127. DISPLAY_DEBUG(self, "update monitor: no monitor %d", (int)self.monitorID);
  128. self.ready = NO;
  129. if (spice_channel_test_capability(SPICE_CHANNEL(self.display),
  130. SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
  131. DISPLAY_DEBUG(self, "waiting until MonitorsConfig is received");
  132. g_clear_pointer(&monitors, g_array_unref);
  133. return;
  134. }
  135. goto whole;
  136. }
  137. if (c->surface_id != 0) {
  138. g_warning("FIXME: only support monitor config with primary surface 0, "
  139. "but given config surface %u", c->surface_id);
  140. goto whole;
  141. }
  142. /* If only one head on this monitor, update the whole area */
  143. if (monitors->len == 1 && !self.isGLEnabled) {
  144. [self updateVisibleAreaWithRect:CGRectMake(0, 0, c->width, c->height)];
  145. } else {
  146. [self updateVisibleAreaWithRect:CGRectMake(c->x, c->y, c->width, c->height)];
  147. }
  148. self.ready = YES;
  149. g_clear_pointer(&monitors, g_array_unref);
  150. return;
  151. whole:
  152. g_clear_pointer(&monitors, g_array_unref);
  153. /* by display whole surface */
  154. [self updateVisibleAreaWithRect:self.canvasArea];
  155. self.ready = YES;
  156. }
  157. #pragma mark - Cursor events
  158. static void cs_update_mouse_mode(SpiceChannel *channel, gpointer data)
  159. {
  160. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  161. enum SpiceMouseMode mouse_mode;
  162. g_object_get(channel, "mouse-mode", &mouse_mode, NULL);
  163. DISPLAY_DEBUG(self, "mouse mode %u", mouse_mode);
  164. if (mouse_mode == SPICE_MOUSE_MODE_SERVER) {
  165. self.mouseGuest = CGPointMake(-1, -1);
  166. }
  167. }
  168. static void cs_cursor_invalidate(CSDisplayMetal *self)
  169. {
  170. // We implement two different textures so invalidate is not needed
  171. }
  172. static void cs_cursor_set(SpiceCursorChannel *channel,
  173. G_GNUC_UNUSED GParamSpec *pspec,
  174. gpointer data)
  175. {
  176. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  177. SpiceCursorShape *cursor_shape;
  178. g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
  179. if (G_UNLIKELY(cursor_shape == NULL || cursor_shape->data == NULL)) {
  180. if (cursor_shape != NULL) {
  181. g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
  182. }
  183. return;
  184. }
  185. cs_cursor_invalidate(self);
  186. CGPoint hotspot = CGPointMake(cursor_shape->hot_spot_x, cursor_shape->hot_spot_y);
  187. CGSize newSize = CGSizeMake(cursor_shape->width, cursor_shape->height);
  188. if (!CGSizeEqualToSize(newSize, self.cursorSize) || !CGPointEqualToPoint(hotspot, self.cursorHotspot)) {
  189. [self rebuildCursorWithSize:newSize center:hotspot];
  190. }
  191. [self drawCursor:cursor_shape->data];
  192. self.cursorHidden = NO;
  193. cs_cursor_invalidate(self);
  194. g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
  195. }
  196. static void cs_cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
  197. {
  198. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  199. cs_cursor_invalidate(self); // old pointer buffer
  200. self.mouseGuest = CGPointMake(x, y);
  201. cs_cursor_invalidate(self); // new pointer buffer
  202. /* apparently we have to restore cursor when "cursor_move" */
  203. if (self.hasCursor) {
  204. self.cursorHidden = NO;
  205. }
  206. }
  207. static void cs_cursor_hide(SpiceCursorChannel *channel, gpointer data)
  208. {
  209. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  210. self.cursorHidden = YES;
  211. cs_cursor_invalidate(self);
  212. }
  213. static void cs_cursor_reset(SpiceCursorChannel *channel, gpointer data)
  214. {
  215. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  216. DISPLAY_DEBUG(self, "%s", __FUNCTION__);
  217. [self destroyCursor];
  218. cs_cursor_invalidate(self);
  219. }
  220. #pragma mark - GL
  221. static void cs_gl_scanout(SpiceDisplayChannel *channel, GParamSpec *pspec, gpointer data)
  222. {
  223. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  224. DISPLAY_DEBUG(self, "%s: got scanout", __FUNCTION__);
  225. const SpiceGlScanout *scanout;
  226. scanout = spice_display_channel_get_gl_scanout(self.display);
  227. /* should only be called when the display has a scanout */
  228. g_return_if_fail(scanout != NULL);
  229. self.isGLEnabled = YES;
  230. self.hasGLDrawAck = YES;
  231. self.scanout = *scanout;
  232. [self rebuildScanoutTexture];
  233. }
  234. static void cs_gl_draw(SpiceDisplayChannel *channel,
  235. guint32 x, guint32 y, guint32 w, guint32 h,
  236. gpointer data)
  237. {
  238. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  239. DISPLAY_DEBUG(self, "%s", __FUNCTION__);
  240. self.isGLEnabled = YES;
  241. self.hasGLDrawAck = NO;
  242. }
  243. #pragma mark - Channel events
  244. static void cs_channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) {
  245. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  246. gint channel_id;
  247. g_object_get(channel, "channel-id", &channel_id, NULL);
  248. if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
  249. SpiceDisplayPrimary primary;
  250. if (channel_id != self.channelID) {
  251. return;
  252. }
  253. self.display = SPICE_DISPLAY_CHANNEL(channel);
  254. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  255. g_signal_connect(channel, "display-primary-create",
  256. G_CALLBACK(cs_primary_create), GLIB_OBJC_RETAIN(self));
  257. g_signal_connect(channel, "display-primary-destroy",
  258. G_CALLBACK(cs_primary_destroy), GLIB_OBJC_RETAIN(self));
  259. g_signal_connect(channel, "display-invalidate",
  260. G_CALLBACK(cs_invalidate), GLIB_OBJC_RETAIN(self));
  261. g_signal_connect_after(channel, "display-mark",
  262. G_CALLBACK(cs_mark), GLIB_OBJC_RETAIN(self));
  263. g_signal_connect_after(channel, "notify::monitors",
  264. G_CALLBACK(cs_update_monitor_area), GLIB_OBJC_RETAIN(self));
  265. g_signal_connect_after(channel, "gst-video-overlay",
  266. G_CALLBACK(cs_set_overlay), GLIB_OBJC_RETAIN(self));
  267. g_signal_connect(channel, "notify::gl-scanout",
  268. G_CALLBACK(cs_gl_scanout), GLIB_OBJC_RETAIN(self));
  269. g_signal_connect(channel, "gl-draw",
  270. G_CALLBACK(cs_gl_draw), GLIB_OBJC_RETAIN(self));
  271. if (spice_display_channel_get_primary(channel, 0, &primary)) {
  272. cs_primary_create(channel, primary.format, primary.width, primary.height,
  273. primary.stride, primary.shmid, primary.data, (__bridge void *)self);
  274. cs_mark(channel, primary.marked, (__bridge void *)self);
  275. }
  276. spice_channel_connect(channel);
  277. return;
  278. }
  279. if (SPICE_IS_CURSOR_CHANNEL(channel)) {
  280. gpointer cursor_shape;
  281. if (channel_id != self.channelID) {
  282. return;
  283. }
  284. self.cursor = SPICE_CURSOR_CHANNEL(channel);
  285. g_signal_connect(channel, "notify::cursor",
  286. G_CALLBACK(cs_cursor_set), GLIB_OBJC_RETAIN(self));
  287. g_signal_connect(channel, "cursor-move",
  288. G_CALLBACK(cs_cursor_move), GLIB_OBJC_RETAIN(self));
  289. g_signal_connect(channel, "cursor-hide",
  290. G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RETAIN(self));
  291. g_signal_connect(channel, "cursor-reset",
  292. G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RETAIN(self));
  293. spice_channel_connect(channel);
  294. g_object_get(G_OBJECT(channel), "cursor", &cursor_shape, NULL);
  295. if (cursor_shape != NULL) {
  296. g_boxed_free(SPICE_TYPE_CURSOR_SHAPE, cursor_shape);
  297. cs_cursor_set(self.cursor, NULL, (__bridge void *)self);
  298. }
  299. return;
  300. }
  301. if (SPICE_IS_MAIN_CHANNEL(channel)) {
  302. self.main = SPICE_MAIN_CHANNEL(channel);
  303. g_signal_connect(channel, "main-mouse-update",
  304. G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RETAIN(self));
  305. cs_update_mouse_mode(channel, data);
  306. return;
  307. }
  308. }
  309. static void cs_channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data) {
  310. CSDisplayMetal *self = (__bridge CSDisplayMetal *)data;
  311. gint channel_id;
  312. g_object_get(channel, "channel-id", &channel_id, NULL);
  313. DISPLAY_DEBUG(self, "channel_destroy %d", channel_id);
  314. if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
  315. if (channel_id != self.channelID) {
  316. return;
  317. }
  318. cs_primary_destroy(self.display, (__bridge void *)self);
  319. self.display = NULL;
  320. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  321. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_primary_create), GLIB_OBJC_RELEASE(self));
  322. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_primary_destroy), GLIB_OBJC_RELEASE(self));
  323. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_invalidate), GLIB_OBJC_RELEASE(self));
  324. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_mark), GLIB_OBJC_RELEASE(self));
  325. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_update_monitor_area), GLIB_OBJC_RELEASE(self));
  326. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_set_overlay), GLIB_OBJC_RELEASE(self));
  327. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_gl_scanout), GLIB_OBJC_RELEASE(self));
  328. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_gl_draw), GLIB_OBJC_RELEASE(self));
  329. return;
  330. }
  331. if (SPICE_IS_CURSOR_CHANNEL(channel)) {
  332. if (channel_id != self.channelID) {
  333. return;
  334. }
  335. self.cursor = NULL;
  336. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_set), GLIB_OBJC_RELEASE(self));
  337. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_move), GLIB_OBJC_RELEASE(self));
  338. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_hide), GLIB_OBJC_RELEASE(self));
  339. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_cursor_reset), GLIB_OBJC_RELEASE(self));
  340. return;
  341. }
  342. if (SPICE_IS_MAIN_CHANNEL(channel)) {
  343. self.main = NULL;
  344. g_signal_handlers_disconnect_by_func(channel, G_CALLBACK(cs_update_mouse_mode), GLIB_OBJC_RELEASE(self));
  345. return;
  346. }
  347. return;
  348. }
  349. - (void)setDevice:(id<MTLDevice>)device {
  350. _device = device;
  351. [self rebuildDisplayVertices];
  352. if (self.isGLEnabled) {
  353. [self rebuildScanoutTexture];
  354. } else {
  355. [self rebuildCanvasTexture];
  356. }
  357. }
  358. - (UTMScreenshot *)screenshot {
  359. CGImageRef img;
  360. CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
  361. dispatch_semaphore_wait(self.canvasLock, DISPATCH_TIME_FOREVER);
  362. if (self.canvasData) { // may be destroyed at this point
  363. CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(NULL, self.canvasData, self.canvasStride * self.canvasArea.size.height, nil);
  364. img = CGImageCreate(self.canvasArea.size.width, self.canvasArea.size.height, 8, 32, self.canvasStride, colorSpaceRef, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, dataProviderRef, NULL, NO, kCGRenderingIntentDefault);
  365. CGDataProviderRelease(dataProviderRef);
  366. } else if (self.glTexture) {
  367. CIImage *ciimage = [[CIImage alloc] initWithMTLTexture:self.glTexture options:nil];
  368. CIImage *flipped = [ciimage imageByApplyingOrientation:kCGImagePropertyOrientationDownMirrored];
  369. CIContext *cictx = [CIContext context];
  370. img = [cictx createCGImage:flipped fromRect:flipped.extent];
  371. } else {
  372. img = NULL;
  373. }
  374. dispatch_semaphore_signal(self.canvasLock);
  375. CGColorSpaceRelease(colorSpaceRef);
  376. if (img) {
  377. #if TARGET_OS_IPHONE
  378. UIImage *uiimg = [UIImage imageWithCGImage:img];
  379. #else
  380. NSImage *uiimg = [[NSImage alloc] initWithCGImage:img size:NSZeroSize];
  381. #endif
  382. CGImageRelease(img);
  383. return [[UTMScreenshot alloc] initWithImage:uiimg];
  384. } else {
  385. return nil;
  386. }
  387. }
  388. - (id<MTLTexture>)displayTexture {
  389. if (self.isGLEnabled) {
  390. return self.glTexture;
  391. } else {
  392. return self.canvasTexture;
  393. }
  394. }
  395. #pragma mark - Methods
  396. - (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID monitorID:(NSInteger)monitorID {
  397. if (self = [super init]) {
  398. GList *list;
  399. GList *it;
  400. self.canvasLock = dispatch_semaphore_create(1);
  401. self.viewportScale = 1.0f;
  402. self.viewportOrigin = CGPointMake(0, 0);
  403. self.channelID = channelID;
  404. self.monitorID = monitorID;
  405. self.session = session;
  406. g_object_ref(session);
  407. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  408. g_signal_connect(session, "channel-new",
  409. G_CALLBACK(cs_channel_new), GLIB_OBJC_RETAIN(self));
  410. g_signal_connect(session, "channel-destroy",
  411. G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RETAIN(self));
  412. list = spice_session_get_channels(session);
  413. for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
  414. if (SPICE_IS_MAIN_CHANNEL(it->data)) {
  415. cs_channel_new(session, it->data, (__bridge void *)self);
  416. break;
  417. }
  418. }
  419. for (it = g_list_first(list); it != NULL; it = g_list_next(it)) {
  420. if (!SPICE_IS_MAIN_CHANNEL(it->data))
  421. cs_channel_new(session, it->data, (__bridge void *)self);
  422. }
  423. g_list_free(list);
  424. }
  425. return self;
  426. }
  427. - (instancetype)initWithSession:(nonnull SpiceSession *)session channelID:(NSInteger)channelID {
  428. return [self initWithSession:session channelID:channelID monitorID:0];
  429. }
  430. - (void)dealloc {
  431. if (self.display) {
  432. cs_channel_destroy(self.session, SPICE_CHANNEL(self.display), (__bridge void *)self);
  433. }
  434. if (_cursor) {
  435. cs_channel_destroy(self.session, SPICE_CHANNEL(_cursor), (__bridge void *)self);
  436. }
  437. UTMLog(@"%s:%d", __FUNCTION__, __LINE__);
  438. g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_new), GLIB_OBJC_RELEASE(self));
  439. g_signal_handlers_disconnect_by_func(self.session, G_CALLBACK(cs_channel_destroy), GLIB_OBJC_RELEASE(self));
  440. g_object_unref(self.session);
  441. self.session = NULL;
  442. }
  443. - (void)updateVisibleAreaWithRect:(CGRect)rect {
  444. CGRect primary;
  445. if (self.isGLEnabled) {
  446. primary = CGRectMake(0, 0, self.scanout.width, self.scanout.height);
  447. } else {
  448. primary = self.canvasArea;
  449. }
  450. CGRect visible = CGRectIntersection(primary, rect);
  451. if (CGRectIsNull(visible)) {
  452. DISPLAY_DEBUG(self, "The monitor area is not intersecting primary surface");
  453. self.ready = NO;
  454. self.visibleArea = CGRectZero;
  455. } else {
  456. self.visibleArea = visible;
  457. }
  458. self.displaySize = self.visibleArea.size;
  459. [self rebuildDisplayVertices];
  460. if (!self.isGLEnabled) {
  461. [self rebuildCanvasTexture];
  462. }
  463. }
  464. - (void)rebuildScanoutTexture {
  465. if (!self.device) {
  466. return; // not ready
  467. }
  468. IOSurfaceID iosurfaceid = 0;
  469. IOSurfaceRef iosurface = NULL;
  470. if (read(self.scanout.fd, &iosurfaceid, sizeof(iosurfaceid)) != sizeof(iosurfaceid)) {
  471. UTMLog(@"Failed to read scanout fd: %d", self.scanout.fd);
  472. perror("read");
  473. return;
  474. }
  475. if ((iosurface = IOSurfaceLookup(iosurfaceid)) == NULL) {
  476. UTMLog(@"Failed to lookup surface: %d", iosurfaceid);
  477. return;
  478. }
  479. MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
  480. textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
  481. textureDescriptor.width = self.scanout.width;
  482. textureDescriptor.height = self.scanout.height;
  483. textureDescriptor.usage = MTLTextureUsageShaderRead;
  484. self.glTexture = [self.device newTextureWithDescriptor:textureDescriptor iosurface:iosurface plane:0];
  485. CFRelease(iosurface);
  486. }
  487. - (void)rebuildCanvasTexture {
  488. if (CGRectIsEmpty(self.canvasArea) || !self.device) {
  489. return;
  490. }
  491. MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
  492. // don't worry that that components are reversed, we fix it in shaders
  493. textureDescriptor.pixelFormat = (self.canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? MTLPixelFormatBGRA8Unorm : (MTLPixelFormat)43;// FIXME: MTLPixelFormatBGR5A1Unorm is supposed to be available.
  494. textureDescriptor.width = self.visibleArea.size.width;
  495. textureDescriptor.height = self.visibleArea.size.height;
  496. self.canvasTexture = [self.device newTextureWithDescriptor:textureDescriptor];
  497. [self drawRegion:self.visibleArea];
  498. }
  499. - (void)rebuildDisplayVertices {
  500. // We flip the y-coordinates because pixman renders flipped
  501. UTMVertex quadVertices[] =
  502. {
  503. // Pixel positions, Texture coordinates
  504. { { self.visibleArea.size.width/2, self.visibleArea.size.height/2 }, { 1.f, 0.f } },
  505. { { -self.visibleArea.size.width/2, self.visibleArea.size.height/2 }, { 0.f, 0.f } },
  506. { { -self.visibleArea.size.width/2, -self.visibleArea.size.height/2 }, { 0.f, 1.f } },
  507. { { self.visibleArea.size.width/2, self.visibleArea.size.height/2 }, { 1.f, 0.f } },
  508. { { -self.visibleArea.size.width/2, -self.visibleArea.size.height/2 }, { 0.f, 1.f } },
  509. { { self.visibleArea.size.width/2, -self.visibleArea.size.height/2 }, { 1.f, 1.f } },
  510. };
  511. // Create our vertex buffer, and initialize it with our quadVertices array
  512. self.displayVertices = [self.device newBufferWithBytes:quadVertices
  513. length:sizeof(quadVertices)
  514. options:MTLResourceStorageModeShared];
  515. // Calculate the number of vertices by dividing the byte length by the size of each vertex
  516. self.displayNumVertices = sizeof(quadVertices) / sizeof(UTMVertex);
  517. }
  518. - (void)drawRegion:(CGRect)rect {
  519. NSInteger pixelSize = (self.canvasFormat == SPICE_SURFACE_FMT_32_xRGB) ? 4 : 2;
  520. // create draw region
  521. MTLRegion region = {
  522. { rect.origin.x-self.visibleArea.origin.x, rect.origin.y-self.visibleArea.origin.y, 0 }, // MTLOrigin
  523. { rect.size.width, rect.size.height, 1} // MTLSize
  524. };
  525. dispatch_semaphore_wait(self.canvasLock, DISPATCH_TIME_FOREVER);
  526. if (self.canvasData) {
  527. [self.canvasTexture replaceRegion:region
  528. mipmapLevel:0
  529. withBytes:(const char *)self.canvasData + (NSUInteger)(rect.origin.y*self.canvasStride + rect.origin.x*pixelSize)
  530. bytesPerRow:self.canvasStride];
  531. }
  532. dispatch_semaphore_signal(self.canvasLock);
  533. }
  534. - (BOOL)visible {
  535. return self.ready;
  536. }
  537. - (void)requestResolution:(CGRect)bounds {
  538. if (!self.main) {
  539. UTMLog(@"ignoring change resolution because main channel not found");
  540. return;
  541. }
  542. spice_main_channel_update_display_enabled(self.main, (int)self.monitorID, TRUE, FALSE);
  543. spice_main_channel_update_display(self.main,
  544. (int)self.monitorID,
  545. bounds.origin.x,
  546. bounds.origin.y,
  547. bounds.size.width,
  548. bounds.size.height,
  549. TRUE);
  550. spice_main_channel_send_monitor_config(self.main);
  551. }
  552. - (void)rendererFrameHasRendered {
  553. if (self.isGLEnabled && !self.hasGLDrawAck) {
  554. spice_display_channel_gl_draw_done(self.display);
  555. self.hasGLDrawAck = YES;
  556. }
  557. }
  558. #pragma mark - Cursor drawing
  559. - (void)rebuildCursorWithSize:(CGSize)size center:(CGPoint)hotspot {
  560. // hotspot is the offset in buffer for the center of the pointer
  561. if (!self.device) {
  562. UTMLog(@"MTL device not ready for cursor draw");
  563. return;
  564. }
  565. MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
  566. // don't worry that that components are reversed, we fix it in shaders
  567. textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
  568. textureDescriptor.width = size.width;
  569. textureDescriptor.height = size.height;
  570. self.cursorTexture = [self.device newTextureWithDescriptor:textureDescriptor];
  571. // We flip the y-coordinates because pixman renders flipped
  572. UTMVertex quadVertices[] =
  573. {
  574. // Pixel positions, Texture coordinates
  575. { { -hotspot.x + size.width, hotspot.y }, { 1.f, 0.f } },
  576. { { -hotspot.x , hotspot.y }, { 0.f, 0.f } },
  577. { { -hotspot.x , hotspot.y - size.height }, { 0.f, 1.f } },
  578. { { -hotspot.x + size.width, hotspot.y }, { 1.f, 0.f } },
  579. { { -hotspot.x , hotspot.y - size.height }, { 0.f, 1.f } },
  580. { { -hotspot.x + size.width, hotspot.y - size.height }, { 1.f, 1.f } },
  581. };
  582. // Create our vertex buffer, and initialize it with our quadVertices array
  583. self.cursorVertices = [self.device newBufferWithBytes:quadVertices
  584. length:sizeof(quadVertices)
  585. options:MTLResourceStorageModeShared];
  586. // Calculate the number of vertices by dividing the byte length by the size of each vertex
  587. self.cursorNumVertices = sizeof(quadVertices) / sizeof(UTMVertex);
  588. self.cursorSize = size;
  589. self.cursorHotspot = hotspot;
  590. self.hasCursor = YES;
  591. }
  592. - (void)destroyCursor {
  593. self.cursorNumVertices = 0;
  594. self.cursorVertices = nil;
  595. self.cursorTexture = nil;
  596. self.cursorSize = CGSizeZero;
  597. self.cursorHotspot = CGPointZero;
  598. self.hasCursor = NO;
  599. }
  600. - (void)drawCursor:(const void *)buffer {
  601. const NSInteger pixelSize = 4;
  602. MTLRegion region = {
  603. { 0, 0 }, // MTLOrigin
  604. { self.cursorSize.width, self.cursorSize.height, 1} // MTLSize
  605. };
  606. [self.cursorTexture replaceRegion:region
  607. mipmapLevel:0
  608. withBytes:buffer
  609. bytesPerRow:self.cursorSize.width*pixelSize];
  610. }
  611. - (BOOL)cursorVisible {
  612. return !self.inhibitCursor && self.hasCursor && !self.cursorHidden;
  613. }
  614. - (CGPoint)cursorOrigin {
  615. CGPoint point = self.mouseGuest;
  616. point.x -= self.displaySize.width/2;
  617. point.y -= self.displaySize.height/2;
  618. point.x *= self.viewportScale;
  619. point.y *= self.viewportScale;
  620. return point;
  621. }
  622. - (BOOL)cursorInverted {
  623. return !self.isGLEnabled;
  624. }
  625. - (void)forceCursorPosition:(CGPoint)pos {
  626. self.mouseGuest = pos;
  627. }
  628. @end