Initial support for Metal layers and drawables

In its current state, I consider this code to be experimental and
unstable. The biggest problem is that there's currently a HUGE memory
leak somewhere (over 2GiB in under 5 seconds at 60fps). I'm committing
what I have now since I don't feel like debugging that right now and it
*does* technically work as-is.

The current approach is not great for performance: first, we render to a Metal/Vulkan
texture in the desired format then blit that to an RGBA Metal/Vulkan
texture shared with
OpenGL. Then, we copy *that* to another OpenGL texture in CAMetalLayer
to use as the content for the layer (we can't keep the drawable's OpenGL
texture, since that has to be recycled). Finally, this is rendered to a
subwindow by CARenderer/CALayerContext. We cannot get rid of the last copy
since we must render to the subwindow somehow and it's the easiest
way to play nice with sublayers. I also don't think we can get rid of the
first copy since OpenGL doesn't support some of Metal/Vulkan's texture
formats; plus, we can't share optimally tiled images between Vulkan and
OpenGL with some vendors' drivers (e.g. AMD).
We *could* get rid of the second copy if we were able to accurately
determine when the content is finally presented; then we could simply
keep the drawable in-use, render it to the subwindow when asked to, and
release it once we know the render is complete.
This commit is contained in:
Ariel Abreu 2022-12-26 00:03:28 -05:00
parent 3159ee117c
commit 55bafce1f1
No known key found for this signature in database
GPG Key ID: C06B805216EDEEED
17 changed files with 1700 additions and 74 deletions

View File

@ -67,12 +67,12 @@ const NSViewFullScreenModeOptionKey NSFullScreenModeApplicationPresentationOptio
@implementation NSView
static BOOL NSViewLayersEnabled = NO;
static BOOL NSViewLayersEnabled = YES;
static BOOL NSShowAllViews = NO;
+ (void) initialize {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSViewLayersEnabled = [defaults boolForKey: @"NSViewLayersEnabled"];
//NSViewLayersEnabled = [defaults boolForKey: @"NSViewLayersEnabled"];
NSShowAllViews = [defaults boolForKey: @"NSShowAllViews"];
}
@ -370,6 +370,11 @@ typedef struct __VFlags {
_transformToWindow = CGAffineTransformIdentity;
_transformToLayer = CGAffineTransformIdentity;
// according to some Apple sample code ("Creating a Custom Metal View"),
// it's valid for a subclass of NSView to set `wantsLayer` before invoking the superclass `initWithFrame:`.
// thus, we might have a layer context already but with an incorrect frame.
[_layerContext setFrame: _frame];
return self;
}
@ -1090,6 +1095,10 @@ static inline void buildTransformsIfNeeded(NSView *self) {
_window = window;
if (_layerContext) {
[_layerContext setSubwindow: [_window _createSubWindowWithFrame: [self frame]]];
}
[_subviews makeObjectsPerformSelector: _cmd withObject: window];
_validTrackingAreas = NO;
[_window invalidateCursorRectsForView: self]; // this also invalidates
@ -1757,6 +1766,9 @@ static inline void buildTransformsIfNeeded(NSView *self) {
if ([_superview layer] == nil) {
_layerContext = [[CALayerContext alloc] initWithFrame: [self frame]];
[_layerContext setLayer: _layer];
if (_window) {
[_layerContext setSubwindow: [_window _createSubWindowWithFrame: [self frame]]];
}
}
}
@ -2418,6 +2430,8 @@ static NSView *viewBeingPrinted = nil;
}
if (shouldFlush) {
[_layerContext flush];
// We do the flushWindow here. If any of the display* methods are being
// used, you want it to update on screen immediately. If the view
// hierarchy is being displayed as needed at the end of an event,
@ -2809,4 +2823,10 @@ static NSView *viewBeingPrinted = nil;
self, NSStringFromRect(_frame)];
}
// XXX: this probably shouldn't be here, but it's a convenient way for CALayers to request a re-render
- (void)displayLayer: (CALayer*)layer
{
[self display];
}
@end

View File

