| /* |
| * Copyright © 2021 Red Hat, Inc. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #include "config.h" |
| |
| #include <IOSurface/IOSurface.h> |
| #include <Foundation/Foundation.h> |
| #include <OpenGL/CGLIOSurface.h> |
| #include <QuartzCore/QuartzCore.h> |
| |
| #include "gdkmacosbuffer-private.h" |
| |
| struct _GdkMacosBuffer |
| { |
| GObject parent_instance; |
| cairo_region_t *damage; |
| IOSurfaceRef surface; |
| int lock_count; |
| guint bytes_per_element; |
| guint bits_per_pixel; |
| guint width; |
| guint height; |
| guint stride; |
| double device_scale; |
| guint flipped : 1; |
| }; |
| |
| G_DEFINE_TYPE (GdkMacosBuffer, gdk_macos_buffer, G_TYPE_OBJECT) |
| |
| static void |
| gdk_macos_buffer_dispose (GObject *object) |
| { |
| GdkMacosBuffer *self = (GdkMacosBuffer *)object; |
| |
| if (self->lock_count != 0) |
| g_critical ("Attempt to dispose %s while lock is held", |
| G_OBJECT_TYPE_NAME (self)); |
| |
| /* We could potentially force the unload of our surface here with |
| * IOSurfaceSetPurgeable (self->surface, kIOSurfacePurgeableEmpty, NULL) |
| * but that would cause it to empty when the layers may still be attached |
| * to it. Better to just let it get GC'd by the system after they have |
| * moved on to a new buffer. |
| */ |
| g_clear_pointer (&self->surface, CFRelease); |
| g_clear_pointer (&self->damage, cairo_region_destroy); |
| |
| G_OBJECT_CLASS (gdk_macos_buffer_parent_class)->dispose (object); |
| } |
| |
| static void |
| gdk_macos_buffer_class_init (GdkMacosBufferClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| |
| object_class->dispose = gdk_macos_buffer_dispose; |
| } |
| |
| static void |
| gdk_macos_buffer_init (GdkMacosBuffer *self) |
| { |
| } |
| |
| static void |
| add_int (CFMutableDictionaryRef dict, |
| const CFStringRef key, |
| int value) |
| { |
| CFNumberRef number = CFNumberCreate (NULL, kCFNumberIntType, &value); |
| CFDictionaryAddValue (dict, key, number); |
| CFRelease (number); |
| } |
| |
| static IOSurfaceRef |
| create_surface (int width, |
| int height, |
| int bytes_per_element, |
| guint *stride) |
| { |
| CFMutableDictionaryRef props; |
| IOSurfaceRef ret; |
| size_t bytes_per_row; |
| size_t total_bytes; |
| |
| props = CFDictionaryCreateMutable (kCFAllocatorDefault, |
| 16, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| if (props == NULL) |
| return NULL; |
| |
| bytes_per_row = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow, width * bytes_per_element); |
| total_bytes = IOSurfaceAlignProperty (kIOSurfaceAllocSize, height * bytes_per_row); |
| |
| add_int (props, kIOSurfaceAllocSize, total_bytes); |
| add_int (props, kIOSurfaceBytesPerElement, bytes_per_element); |
| add_int (props, kIOSurfaceBytesPerRow, bytes_per_row); |
| add_int (props, kIOSurfaceHeight, height); |
| add_int (props, kIOSurfacePixelFormat, (int)'BGRA'); |
| add_int (props, kIOSurfaceWidth, width); |
| |
| ret = IOSurfaceCreate (props); |
| |
| CFRelease (props); |
| |
| *stride = bytes_per_row; |
| |
| return ret; |
| } |
| |
| GdkMacosBuffer * |
| _gdk_macos_buffer_new (int width, |
| int height, |
| double device_scale, |
| int bytes_per_element, |
| int bits_per_pixel) |
| { |
| GdkMacosBuffer *self; |
| |
| g_return_val_if_fail (width > 0, NULL); |
| g_return_val_if_fail (height > 0, NULL); |
| |
| self = g_object_new (GDK_TYPE_MACOS_BUFFER, NULL); |
| self->bytes_per_element = bytes_per_element; |
| self->bits_per_pixel = bits_per_pixel; |
| self->surface = create_surface (width, height, bytes_per_element, &self->stride); |
| self->width = width; |
| self->height = height; |
| self->device_scale = device_scale; |
| self->lock_count = 0; |
| |
| if (self->surface == NULL) |
| g_clear_object (&self); |
| |
| return self; |
| } |
| |
| IOSurfaceRef |
| _gdk_macos_buffer_get_native (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); |
| |
| return self->surface; |
| } |
| |
| /** |
| * _gdk_macos_buffer_lock: |
| * |
| * This function matches the IOSurfaceLock() name but what it really |
| * does is page the buffer back for the CPU to access from VRAM. |
| * |
| * Generally we don't want to do that, but we do need to in some |
| * cases such as when we are rendering with Cairo. There might |
| * be an opportunity later to avoid that, but since we are using |
| * GL pretty much everywhere already, we don't try. |
| */ |
| void |
| _gdk_macos_buffer_lock (GdkMacosBuffer *self) |
| { |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| g_return_if_fail (self->lock_count == 0); |
| |
| self->lock_count++; |
| |
| IOSurfaceLock (self->surface, 0, NULL); |
| } |
| |
| void |
| _gdk_macos_buffer_unlock (GdkMacosBuffer *self) |
| { |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| g_return_if_fail (self->lock_count == 1); |
| |
| self->lock_count--; |
| |
| IOSurfaceUnlock (self->surface, 0, NULL); |
| } |
| |
| /** |
| * _gdk_macos_buffer_lock_readonly: |
| * |
| * Like _gdk_macos_buffer_lock() but uses the read-only flag to |
| * indicate we are not interested in retrieving the updates from |
| * the GPU before modifying the CPU-side cache. |
| * |
| * Must be used with _gdk_macos_buffer_unlock_readonly(). |
| */ |
| void |
| _gdk_macos_buffer_read_lock (GdkMacosBuffer *self) |
| { |
| kern_return_t ret; |
| |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| g_return_if_fail (self->lock_count == 0); |
| |
| self->lock_count++; |
| |
| ret = IOSurfaceLock (self->surface, kIOSurfaceLockReadOnly, NULL); |
| |
| g_return_if_fail (ret == KERN_SUCCESS); |
| } |
| |
| void |
| _gdk_macos_buffer_read_unlock (GdkMacosBuffer *self) |
| { |
| kern_return_t ret; |
| |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| g_return_if_fail (self->lock_count == 1); |
| |
| self->lock_count--; |
| |
| ret = IOSurfaceUnlock (self->surface, kIOSurfaceLockReadOnly, NULL); |
| |
| g_return_if_fail (ret == KERN_SUCCESS); |
| } |
| |
| guint |
| _gdk_macos_buffer_get_width (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); |
| |
| return self->width; |
| } |
| |
| guint |
| _gdk_macos_buffer_get_height (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); |
| |
| return self->height; |
| } |
| |
| guint |
| _gdk_macos_buffer_get_stride (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); |
| |
| return self->stride; |
| } |
| |
| double |
| _gdk_macos_buffer_get_device_scale (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 1.0); |
| |
| return self->device_scale; |
| } |
| |
| const cairo_region_t * |
| _gdk_macos_buffer_get_damage (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); |
| |
| return self->damage; |
| } |
| |
| void |
| _gdk_macos_buffer_set_damage (GdkMacosBuffer *self, |
| cairo_region_t *damage) |
| { |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| |
| if (damage == self->damage) |
| return; |
| |
| g_clear_pointer (&self->damage, cairo_region_destroy); |
| self->damage = cairo_region_copy (damage); |
| } |
| |
| gpointer |
| _gdk_macos_buffer_get_data (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); |
| |
| return IOSurfaceGetBaseAddress (self->surface); |
| } |
| |
| gboolean |
| _gdk_macos_buffer_get_flipped (GdkMacosBuffer *self) |
| { |
| g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), FALSE); |
| |
| return self->flipped; |
| } |
| |
| void |
| _gdk_macos_buffer_set_flipped (GdkMacosBuffer *self, |
| gboolean flipped) |
| { |
| g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); |
| |
| self->flipped = !!flipped; |
| } |