mirror of
https://github.com/darlinghq/darling-cocotron.git
synced 2024-11-26 21:40:44 +00:00
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:
parent
3159ee117c
commit
55bafce1f1
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
26
QuartzCore/CALayerInternal.h
Normal file
26
QuartzCore/CALayerInternal.h
Normal 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
|
745
QuartzCore/CAMetalDrawable.mm
Normal file
745
QuartzCore/CAMetalDrawable.mm
Normal 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, ®ion, 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
|
116
QuartzCore/CAMetalDrawableInternal.h
Normal file
116
QuartzCore/CAMetalDrawableInternal.h
Normal 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
|
@ -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
588
QuartzCore/CAMetalLayer.mm
Normal 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
|
58
QuartzCore/CAMetalLayerInternal.h
Normal file
58
QuartzCore/CAMetalLayerInternal.h
Normal 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
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
29
QuartzCore/include/QuartzCore/CAMetalDrawable.h
Normal file
29
QuartzCore/include/QuartzCore/CAMetalDrawable.h
Normal 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
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user