@ -70,11 +70,11 @@ NSString *const kCAContentsFormatGray8Uint = @"Gray8";
withObject: _context];
}
- (id) delegate {
- (id<CALayerDelegate>) delegate {
return _delegate;
}
- (void) setDelegate: value {
- (void) setDelegate: (id<CALayerDelegate>)value {
_delegate = value;
}

View File

@ -2,15 +2,15 @@
#import <QuartzCore/CALayer.h>
#import <QuartzCore/CALayerContext.h>
#import <QuartzCore/CARenderer.h>
#import "CALayerInternal.h"
#import <OpenGL/CGLInternal.h>
@interface CALayer (private)
- (void) _setContext: (CALayerContext *) context;
- (NSNumber *) _textureId;
- (void) _setTextureId: (NSNumber *) value;
@end
@class CAMetalLayerInternal;
@implementation CALayerContext
@synthesize glContext = _glContext;
- initWithFrame: (CGRect) rect {
CGLError error;
@ -28,19 +28,6 @@
_frame = rect;
GLint width = rect.size.width;
GLint height = rect.size.height;
GLint backingOrigin[2] = {rect.origin.x, rect.origin.y};
GLint backingSize[2] = {width, height};
// FIXME: convert to CGSubWindow
// CGLSetParameter(_glContext,kCGLCPSurfaceBackingOrigin,backingOrigin);
// CGLSetParameter(_glContext,kCGLCPSurfaceBackingSize,backingSize);
GLint opacity = 0;
// CGLSetParameter(_glContext,kCGLCPSurfaceOpacity,&opacity);
_renderer = [[CARenderer rendererWithCGLContext: _glContext
options: nil] retain];
@ -50,20 +37,17 @@
- (void) dealloc {
[_timer invalidate];
[_timer release];
[_renderer release];
CGLReleaseContext(_glContext);
CGLDestroyWindow(_cglWindow);
[_subwindow release];
[_layer release];
[super dealloc];
}
- (void) setFrame: (CGRect) rect {
_frame = rect;
GLint width = rect.size.width;
GLint height = rect.size.height;
GLint backingOrigin[2] = {rect.origin.x, rect.origin.y};
GLint backingSize[2] = {width, height};
// CGLSetParameter(_glContext,kCGLCPSurfaceBackingOrigin,backingOrigin);
// CGLSetParameter(_glContext,kCGLCPSurfaceBackingSize,backingSize);
[_subwindow setFrame: _frame];
}
- (void) setLayer: (CALayer *) layer {
@ -75,6 +59,24 @@
[_renderer setLayer: layer];
}
- (void) setSubwindow: (CGSubWindow*) subwindow
{
CGSubWindow* oldSubwindow = _subwindow;
if (_cglWindow) {
CGLDestroyWindow(_cglWindow);
}
_subwindow = [subwindow retain];
_cglWindow = CGLGetWindow([_subwindow nativeWindow]);
[_subwindow show];
[oldSubwindow release];
[_subwindow setFrame: _frame];
}
- (void) invalidate {
}
@ -92,7 +94,7 @@
}
- (void) renderLayer: (CALayer *) layer {
CGLSetCurrentContext(_glContext);
CGLContextMakeCurrentAndAttachToWindow(_glContext, _cglWindow);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
@ -118,6 +120,12 @@
[self assignTextureIdsToLayerTree: layer];
// this is where the Metal layer renders to an internal texture for us to use
if ([[layer class] isSubclassOfClass: [CAMetalLayerInternal class]]) {
CAMetalLayerInternal* mtl = (CAMetalLayerInternal*)layer;
[mtl prepareRender];
}
[_renderer render];
}
@ -146,4 +154,8 @@
[_deleteTextureIds addObject: textureId];
}
- (void) flush {
CGLFlushDrawable(_glContext);
}
@end

View File

@ -0,0 +1,26 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#import <QuartzCore/CALayer.h>
@interface CALayer (Internal)
- (void) _setContext: (CALayerContext *) context;
- (NSNumber *) _textureId;
- (void) _setTextureId: (NSNumber *) value;
@end

View File

@ -0,0 +1,745 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#define GL_GLEXT_PROTOTYPES 1
#import "CAMetalDrawableInternal.h"
#import "CAMetalLayerInternal.h"
#import <QuartzCore/CAMetalLayer.h>
#import <Metal/MTLDeviceInternal.h>
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <indium/indium.private.hpp>
static void reportGLErrors(void) {
#if 0
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
printf("*** OPENGL ERROR: %d ***\n", err);
}
#endif
};
//
// dynamically imported
//
VkResult dynamic_vkGetSemaphoreFdKHR(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd) {
static PFN_vkGetSemaphoreFdKHR ptr = [&]() {
return reinterpret_cast<PFN_vkGetSemaphoreFdKHR>(vkGetInstanceProcAddr(Indium::globalInstance, "vkGetSemaphoreFdKHR"));
}();
return ptr(device, pGetFdInfo, pFd);
};
VkResult dynamic_vkGetMemoryFdKHR(VkDevice device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd) {
static PFN_vkGetMemoryFdKHR ptr = [&]() {
return reinterpret_cast<PFN_vkGetMemoryFdKHR>(vkGetInstanceProcAddr(Indium::globalInstance, "vkGetMemoryFdKHR"));
}();
return ptr(device, pGetFdInfo, pFd);
};
//
// ideally, we would use a Vulkan surface + swapchain here to render directly to the screen.
// however, CALayers can also be rendered offscreen (e.g. with a CARenderer) and our current implementation of rendering
// in most places is to render to an OpenGL buffer (per window) and display that.
// plus, we don't have an easy way to get a surface here; we would need access to an X11 window/subwindow,
// which we don't have direct access to here (our delegate might). therefore, we follow suit
// with the existing CALayer implementation and just render to an image/texture.
//
// additionally, to avoid refactoring/reworking the existing CARenderer and CALayerContext code,
// we render to a Vulkan image with exportable memory that we can import into an OpenGL texture,
// along with a semaphore to synchronize the Vulkan rendering with OpenGL.
//
//
// texture
//
static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr<Indium::PrivateDevice> device) {
size_t targetIndex = SIZE_MAX;
for (size_t i = 0; i < device->memoryProperties().memoryTypeCount; ++i) {
const auto& type = device->memoryProperties().memoryTypes[i];
if ((reqs.memoryTypeBits & (1 << i)) == 0) {
continue;
}
if ((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
continue;
}
if ((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
continue;
}
// okay, this is good enough
targetIndex = i;
break;
}
return targetIndex;
};
CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat pixelFormat, std::shared_ptr<Indium::PrivateDevice> privateDevice):
Indium::PrivateTexture(privateDevice),
_size(size),
_pixelFormat(pixelFormat)
{
//
// create the images
//
// create the public Vulkan image
VkImageCreateInfo imgInfo {};
imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imgInfo.imageType = VK_IMAGE_TYPE_2D;
imgInfo.format = Indium::pixelFormatToVkFormat(_pixelFormat);
imgInfo.extent.width = _size.width;
imgInfo.extent.height = _size.height;
imgInfo.extent.depth = 1;
imgInfo.mipLevels = 1;
imgInfo.arrayLayers = 1;
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imgInfo.queueFamilyIndexCount = 0;
imgInfo.pQueueFamilyIndices = nullptr;
imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (vkCreateImage(_device->device(), &imgInfo, nullptr, &_image) != VK_SUCCESS) {
// TODO
abort();
}
// now create the internal image (with exportable memory)
imgInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
// some drivers (e.g. AMD's drivers) don't play nice when sharing
// Vulkan images and OpenGL textures with optimal tiling
// (https://gitlab.freedesktop.org/mesa/mesa/-/issues/7657)
imgInfo.tiling = VK_IMAGE_TILING_LINEAR;
VkExternalMemoryImageCreateInfo extMemInfo {};
extMemInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
extMemInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
imgInfo.pNext = &extMemInfo;
if (vkCreateImage(_device->device(), &imgInfo, nullptr, &_internalImage) != VK_SUCCESS) {
// TODO
abort();
}
//
// allocate some memory for the images
//
VkMemoryRequirements reqs {};
// first, the public image
vkGetImageMemoryRequirements(_device->device(), _image, &reqs);
size_t targetIndex = findSharedMemory(reqs, _device);
if (targetIndex == SIZE_MAX) {
throw std::runtime_error("No suitable memory region found for image");
}
VkMemoryAllocateInfo allocInfo {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = reqs.size;
allocInfo.memoryTypeIndex = targetIndex;
if (vkAllocateMemory(_device->device(), &allocInfo, nullptr, &_memory) != VK_SUCCESS) {
// TODO
abort();
}
vkBindImageMemory(_device->device(), _image, _memory, 0);
// now, the internal image
vkGetImageMemoryRequirements(_device->device(), _internalImage, &reqs);
targetIndex = findSharedMemory(reqs, _device);
if (targetIndex == SIZE_MAX) {
throw std::runtime_error("No suitable memory region found for internal image");
}
VkExportMemoryAllocateInfo exportAllocInfo {};
VkMemoryDedicatedAllocateInfo dedicatedInfo {};
allocInfo.allocationSize = reqs.size;
allocInfo.memoryTypeIndex = targetIndex;
exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
allocInfo.pNext = &exportAllocInfo;
dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
dedicatedInfo.image = _internalImage;
exportAllocInfo.pNext = &dedicatedInfo;
if (vkAllocateMemory(_device->device(), &allocInfo, nullptr, &_internalMemory) != VK_SUCCESS) {
// TODO
abort();
}
vkBindImageMemory(_device->device(), _internalImage, _internalMemory, 0);
//
// transition the images into the general layout
//
VkCommandBufferAllocateInfo cmdBufAllocInfo {};
cmdBufAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmdBufAllocInfo.commandPool = _device->oneshotCommandPool();
cmdBufAllocInfo.commandBufferCount = 1;
VkCommandBuffer cmdBuf;
if (vkAllocateCommandBuffers(_device->device(), &cmdBufAllocInfo, &cmdBuf) != VK_SUCCESS) {
// TODO
abort();
}
VkCommandBufferBeginInfo cmdBufBeginInfo {};
cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
cmdBufBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(cmdBuf, &cmdBufBeginInfo);
VkImageMemoryBarrier barriers[2];
barriers[0] = {};
barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barriers[0].srcAccessMask = VK_ACCESS_NONE;
barriers[0].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].image = _image;
barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barriers[0].subresourceRange.baseMipLevel = 0;
barriers[0].subresourceRange.levelCount = 1;
barriers[0].subresourceRange.baseArrayLayer = 0;
barriers[0].subresourceRange.layerCount = 1;
barriers[1] = barriers[0];
barriers[1].image = _internalImage;
vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_NONE, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, sizeof(barriers) / sizeof(*barriers), barriers);
vkEndCommandBuffer(cmdBuf);
VkFenceCreateInfo fenceCreateInfo {};
fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VkFence theFence = VK_NULL_HANDLE;
if (vkCreateFence(_device->device(), &fenceCreateInfo, nullptr, &theFence) != VK_SUCCESS) {
// TODO
abort();
}
VkSubmitInfo submitInfo {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmdBuf;
vkQueueSubmit(_device->graphicsQueue(), 1, &submitInfo, theFence);
if (vkWaitForFences(_device->device(), 1, &theFence, VK_TRUE, /* 1s */ 1ull * 1000 * 1000 * 1000) != VK_SUCCESS) {
// TODO
abort();
}
vkDestroyFence(_device->device(), theFence, nullptr);
vkFreeCommandBuffers(_device->device(), _device->oneshotCommandPool(), 1, &cmdBuf);
//
// create an image view for the public image
//
VkImageViewCreateInfo imgViewInfo {};
imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imgViewInfo.image = _image;
imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imgViewInfo.format = Indium::pixelFormatToVkFormat(_pixelFormat);
imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
imgViewInfo.subresourceRange = barriers[0].subresourceRange;
if (vkCreateImageView(_device->device(), &imgViewInfo, nullptr, &_imageView) != VK_SUCCESS) {
// TODO
abort();
}
//
// import the internal image into an OpenGL texture
//
// get an FD for the memory
VkMemoryGetFdInfoKHR getFDInfo {};
getFDInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
getFDInfo.memory = _internalMemory;
getFDInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
int fd = -1;
if (dynamic_vkGetMemoryFdKHR(_device->device(), &getFDInfo, &fd) != VK_SUCCESS) {
// TODO
abort();
}
reportGLErrors();
// import the memory into OpenGL
glCreateMemoryObjectsEXT(1, &_memoryObject);
reportGLErrors();
// this transfers ownership of the FD to OpenGL
glImportMemoryFdEXT(_memoryObject, reqs.size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
fd = -1;
reportGLErrors();
GLint prevTex = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex);
glCreateTextures(GL_TEXTURE_2D, 1, &_textureID);
reportGLErrors();
glTextureParameteri(_textureID, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT /*GL_OPTIMAL_TILING_EXT*/);
glTextureParameteri(_textureID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(_textureID, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_T, GL_REPEAT);
reportGLErrors();
glTextureStorageMem2DEXT(_textureID, 1, GL_RGBA8, _size.width, _size.height, _memoryObject, 0);
reportGLErrors();
glBindTexture(GL_TEXTURE_2D, prevTex);
};
CAMetalDrawableTexture::~CAMetalDrawableTexture() {
reportGLErrors();
glDeleteTextures(1, &_textureID);
reportGLErrors();
glDeleteMemoryObjectsEXT(1, &_memoryObject);
reportGLErrors();
vkDestroyImageView(_device->device(), _imageView, nullptr);
vkDestroyImage(_device->device(), _image, nullptr);
vkFreeMemory(_device->device(), _memory, nullptr);
vkDestroyImage(_device->device(), _internalImage, nullptr);
vkFreeMemory(_device->device(), _internalMemory, nullptr);
};
GLuint CAMetalDrawableTexture::glTexture() const {
return _textureID;
};
VkImageView CAMetalDrawableTexture::imageView() {
return _imageView;
};
VkImage CAMetalDrawableTexture::image() {
return _image;
};
VkImageLayout CAMetalDrawableTexture::imageLayout() {
return VK_IMAGE_LAYOUT_GENERAL;
};
Indium::TextureType CAMetalDrawableTexture::textureType() const {
return Indium::TextureType::e2D;
};
Indium::PixelFormat CAMetalDrawableTexture::pixelFormat() const {
return _pixelFormat;
};
size_t CAMetalDrawableTexture::width() const {
return _size.width;
};
size_t CAMetalDrawableTexture::height() const {
return _size.height;
};
size_t CAMetalDrawableTexture::depth() const {
return 1;
};
size_t CAMetalDrawableTexture::mipmapLevelCount() const {
return 1;
};
size_t CAMetalDrawableTexture::arrayLength() const {
return 1;
};
size_t CAMetalDrawableTexture::sampleCount() const {
return 1;
};
bool CAMetalDrawableTexture::framebufferOnly() const {
// TODO: determine this according to the layer
return false;
};
bool CAMetalDrawableTexture::allowGPUOptimizedContents() const {
return true;
};
bool CAMetalDrawableTexture::shareable() const {
return false;
};
Indium::TextureSwizzleChannels CAMetalDrawableTexture::swizzle() const {
return {};
};
void CAMetalDrawableTexture::replaceRegion(Indium::Region region, size_t mipmapLevel, const void* bytes, size_t bytesPerRow) {
// TODO
abort();
};
void CAMetalDrawableTexture::replaceRegion(Indium::Region region, size_t mipmapLevel, size_t slice, const void* bytes, size_t bytesPerRow, size_t bytesPerImage) {
// TODO
abort();
};
void CAMetalDrawableTexture::precommit(std::shared_ptr<Indium::PrivateCommandBuffer> cmdbuf) {
// TODO: check if we need a barrier for the internal image as well; we probably do.
// we might even need a separate semaphore for it.
VkImageMemoryBarrier barriers[2];
barriers[0] = {};
barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barriers[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].image = _image;
barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barriers[0].subresourceRange.baseMipLevel = 0;
barriers[0].subresourceRange.levelCount = 1;
barriers[0].subresourceRange.baseArrayLayer = 0;
barriers[0].subresourceRange.layerCount = 1;
barriers[1] = barriers[0];
barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[1].image = _internalImage;
vkCmdPipelineBarrier(cmdbuf->commandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, sizeof(barriers) / sizeof(*barriers), barriers);
VkImageBlit region {};
region.srcOffsets[0] = { 0, 0, 0 };
region.srcOffsets[1] = { static_cast<int32_t>(width()), static_cast<int32_t>(height()), 1 };
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.dstOffsets[0] = region.srcOffsets[0];
region.dstOffsets[1] = region.srcOffsets[1];
region.dstSubresource = region.srcSubresource;
vkCmdBlitImage(cmdbuf->commandBuffer(), _image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, _internalImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region, VK_FILTER_LINEAR);
barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barriers[0].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barriers[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barriers[1].newLayout = VK_IMAGE_LAYOUT_GENERAL;
vkCmdPipelineBarrier(cmdbuf->commandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, sizeof(barriers) / sizeof(*barriers), barriers);
};
bool CAMetalDrawableTexture::needsExportablePresentationSemaphore() const {
return true;
};
//
// drawable
//
CAMetalDrawableActual::CAMetalDrawableActual(CAMetalLayerInternal* layer, CGSize size, NSUInteger drawableID, Indium::PixelFormat pixelFormat, std::shared_ptr<Indium::Device> device, CGLContextObj glContext):
_drawableID(drawableID),
_glContext(CGLRetainContext(glContext))
{
_texture = std::make_shared<CAMetalDrawableTexture>(size, pixelFormat, std::dynamic_pointer_cast<Indium::PrivateDevice>(device));
objc_storeWeak(&_layer, layer);
};
CAMetalDrawableActual::~CAMetalDrawableActual() {
reset();
objc_storeWeak(&_layer, nil);
CGLReleaseContext(_glContext);
};
void CAMetalDrawableActual::present() {
if (_queued) {
if (_presentCallback) {
_presentCallback();
_presentCallback = nullptr;
}
return;
}
_queued = true;
@autoreleasepool {
CAMetalLayerInternal* layer = objc_loadWeak(&_layer);
if (!layer) {
// if we've been disowned, drop all presentation requests
didDrop();
return;
}
[layer queuePresent: _drawableID];
_semaphore = _texture->synchronizePresentation();
if (_semaphore) {
int fd = -1;
VkSemaphoreGetFdInfoKHR info {};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
info.semaphore = _semaphore->semaphore;
info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
if (dynamic_vkGetSemaphoreFdKHR(_semaphore->device->device(), &info, &fd) != VK_SUCCESS) {
// TODO
abort();
}
CGLContextObj prev = CGLGetCurrentContext();
CGLSetCurrentContext(_glContext);
reportGLErrors();
glGenSemaphoresEXT(1, &_glSemaphore);
reportGLErrors();
glImportSemaphoreFdEXT(_glSemaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); // this consumes the FD
fd = -1;
reportGLErrors();
CGLSetCurrentContext(prev);
}
}
}
std::shared_ptr<CAMetalDrawableTexture> CAMetalDrawableActual::texture() {
return _texture;
};
NSUInteger CAMetalDrawableActual::drawableID() const {
return _drawableID;
};
CFTimeInterval CAMetalDrawableActual::presentedTime() const {
return _presentedTime;
};
void CAMetalDrawableActual::setPresentCallback(std::function<void()> presentCallback) {
_presentCallback = presentCallback;
};
void CAMetalDrawableActual::disown() {
objc_storeWeak(&_layer, nil);
};
void CAMetalDrawableActual::didPresent() {
_presentedTime = CACurrentMediaTime();
if (_presentCallback) {
_presentCallback();
_presentCallback = nullptr;
}
};
void CAMetalDrawableActual::didDrop() {
_presentedTime = 0;
if (_presentCallback) {
_presentCallback();
_presentCallback = nullptr;
}
};
void CAMetalDrawableActual::release() {
@autoreleasepool {
CAMetalLayerInternal* layer = objc_loadWeak(&_layer);
[layer releaseDrawable: _drawableID];
}
};
void CAMetalDrawableActual::reset() {
if (_glSemaphore != 0) {
reportGLErrors();
glDeleteSemaphoresEXT(1, &_glSemaphore);
_glSemaphore = 0;
reportGLErrors();
}
_semaphore = nullptr;
_presentCallback = nullptr;
_presentedTime = 0;
_queued = false;
};
void CAMetalDrawableActual::synchronizeRender() {
GLuint tex = _texture->glTexture();
GLenum layout = GL_LAYOUT_GENERAL_EXT;
glWaitSemaphoreEXT(_glSemaphore, 0, NULL, 1, &tex, &layout);
};
static void glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* context) {
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message);
};
@implementation CAMetalDrawableInternal
@synthesize drawableID = _drawableID;
@synthesize presentedTime = _presentedTime;
@synthesize texture = _texture;
@synthesize layer = _layer;
#if 0
+ (void)initialize
{
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugCallback, NULL);
}
#endif
- (instancetype)initWithLayer: (CAMetalLayer*)layer
drawable: (std::shared_ptr<CAMetalDrawableActual>)drawable
{
self = [super init];
if (self != nil) {
_layer = [layer retain];
_drawableID = drawable->drawableID();
_presentedHandlers = [NSMutableArray new];
_drawable = drawable;
_texture = [[MTLTextureInternal alloc] initWithTexture: drawable->texture() device: layer.device resourceOptions: MTLResourceStorageModeShared];
_drawable->reset();
}
return self;
}
- (void)dealloc
{
// explicitly release the drawable, in case it hasn't been released already.
// this occurs when the drawable is requested (via nextDrawable from CAMetalLayer) but never presented.
if (_drawable) {
_drawable->release();
}
[_texture release];
[_layer release];
[_presentedHandlers release];
[super dealloc];
}
- (void)present
{
// retain ourselves for the callback
[self retain];
_drawable->setPresentCallback([self]() {
@autoreleasepool {
// autorelease ourselves
[self autorelease];
self->_presentedTime = self->_drawable->presentedTime();
// TODO: synchronize/lock this
for (MTLDrawablePresentedHandler handler in self->_presentedHandlers) {
handler(self);
}
// release the C++ drawable instance (and allow it to be recycled)
self->_drawable->release();
self->_drawable = nullptr;
// XXX: it's not clear whether the texture is still accessible after presentation,
// but it *seems* that it would no longer be accessible. according to the documentation,
// you can safely retain a drawable to query certain properties such as drawableID and presentedTime,
// but no mention of texture is made.
// TODO: verify this
[self->_texture release];
self->_texture = nil;
}
});
_drawable->present();
}
- (void)presentAfterMinimumDuration: (CFTimeInterval)duration
{
// TODO
abort();
}
- (void)presentAtTime: (CFTimeInterval)presentationTime
{
// TODO
abort();
}
- (void)addPresentedHandler: (MTLDrawablePresentedHandler)block
{
[_presentedHandlers addObject: [[block copy] autorelease]];
}
- (std::shared_ptr<Indium::Drawable>)drawable
{
return _drawable;
}
@end

