orca/src/mtl_surface.m

257 lines
7.6 KiB
Mathematica
Raw Normal View History

2023-03-05 15:05:43 +00:00
/************************************************************//**
*
* @file: mtl_surface.m
* @author: Martin Fouilleul
* @date: 12/07/2023
* @revision:
*
*****************************************************************/
#import<Metal/Metal.h>
#import <QuartzCore/QuartzCore.h>
#import<QuartzCore/CAMetalLayer.h>
#include<simd/simd.h>
#include"graphics_surface.h"
2023-03-05 15:05:43 +00:00
#include"macro_helpers.h"
#include"osx_app.h"
typedef struct mg_mtl_surface
{
mg_surface_data interface;
// permanent mtl resources
id<MTLDevice> device;
CAMetalLayer* mtlLayer;
id<MTLCommandQueue> commandQueue;
// transient metal resources
id<CAMetalDrawable> drawable;
id<MTLCommandBuffer> commandBuffer;
} mg_mtl_surface;
void mg_mtl_surface_destroy(mg_surface_data* interface)
{
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
@autoreleasepool
{
//NOTE: when GPU frame capture is enabled, if we release resources before all work is completed,
// libMetalCapture crashes... the following hack avoids this crash by enqueuing a last (empty)
// command buffer and waiting for it to complete, ensuring all previous buffers have completed
id<MTLCommandBuffer> endBuffer = [surface->commandQueue commandBuffer];
[endBuffer commit];
[endBuffer waitUntilCompleted];
2023-03-05 15:05:43 +00:00
[surface->commandQueue release];
[surface->mtlLayer removeFromSuperlayer];
[surface->mtlLayer release];
[surface->device release];
}
//NOTE: we don't use mp_layer_cleanup here, because the CAMetalLayer is taken care off by the surface itself
}
void mg_mtl_surface_acquire_command_buffer(mg_mtl_surface* surface)
{
if(surface->commandBuffer == nil)
{
surface->commandBuffer = [surface->commandQueue commandBuffer];
[surface->commandBuffer retain];
}
}
void mg_mtl_surface_acquire_drawable(mg_mtl_surface* surface)
2023-03-05 15:05:43 +00:00
{@autoreleasepool{
/*WARN(martin):
//TODO: we should stop trying to render if we detect that the app is in the background
or occluded
2023-03-05 15:05:43 +00:00
//TODO: We should set a reasonable timeout and skip the frame and log an error in case we are stalled
for too long.
*/
//NOTE: returned drawable could be nil if we stall for more than 1s, although that never seem to happen in practice?
if(surface->drawable == nil)
{
surface->drawable = [surface->mtlLayer nextDrawable];
if(surface->drawable)
{
[surface->drawable retain];
}
}
2023-03-05 15:05:43 +00:00
}}
void mg_mtl_surface_prepare(mg_surface_data* interface)
{
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
mg_mtl_surface_acquire_command_buffer(surface);
2023-03-05 15:05:43 +00:00
}
void mg_mtl_surface_present(mg_surface_data* interface)
{
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
@autoreleasepool
{
if(surface->commandBuffer != nil)
{
if(surface->drawable != nil)
{
[surface->commandBuffer presentDrawable: surface->drawable];
[surface->drawable release];
surface->drawable = nil;
}
[surface->commandBuffer commit];
[surface->commandBuffer release];
surface->commandBuffer = nil;
}
2023-03-05 15:05:43 +00:00
}
}
void mg_mtl_surface_swap_interval(mg_surface_data* interface, int swap)
{
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
@autoreleasepool
{
[surface->mtlLayer setDisplaySyncEnabled: (swap ? YES : NO)];
}
2023-03-05 15:05:43 +00:00
}
void mg_mtl_surface_set_frame(mg_surface_data* interface, mp_rect frame)
{
mg_mtl_surface* surface = (mg_mtl_surface*)interface;
mg_osx_surface_set_frame(interface, frame);
vec2 scale = mg_osx_surface_contents_scaling(interface);
CGRect cgFrame = {{frame.x, frame.y}, {frame.w, frame.h}};
// dispatch_async(dispatch_get_main_queue(),
// ^{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[surface->mtlLayer setFrame: cgFrame];
[CATransaction commit];
// });
2023-03-05 15:05:43 +00:00
CGSize drawableSize = (CGSize){.width = frame.w * scale.x, .height = frame.h * scale.y};
surface->mtlLayer.drawableSize = drawableSize;
}
//TODO fix that according to real scaling, depending on the monitor settings
static const f32 MG_MTL_SURFACE_CONTENTS_SCALING = 2;
//NOTE: max frames in flight (n frames being queued on the GPU while we're working on the n+1 frame).
// for triple buffering, there's 2 frames being queued on the GPU while we're working on the 3rd frame
static const int MG_MTL_MAX_FRAMES_IN_FLIGHT = 2;
2023-03-05 15:05:43 +00:00
mg_surface_data* mg_mtl_surface_create_for_window(mp_window window)
{
mg_mtl_surface* surface = 0;
mp_window_data* windowData = mp_window_ptr_from_handle(window);
if(windowData)
{
surface = (mg_mtl_surface*)malloc(sizeof(mg_mtl_surface));
mg_surface_init_for_window((mg_surface_data*)surface, windowData);
//NOTE(martin): setup interface functions
surface->interface.api = MG_METAL;
2023-03-05 15:05:43 +00:00
surface->interface.destroy = mg_mtl_surface_destroy;
surface->interface.prepare = mg_mtl_surface_prepare;
surface->interface.deselect = 0;
2023-03-05 15:05:43 +00:00
surface->interface.present = mg_mtl_surface_present;
surface->interface.swapInterval = mg_mtl_surface_swap_interval;
surface->interface.setFrame = mg_mtl_surface_set_frame;
@autoreleasepool
{
//-----------------------------------------------------------
//NOTE(martin): create a mtl device and a mtl layer and
//-----------------------------------------------------------
surface->device = MTLCreateSystemDefaultDevice();
[surface->device retain];
surface->mtlLayer = [CAMetalLayer layer];
[surface->mtlLayer retain];
surface->mtlLayer.device = surface->device;
[surface->mtlLayer setOpaque:NO];
[surface->interface.layer.caLayer addSublayer: (CALayer*)surface->mtlLayer];
//-----------------------------------------------------------
//NOTE(martin): set the size and scaling
//-----------------------------------------------------------
NSRect frame = [[windowData->osx.nsWindow contentView] frame];
CGSize size = frame.size;
surface->mtlLayer.frame = (CGRect){{0, 0}, size};
size.width *= MG_MTL_SURFACE_CONTENTS_SCALING;
size.height *= MG_MTL_SURFACE_CONTENTS_SCALING;
surface->mtlLayer.drawableSize = size;
surface->mtlLayer.contentsScale = MG_MTL_SURFACE_CONTENTS_SCALING;
surface->mtlLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
//NOTE(martin): handling resizing
// surface->mtlLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
// surface->mtlLayer.needsDisplayOnBoundsChange = YES;
2023-03-05 15:05:43 +00:00
//-----------------------------------------------------------
//NOTE(martin): create a command queue
//-----------------------------------------------------------
surface->commandQueue = [surface->device newCommandQueue];
[surface->commandQueue retain];
//NOTE(martin): command buffer and drawable are set on demand and at the end of each present() call
surface->drawable = nil;
surface->commandBuffer = nil;
}
}
return((mg_surface_data*)surface);
}
void* mg_mtl_surface_layer(mg_surface surface)
{
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->api == MG_METAL)
2023-03-05 15:05:43 +00:00
{
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
return(mtlSurface->mtlLayer);
}
else
{
return(nil);
}
}
void* mg_mtl_surface_drawable(mg_surface surface)
{
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->api == MG_METAL)
2023-03-05 15:05:43 +00:00
{
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
mg_mtl_surface_acquire_drawable(mtlSurface);
2023-03-05 15:05:43 +00:00
return(mtlSurface->drawable);
}
else
{
return(nil);
}
}
void* mg_mtl_surface_command_buffer(mg_surface surface)
{
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
if(surfaceData && surfaceData->api == MG_METAL)
2023-03-05 15:05:43 +00:00
{
mg_mtl_surface* mtlSurface = (mg_mtl_surface*)surfaceData;
mg_mtl_surface_acquire_command_buffer(mtlSurface);
2023-03-05 15:05:43 +00:00
return(mtlSurface->commandBuffer);
}
else
{
return(nil);
}
}