View File

@ -0,0 +1,116 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#import <QuartzCore/CAMetalDrawable.h>
#import <Metal/MTLDrawableInternal.h>
#import <Metal/MTLTextureInternal.h>
#import <OpenGL/gl.h>
#import <OpenGL/OpenGL.h>
#include <indium/indium.private.hpp>
@class CAMetalLayerInternal;
class CAMetalDrawableTexture: public Indium::PrivateTexture {
CGSize _size;
VkImage _image = VK_NULL_HANDLE;
VkDeviceMemory _memory = VK_NULL_HANDLE;
VkImageView _imageView = VK_NULL_HANDLE;
GLuint _textureID = 0;
GLuint _memoryObject = 0;
Indium::PixelFormat _pixelFormat;
VkImage _internalImage = VK_NULL_HANDLE;
VkDeviceMemory _internalMemory = VK_NULL_HANDLE;
public:
CAMetalDrawableTexture(CGSize size, Indium::PixelFormat pixelFormat, std::shared_ptr<Indium::PrivateDevice> privateDevice);
virtual ~CAMetalDrawableTexture();
GLuint glTexture() const;
virtual VkImageView imageView() override;
virtual VkImage image() override;
virtual VkImageLayout imageLayout() override;
virtual Indium::TextureType textureType() const override;
virtual Indium::PixelFormat pixelFormat() const override;
virtual size_t width() const override;
virtual size_t height() const override;
virtual size_t depth() const override;
virtual size_t mipmapLevelCount() const override;
virtual size_t arrayLength() const override;
virtual size_t sampleCount() const override;
virtual bool framebufferOnly() const override;
virtual bool allowGPUOptimizedContents() const override;
virtual bool shareable() const override;
virtual Indium::TextureSwizzleChannels swizzle() const override;
virtual void replaceRegion(Indium::Region region, size_t mipmapLevel, const void* bytes, size_t bytesPerRow) override;
virtual void replaceRegion(Indium::Region region, size_t mipmapLevel, size_t slice, const void* bytes, size_t bytesPerRow, size_t bytesPerImage) override;
virtual void precommit(std::shared_ptr<Indium::PrivateCommandBuffer> cmdbuf) override;
virtual bool needsExportablePresentationSemaphore() const override;
};
class CAMetalDrawableActual: public Indium::Drawable {
private:
std::shared_ptr<CAMetalDrawableTexture> _texture = nullptr;
CAMetalLayerInternal* _layer = nil;
std::shared_ptr<Indium::BinarySemaphore> _semaphore = nullptr;
GLuint _glSemaphore = 0;
std::function<void()> _presentCallback = nullptr;
NSUInteger _drawableID = NSUIntegerMax;
CFTimeInterval _presentedTime = 0;
bool _queued = false;
CGLContextObj _glContext = nullptr;
public:
CAMetalDrawableActual(CAMetalLayerInternal* layer, CGSize size, NSUInteger drawableID, Indium::PixelFormat pixelFormat, std::shared_ptr<Indium::Device> device, CGLContextObj _glContext);
virtual ~CAMetalDrawableActual();
virtual void present() override;
std::shared_ptr<CAMetalDrawableTexture> texture();
NSUInteger drawableID() const;
CFTimeInterval presentedTime() const;
void setPresentCallback(std::function<void()> presentCallback);
void disown();
void synchronizeRender();
void didPresent();
void didDrop();
// this has nothing to do with memory management; this indicates that the drawable is no longer in use and can be recycled
void release();
void reset();
};
@interface CAMetalDrawableInternal : NSObject <CAMetalDrawable, MTLDrawableInternal> {
NSUInteger _drawableID;
CFTimeInterval _presentedTime;
std::shared_ptr<CAMetalDrawableActual> _drawable;
MTLTextureInternal* _texture;
CAMetalLayer* _layer;
NSMutableArray<MTLDrawablePresentedHandler>* _presentedHandlers;
}
- (instancetype)initWithLayer: (CAMetalLayer*)layer
drawable: (std::shared_ptr<CAMetalDrawableActual>)drawable;
@end

View File

@ -1,23 +0,0 @@
/*
This file is part of Darling.
Copyright (C) 2019 Lubos Dolezel
Darling is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Darling 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#import <QuartzCore/CAMetalLayer.h>
@implementation CAMetalLayer
@end

588
QuartzCore/CAMetalLayer.mm Normal file
View File

@ -0,0 +1,588 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#define GL_GLEXT_PROTOTYPES 1
#import "CAMetalLayerInternal.h"
#import "CAMetalDrawableInternal.h"
#import <Foundation/NSRaise.h>
#import <Metal/MTLDeviceInternal.h>
#import <QuartzCore/CALayerContext.h>
#include <algorithm>
#import "CALayerInternal.h"
static void reportGLErrors(void) {
#if 0
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR) {
printf("*** OPENGL ERROR: %d ***\n", err);
}
#endif
};
@implementation CAMetalLayer
// FIXME: this breaks inheritance from CAMetalLayer.
// the problem is that we need some C++ ivars, but we can't put those in the public header
// and this code needs to compile on 32-bit (so it can't use non-fragile ivars).
// maybe we could keep the troublesome ivars in an associated object...
//
// then again, maybe we could get away with stubbing the entire class when compiling for 32-bit
// since Metal is unavailable for 32-bit code.
+ (instancetype)allocWithZone: (NSZone*)zone
{
if (self == [CAMetalLayer class]) {
return [CAMetalLayerInternal allocWithZone: zone];
} else {
return [super allocWithZone: zone];
}
}
- (id<MTLDevice>)device
{
NSInvalidAbstractInvocation();
return nil;
}
- (void)setDevice: (id<MTLDevice>)device
{
NSInvalidAbstractInvocation();
}
- (id<MTLDevice>)preferredDevice
{
NSInvalidAbstractInvocation();
return nil;
}
- (MTLPixelFormat)pixelFormat
{
NSInvalidAbstractInvocation();
return MTLPixelFormatInvalid;
}
- (void)setPixelFormat: (MTLPixelFormat)pixelFormat
{
NSInvalidAbstractInvocation();
}
- (CGColorSpaceRef)colorspace
{
NSInvalidAbstractInvocation();
return NULL;
}
- (void)setColorspace: (CGColorSpaceRef)colorspace
{
NSInvalidAbstractInvocation();
}
- (BOOL)framebufferOnly
{
NSInvalidAbstractInvocation();
return YES;
}
- (void)setFramebufferOnly: (BOOL)framebufferOnly
{
NSInvalidAbstractInvocation();
}
- (CGSize)drawableSize
{
NSInvalidAbstractInvocation();
return CGSizeZero;
}
- (void)setDrawableSize: (CGSize)drawableSize
{
NSInvalidAbstractInvocation();
}
- (BOOL)presentsWithTransaction
{
NSInvalidAbstractInvocation();
return NO;
}
- (void)setPresentsWithTransaction: (BOOL)presentsWithTransaction
{
NSInvalidAbstractInvocation();
}
- (BOOL)displaySyncEnabled
{
NSInvalidAbstractInvocation();
return NO;
}
- (void)setDisplaySyncEnabled: (BOOL)displaySyncEnabled
{
NSInvalidAbstractInvocation();
}
- (BOOL)wantsExtendedDynamicRangeContent
{
NSInvalidAbstractInvocation();
return NO;
}
- (void)setWantsExtendedDynamicRangeContent: (BOOL)wantsExtendedDynamicRangeContent
{
NSInvalidAbstractInvocation();
}
- (CAEDRMetadata*)EDRMetadata
{
NSInvalidAbstractInvocation();
return nil;
}
- (void)setEDRMetadata: (CAEDRMetadata*)EDRMetadata
{
NSInvalidAbstractInvocation();
}
- (NSUInteger)maximumDrawableCount
{
NSInvalidAbstractInvocation();
return 0;
}
- (void)setMaximumDrawableCount: (NSUInteger)maximumDrawableCount
{
NSInvalidAbstractInvocation();
}
- (BOOL)allowsNextDrawableTimeout
{
NSInvalidAbstractInvocation();
return NO;
}
- (void)setAllowsNextDrawableTimeout: (BOOL)allowsNextDrawableTimeout
{
NSInvalidAbstractInvocation();
}
- (NSDictionary*)developerHUDProperties
{
NSInvalidAbstractInvocation();
return nil;
}
- (void)setDeveloperHUDProperties: (NSDictionary*)developerHUDProperties
{
NSInvalidAbstractInvocation();
}
- (id<CAMetalDrawable>)nextDrawable
{
NSInvalidAbstractInvocation();
return nil;
}
@end
@implementation CAMetalLayerInternal
@synthesize presentsWithTransaction = _presentsWithTransaction;
@synthesize displaySyncEnabled = _displaySyncEnabled;
@synthesize wantsExtendedDynamicRangeContent = _wantsExtendedDynamicRangeContent;
@synthesize EDRMetadata = _EDRMetadata;
@synthesize allowsNextDrawableTimeout = _allowsNextDrawableTimeout;
@synthesize developerHUDProperties = _developerHUDProperties;
// TODO: use CGL here instead of direct OpenGL calls
// (once we CGL-ify the rest of QuartzCore)
- (instancetype)init
{
self = [super init];
if (self != nil) {
_pixelFormat = MTLPixelFormatBGRA8Unorm;
_framebufferOnly = YES;
_drawableSize = _bounds.size; // TODO: multiply by contentsScale, once we add that to CALayer
_displaySyncEnabled = YES;
_maximumDrawableCount = 3;
_allowsNextDrawableTimeout = YES;
_developerHUDProperties = [NSDictionary new];
_drawableCondition = [NSCondition new];
}
return self;
}
- (void)dealloc
{
[_device release];
if (_colorspace) {
CGColorSpaceRelease(_colorspace);
}
[_EDRMetadata release];
[_developerHUDProperties release];
[_drawableCondition release];
reportGLErrors();
if (_tex != 0) {
glDeleteTextures(1, &_tex);
reportGLErrors();
}
[super dealloc];
}
//
// properties
//
- (id<MTLDevice>)device
{
return [[_device retain] autorelease];
}
- (void)setDevice: (id<MTLDevice>)device
{
id<MTLDevice> old = _device;
_device = [device retain];
[old release];
[self recreateDrawables];
}
- (id<MTLDevice>)preferredDevice
{
return [MTLCreateSystemDefaultDevice() autorelease];
}
- (MTLPixelFormat)pixelFormat
{
return _pixelFormat;
}
- (void)setPixelFormat: (MTLPixelFormat)pixelFormat
{
_pixelFormat = pixelFormat;
[self recreateDrawables];
}
- (CGColorSpaceRef)colorspace
{
// TODO: retain and autorelease this? it's technically an objc object
return _colorspace;
}
- (void)setColorspace: (CGColorSpaceRef)colorspace
{
CGColorSpaceRef old = _colorspace;
_colorspace = CGColorSpaceRetain(colorspace);
if (old) {
CGColorSpaceRelease(old);
}
}
- (BOOL)framebufferOnly
{
return _framebufferOnly;
}
- (void)setFramebufferOnly: (BOOL)framebufferOnly
{
_framebufferOnly = framebufferOnly;
[self recreateDrawables];
}
- (CGSize)drawableSize
{
return _drawableSize;
}
- (void)setDrawableSize: (CGSize)drawableSize
{
_drawableSize = drawableSize;
[self recreateDrawables];
}
- (NSUInteger)maximumDrawableCount
{
return _maximumDrawableCount;
}
- (void)setMaximumDrawableCount: (NSUInteger)maximumDrawableCount
{
if (maximumDrawableCount < 2 || maximumDrawableCount > 3) {
@throw [NSException exceptionWithName: NSInvalidArgumentException reason: @"Attempt to set maximumDrawableCount to an invalid value" userInfo: nil];
}
_maximumDrawableCount = maximumDrawableCount;
[self recreateDrawables];
}
//
// overridden properties
//
// it appears that the drawable size does NOT change after initially being set
#if 0
- (void)setBounds: (CGRect)value
{
[super setBounds: value];
CGSize drawableSize = value.size;
// TODO: multiply by contentsScale (CALayer doesn't currently have that property)
[self setDrawableSize: drawableSize];
}
#endif
//
// methods
//
- (id<CAMetalDrawable>)nextDrawable
{
std::shared_ptr<CAMetalDrawableActual> drawable = nullptr;
[_drawableCondition lock];
NSDate* endTime = [NSDate dateWithTimeIntervalSinceNow: 1];
while (_usableDrawablesBitmap == 0) {
if (_allowsNextDrawableTimeout) {
if (![_drawableCondition waitUntilDate: endTime]) {
break;
}
} else {
[_drawableCondition wait];
}
}
if (_usableDrawablesBitmap == 0) {
// timeout reached
[_drawableCondition unlock];
return nil;
}
uint8_t drawableID = UINT8_MAX;
for (uint8_t i = 0; i < _maximumDrawableCount; ++i) {
if (_usableDrawablesBitmap & (1u << i)) {
drawableID = i;
break;
}
}
if (drawableID == UINT8_MAX) {
// shouldn't happen, but just in case
[_drawableCondition unlock];
return nil;
}
_usableDrawablesBitmap &= ~(1u << drawableID);
drawable = _drawables[drawableID];
[_drawableCondition unlock];
return [[[CAMetalDrawableInternal alloc] initWithLayer: self drawable: drawable] autorelease];
}
- (void)queuePresent: (NSUInteger)drawableID
{
@synchronized(self) {
_queuedDrawables[_queuedDrawableCount] = drawableID;
++_queuedDrawableCount;
}
// we now need to schedule a render
//
// FIXME: once we fix up CALayer and make it more featureful, this needs to change to `self.needsDisplay = YES` or equivalently `[self setNeedsDisplay: YES]`
// right now, CALayer is missing all the needs-display logic (which is currently in NSView)
[self display];
}
- (void)releaseDrawable: (NSUInteger)drawableID
{
[_drawableCondition lock];
_usableDrawablesBitmap |= 1u << drawableID;
[_drawableCondition signal];
[_drawableCondition unlock];
}
- (void)recreateDrawables
{
[_drawableCondition lock];
// drop all queued presentations
for (NSUInteger i = 0; i < _queuedDrawableCount; ++i) {
auto& drawable = _drawables[_queuedDrawables[i]];
drawable->didDrop();
}
_queuedDrawableCount = 0;
// clear the drawable array (and disown them so they don't try to queue with us anymore)
for (auto& drawable: _drawables) {
if (drawable) {
drawable->disown();
}
drawable = nullptr;
}
_usableDrawablesBitmap = 0;
if (!_device || !_context || _drawableSize.width == 0 || _drawableSize.height == 0) {
// can't create new drawables
[_drawableCondition unlock];
return;
}
Indium::PixelFormat pixelFormat;
switch (_pixelFormat) {
case MTLPixelFormatBGRA8Unorm: pixelFormat = Indium::PixelFormat::BGRA8Unorm; break;
case MTLPixelFormatBGRA8Unorm_sRGB: pixelFormat = Indium::PixelFormat::BGRA8Unorm_sRGB; break;
case MTLPixelFormatRGBA16Float: pixelFormat = Indium::PixelFormat::RGBA16Float; break;
case MTLPixelFormatRGB10A2Unorm: pixelFormat = Indium::PixelFormat::RGB10A2Unorm; break;
case MTLPixelFormatBGR10A2Unorm: pixelFormat = Indium::PixelFormat::BGR10A2Unorm; break;
case MTLPixelFormatBGRA10_XR: pixelFormat = Indium::PixelFormat::BGRA10_XR; break;
case MTLPixelFormatBGRA10_XR_sRGB: pixelFormat = Indium::PixelFormat::BGRA10_XR_sRGB; break;
case MTLPixelFormatBGR10_XR: pixelFormat = Indium::PixelFormat::BGR10_XR; break;
case MTLPixelFormatBGR10_XR_sRGB: pixelFormat = Indium::PixelFormat::BGR10_XR_sRGB; break;
default: pixelFormat = Indium::PixelFormat::Invalid; break;
}
CGLContextObj prev = CGLGetCurrentContext();
CGLSetCurrentContext(_context.glContext);
// now let's start creating the new drawables
for (size_t i = 0; i < _maximumDrawableCount; ++i) {
_drawables[i] = std::make_shared<CAMetalDrawableActual>(self, _drawableSize, i, pixelFormat, ((MTLDeviceInternal*)self.device).device, _context.glContext);
_usableDrawablesBitmap |= 1u << i;
}
// now signal the right number of waiters
for (size_t i = 0; i < _maximumDrawableCount; ++i) {
[_drawableCondition signal];
}
[_drawableCondition unlock];
@synchronized(self) {
// resize the render texture
if (_tex != 0) {
reportGLErrors();
glDeleteTextures(1, &_tex);
reportGLErrors();
glCreateTextures(GL_TEXTURE_2D, 1, &_tex);
reportGLErrors();
glTextureStorage2D(_tex, 1, GL_RGBA8, _drawableSize.width, _drawableSize.height);
reportGLErrors();
glTextureParameteri(_tex, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(_tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(_tex, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(_tex, GL_TEXTURE_WRAP_T, GL_REPEAT);
reportGLErrors();
}
}
CGLSetCurrentContext(prev);
}
- (void)prepareRender
{
std::shared_ptr<CAMetalDrawableActual> drawable = nullptr;
@synchronized(self) {
if (_queuedDrawableCount > 0) {
drawable = _drawables[_queuedDrawables[0]];
--_queuedDrawableCount;
std::copy(_queuedDrawables.begin() + 1, _queuedDrawables.end(), _queuedDrawables.begin());
}
}
if (drawable) {
// wait for the drawable to be fully rendered before copying it to the render texture
// (this is a GPU-side wait)
drawable->synchronizeRender();
reportGLErrors();
glCopyImageSubData(drawable->texture()->glTexture(), GL_TEXTURE_2D, 0, 0, 0, 0, _tex, GL_TEXTURE_2D, 0, 0, 0, 0, _drawableSize.width, _drawableSize.height, 1);
reportGLErrors();
}
@synchronized(self) {
// FIXME: this is wrong, but there's no good way to tell when the texture is actually fully rendered/presented.
// this is at least close enough. it should balance out in the long run since we're supposed to be invoked on vsync
// or at least a fixed interval.
if (_lastPresentedDrawable) {
_lastPresentedDrawable->didPresent();
}
_lastPresentedDrawable = drawable;
}
}
- (NSNumber*)_textureId
{
return [NSNumber numberWithUnsignedInt: _tex];
}
//
// overridden methods
//
- (void)removeFromSuperlayer
{
// we're being removed from our superlayer, so we probably won't be rendered again for a while
//
// FIXME: if we're the layer for a root view, this won't be called (i think) so we'll just be leaked.
// we'll probably have to add another private method to be called when the layer is released externally (e.g. by the layer context).
@synchronized(self) {
if (_lastPresentedDrawable) {
// no way to tell if it was actually presented or not, so assume it was dropped
_lastPresentedDrawable->didDrop();
_lastPresentedDrawable = nullptr;
}
}
[super removeFromSuperlayer];
}
- (void)_setContext: (CALayerContext*)context
{
[super _setContext: context];
if (_tex == 0) {
// create our textures now using the context's CGLContext
CGLContextObj prev = CGLGetCurrentContext();
CGLSetCurrentContext(context.glContext);
reportGLErrors();
glCreateTextures(GL_TEXTURE_2D, 1, &_tex);
reportGLErrors();
glTextureStorage2D(_tex, 1, GL_RGBA8, _drawableSize.width, _drawableSize.height);
reportGLErrors();
glTextureParameteri(_tex, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(_tex, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(_tex, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(_tex, GL_TEXTURE_WRAP_T, GL_REPEAT);
reportGLErrors();
CGLSetCurrentContext(prev);
}
}
@end

View File

@ -0,0 +1,58 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#import <QuartzCore/CAMetalLayer.h>
#import <OpenGL/gl.h>
#include <memory>
#include <array>
class CAMetalDrawableActual;
@interface CAMetalLayerInternal : CAMetalLayer {
id<MTLDevice> _device;
MTLPixelFormat _pixelFormat;
CGColorSpaceRef _colorspace;
BOOL _framebufferOnly;
CGSize _drawableSize;
BOOL _presentsWithTransaction;
BOOL _displaySyncEnabled;
BOOL _wantsExtendedDynamicRangeContent;
CAEDRMetadata* _EDRMetadata;
NSUInteger _maximumDrawableCount;
BOOL _allowsNextDrawableTimeout;
NSDictionary* _developerHUDProperties;
std::array<std::shared_ptr<CAMetalDrawableActual>, 3> _drawables;
std::array<NSUInteger, 3> _queuedDrawables;
NSUInteger _queuedDrawableCount;
uint8_t _usableDrawablesBitmap;
NSCondition* _drawableCondition;
std::shared_ptr<CAMetalDrawableActual> _lastPresentedDrawable;
GLuint _tex;
}
- (void)queuePresent: (NSUInteger)drawableID;
- (void)releaseDrawable: (NSUInteger)drawableID;
- (void)prepareRender;
@end

View File

@ -2,18 +2,12 @@
#import <Onyx2D/O2Surface.h>
#import <OpenGL/OpenGL.h>
#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CALayer.h>
#import <QuartzCore/CAMediaTimingFunction.h>
#import <QuartzCore/CARenderer.h>
#import "CALayerInternal.h"
NSString *const kCARendererColorSpace = @"kCARendererColorSpace";
@interface CALayer (private)
- (void) _setContext: (CALayerContext *) context;
- (void) _setTextureId: (NSNumber *) value;
- (NSNumber *) _textureId;
@end
@implementation CARenderer
- (CGRect) bounds {

View File

@ -58,11 +58,12 @@ set(QuartzCore_sources
CAReplicatorLayer.m
CAScrollLayer.m
CAOpenGLLayer.m
CAMetalLayer.m
CAMetalLayer.mm
CAMetalDrawable.mm
CASpringAnimation.m
)
set_source_files_properties(${QuartzCore_sources} LANGUAGE C)
#set_source_files_properties(${QuartzCore_sources} LANGUAGE C)
set(DYLIB_COMPAT_VERSION "1.2.0")
set(DYLIB_CURRENT_VERSION "1.11.0")
@ -88,10 +89,20 @@ add_framework(QuartzCore
Onyx2D
OpenGL
CoreGraphics
Metal_private
indium_private
cxx
LINK_FLAGS
" -Wl,-reexport_library,${CMAKE_BINARY_DIR}/src/frameworks/CoreImage/CoreImage \
-Wl,-reexport_library,${CMAKE_BINARY_DIR}/src/frameworks/CoreVideo/CoreVideo \
-Wl,-reexport_library,${CMAKE_BINARY_DIR}/src/external/objc4/runtime/libobjc.A.dylib "
)
set_target_properties(QuartzCore
PROPERTIES
CXX_STANDARD 17
CXX_EXTENSIONS ON
CXX_STANDARD_REQUIRED ON
)
add_dependencies(QuartzCore CoreImage CoreVideo)

View File

@ -1,5 +1,9 @@
#import <CoreFoundation/CoreFoundation.h>
#define CA_EXPORT extern
#if __cplusplus
#define CA_EXPORT extern "C"
#else
#define CA_EXPORT extern
#endif
CA_EXPORT CFTimeInterval CACurrentMediaTime(void);

View File

@ -4,7 +4,7 @@
#import <QuartzCore/CAAction.h>
#import <QuartzCore/CATransform3D.h>
@class CAAnimation, CALayerContext;
@class CAAnimation, CALayerContext, CALayer;
enum {
kCALayerNotSizable = 0x00,
@ -42,6 +42,24 @@ CA_EXPORT NSString *const kCAContentsFormatRGBA8Uint;
CA_EXPORT NSString *const kCAContentsFormatRGBA16Float;
CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
@protocol CALayerDelegate <NSObject>
@optional
- (void)displayLayer: (CALayer*)layer;
- (void)drawLayer: (CALayer*)layer
inContext: (CGContextRef)ctx;
- (void)layerWillDraw: (CALayer*)layer;
- (void)layoutSublayersOfLayer: (CALayer*)layer;
- (id<CAAction>)actionForLayer: (CALayer*)layer
forKey: (NSString*)event;
@end
@interface CALayer : NSObject {
CALayerContext *_context;
CALayer *_superlayer;
@ -66,7 +84,7 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
@property(readonly) CALayer *superlayer;
@property(copy) NSArray *sublayers;
@property(assign) id delegate;
@property(assign) id<CALayerDelegate> delegate;
@property CGPoint anchorPoint;
@property CGPoint position;
@property CGRect bounds;
@ -102,13 +120,6 @@ CA_EXPORT NSString *const kCAContentsFormatGray8Uint;
@end
@interface NSObject (CALayerDelegate)
- (void) displayLayer: (CALayer *) layer;
- (void) drawLayer: (CALayer *) layer inContext: (CGContextRef) context;
@end
@protocol CALayoutManager <NSObject>
@optional

View File

@ -1,6 +1,7 @@
#import <CoreGraphics/CGGeometry.h>
#import <Foundation/NSObject.h>
#import <OpenGL/OpenGL.h>
#import <CoreGraphics/CGSubWindow.h>
@class CARenderer, CALayer, CGLPixelSurface, NSTimer, NSMutableArray, NSNumber;
@ -14,12 +15,17 @@
NSMutableArray *_deleteTextureIds;
NSTimer *_timer;
CGSubWindow* _subwindow;
void* _cglWindow;
}
@property(readonly) CGLContextObj glContext;
- initWithFrame: (CGRect) rect;
- (void) setFrame: (CGRect) value;
- (void) setLayer: (CALayer *) layer;
- (void) setSubwindow: (CGSubWindow*) subwindow;
- (void) invalidate;
@ -29,4 +35,6 @@
- (void) deleteTextureId: (NSNumber *) textureId;
- (void) flush;
@end

View File

@ -0,0 +1,29 @@
/*
* This file is part of Darling.
*
* Copyright (C) 2022 Darling developers
*
* Darling is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Darling 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
*/
#import <Metal/Metal.h>
@class CAMetalLayer;
@protocol CAMetalDrawable <MTLDrawable>
@property(readonly) id<MTLTexture> texture;
@property(readonly) CAMetalLayer* layer;
@end

View File

@ -18,6 +18,32 @@
*/
#import <QuartzCore/CALayer.h>
#import <Metal/Metal.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
@protocol CAMetalDrawable;
@class CAEDRMetadata;
struct _CAMetalLayerPrivate;
@interface CAMetalLayer : CALayer
@property(retain) id<MTLDevice> device;
@property(readonly) id<MTLDevice> preferredDevice;
@property MTLPixelFormat pixelFormat;
@property CGColorSpaceRef colorspace;
@property BOOL framebufferOnly;
@property CGSize drawableSize;
@property BOOL presentsWithTransaction;
@property BOOL displaySyncEnabled;
@property BOOL wantsExtendedDynamicRangeContent;
@property(strong) CAEDRMetadata* EDRMetadata;
@property NSUInteger maximumDrawableCount;
@property BOOL allowsNextDrawableTimeout;
@property(copy) NSDictionary* developerHUDProperties;
- (id<CAMetalDrawable>)nextDrawable;
@end

View File

@ -7,6 +7,7 @@
#import <QuartzCore/CATransaction.h>
#import <QuartzCore/CAGradientLayer.h>
#import <QuartzCore/CAMetalDrawable.h>
#import <QuartzCore/CAMetalLayer.h>
#import <QuartzCore/CAOpenGLLayer.h>
#import <QuartzCore/CAReplicatorLayer.h>