405 lines
15 KiB
Objective-C
405 lines
15 KiB
Objective-C
// Graphics layer abstraction.
|
|
|
|
#ifndef ED_GFX_INCLUDED
|
|
#define ED_GFX_INCLUDED
|
|
#include <AppKit/AppKit.h>
|
|
#include <Foundation/Foundation.h>
|
|
#include <CoreGraphics/CoreGraphics.h>
|
|
#include <Metal/Metal.h>
|
|
#include <QuartzCore/QuartzCore.h>
|
|
#include <QuartzCore/CoreAnimation.h>
|
|
#include <stdint.h>
|
|
|
|
#include "ed_array.h"
|
|
|
|
bool keep_running = true;
|
|
|
|
@interface EDGFXView : NSView
|
|
@end
|
|
@interface AppDelegate : NSObject<NSApplicationDelegate>
|
|
@end
|
|
@interface WindowDelegate : NSObject<NSWindowDelegate>
|
|
@end
|
|
|
|
#define wrapIdArray(T) typedef id<T> _ ## T;\
|
|
arrayTemplate(_ ## T);
|
|
|
|
wrapIdArray(MTLRenderPipelineState);
|
|
wrapIdArray(MTLBuffer);
|
|
wrapIdArray(MTLTexture);
|
|
|
|
#if defined(__APPLE__)
|
|
typedef struct {
|
|
NSApplication *application;
|
|
NSWindow *window;
|
|
EDGFXView *view;
|
|
bool keep_running;
|
|
|
|
// Metal objects
|
|
id<MTLDevice> device;
|
|
CAMetalLayer *metal_layer;
|
|
id<MTLLibrary> library;
|
|
id<MTLCommandQueue> command_queue;
|
|
|
|
array(_MTLRenderPipelineState) pipelines;
|
|
array(_MTLBuffer) buffers;
|
|
array(_MTLTexture) textures;
|
|
} _metal_gfx_context;
|
|
#endif
|
|
|
|
typedef void (*_gfx_frame_func)();
|
|
typedef struct {
|
|
#if defined(__APPLE__)
|
|
_metal_gfx_context backend;
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
uint32_t frame_width;
|
|
uint32_t frame_height;
|
|
_gfx_frame_func frame_func;
|
|
} gfx_context_t;
|
|
static gfx_context_t _gfx_context;
|
|
|
|
#ifdef ED_GFX_IMPLEMENTATION
|
|
|
|
#if defined(__APPLE__)
|
|
static void _metal_gfx_present(_metal_gfx_context *cx);
|
|
static void _metal_gfx_send_events(_metal_gfx_context *cx);
|
|
|
|
@implementation AppDelegate
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
|
keep_running = false;
|
|
|
|
return NSTerminateCancel;
|
|
}
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
|
keep_running = false;
|
|
|
|
return NO;
|
|
}
|
|
@end
|
|
|
|
@implementation WindowDelegate
|
|
- (BOOL)windowShouldClose:(NSApplication *)sender {
|
|
keep_running = false;
|
|
return YES;
|
|
}
|
|
|
|
- (void)windowDidResize:(NSNotification *)notification {
|
|
NSLog(@"did resize\n");
|
|
|
|
_gfx_context.frame_width = _gfx_context.backend.window.contentView.frame.size.width;
|
|
_gfx_context.frame_height = _gfx_context.backend.window.contentView.frame.size.height;
|
|
|
|
CGFloat scale = _gfx_context.backend.metal_layer.contentsScale;
|
|
[_gfx_context.backend.metal_layer setDrawableSize:CGSizeMake(_gfx_context.frame_width * scale, _gfx_context.frame_height * scale)];
|
|
|
|
_metal_gfx_present(&_gfx_context.backend);
|
|
}
|
|
@end
|
|
|
|
@implementation EDGFXView
|
|
- (BOOL)isOpaque {
|
|
return YES;
|
|
}
|
|
- (void)updateLayer {
|
|
_metal_gfx_present(&_gfx_context.backend);
|
|
}
|
|
|
|
- (BOOL)wantsLayer {
|
|
return YES;
|
|
}
|
|
- (BOOL)wantsUpdateLayer {
|
|
return YES;
|
|
}
|
|
- (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy {
|
|
return NSViewLayerContentsRedrawOnSetNeedsDisplay;
|
|
}
|
|
@end
|
|
|
|
static _metal_gfx_context _metal_gfx_init_context(uint32_t width, uint32_t height) {
|
|
NSApplication *application = [NSApplication sharedApplication];
|
|
if (application == NULL) {
|
|
fprintf(stderr, "NSApplication:sharedApplication failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
NSString *title = @"editor - [C is Illegal]";
|
|
|
|
NSRect rect = NSMakeRect(0, 0, width, height);
|
|
NSWindow *window = [[NSWindow alloc] initWithContentRect:rect
|
|
styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO];
|
|
EDGFXView *view = [[EDGFXView alloc] initWithFrame:rect];
|
|
[view updateTrackingAreas];
|
|
[window setTitle:title];
|
|
[window setContentView:view];
|
|
[window setDelegate:[[WindowDelegate alloc] init]];
|
|
[window makeKeyAndOrderFront:NULL];
|
|
|
|
// TODO: make this work
|
|
// if (application.mainMenu == NULL) {
|
|
// NSMenu *menu = [[NSMenu alloc] initWithTitle:@"an_editor"];
|
|
// if (menu == NULL) {
|
|
// fprintf(stderr, "failed to create application menu\n");
|
|
// exit(1);
|
|
// }
|
|
// application.mainMenu = menu;
|
|
// }
|
|
[application setDelegate:[[AppDelegate alloc] init]];
|
|
[application setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
[application setPresentationOptions:NSApplicationPresentationDefault];
|
|
[application finishLaunching];
|
|
|
|
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
|
|
|
CAMetalLayer *metal_layer = [CAMetalLayer layer];
|
|
metal_layer.device = device;
|
|
metal_layer.pixelFormat = MTLPixelFormatRGBA8Unorm;
|
|
metal_layer.frame = CGRectMake(0, 0, width, height);
|
|
metal_layer.needsDisplayOnBoundsChange = YES;
|
|
metal_layer.presentsWithTransaction = YES;
|
|
metal_layer.autoresizingMask = kCALayerWidthSizable|kCALayerHeightSizable;
|
|
|
|
// TODO: set this to the display dpi scale
|
|
metal_layer.contentsScale = 2.0;
|
|
view.wantsLayer = YES;
|
|
[view.layer addSublayer:metal_layer];
|
|
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
|
|
|
|
NSError *libraryError = NULL;
|
|
NSURL *libraryURL = [[NSBundle mainBundle] URLForResource:@"./shaders" withExtension:@"metallib"];
|
|
if (libraryURL == NULL) {
|
|
fprintf(stderr, "Couldn't find library file\n");
|
|
exit(1);
|
|
}
|
|
|
|
id<MTLLibrary> library = [device newLibraryWithURL:libraryURL error:&libraryError];
|
|
|
|
if (library == NULL) {
|
|
if (libraryError.description != NULL) {
|
|
NSLog(@"Error description: %@\n", libraryError.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
|
|
id<MTLCommandQueue> command_queue = [device newCommandQueue];
|
|
id<MTLFunction> vertex_func = [library newFunctionWithName:@"vs_main"];
|
|
id<MTLFunction> fragment_func = [library newFunctionWithName:@"fs_main"];
|
|
id<MTLFunction> ui_rect_vertex_func = [library newFunctionWithName:@"ui_rect_vs"];
|
|
id<MTLFunction> ui_rect_fragment_func = [library newFunctionWithName:@"ui_rect_fs"];
|
|
|
|
MTLRenderPipelineDescriptor *pipeline_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
|
[pipeline_descriptor setVertexFunction:vertex_func];
|
|
[pipeline_descriptor setFragmentFunction:fragment_func];
|
|
pipeline_descriptor.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm;
|
|
pipeline_descriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
|
|
pipeline_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
|
pipeline_descriptor.colorAttachments[0].blendingEnabled = true;
|
|
|
|
MTLRenderPipelineDescriptor *ui_rect_pipeline_descriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
|
[ui_rect_pipeline_descriptor setVertexFunction:ui_rect_vertex_func];
|
|
[ui_rect_pipeline_descriptor setFragmentFunction:ui_rect_fragment_func];
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
|
|
ui_rect_pipeline_descriptor.colorAttachments[0].blendingEnabled = true;
|
|
|
|
NSError *pipeline_error = NULL;
|
|
array(_MTLRenderPipelineState) pipelines = newArray(_MTLRenderPipelineState, 2);
|
|
pushArray(_MTLRenderPipelineState, &pipelines, [device newRenderPipelineStateWithDescriptor:pipeline_descriptor error:&pipeline_error]);
|
|
if (pipeline_error != NULL) {
|
|
if (pipeline_error.description != NULL) {
|
|
NSLog(@"Error description: %@\n", pipeline_error.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
pushArray(_MTLRenderPipelineState, &pipelines, [device newRenderPipelineStateWithDescriptor:ui_rect_pipeline_descriptor error:&pipeline_error]);
|
|
|
|
array(_MTLBuffer) buffers = newArray(_MTLBuffer, 8);
|
|
array(_MTLTexture) textures = newArray(_MTLTexture, 8);
|
|
|
|
if (pipeline_error != NULL) {
|
|
if (pipeline_error.description != NULL) {
|
|
NSLog(@"Error description: %@\n", pipeline_error.description);
|
|
}
|
|
|
|
exit(1);
|
|
}
|
|
|
|
return (_metal_gfx_context) {
|
|
.application = application,
|
|
.window = window,
|
|
.view = view,
|
|
.keep_running = true,
|
|
|
|
.device = device,
|
|
.metal_layer = metal_layer,
|
|
.library = library,
|
|
.command_queue = command_queue,
|
|
.pipelines = pipelines,
|
|
.buffers = buffers,
|
|
.textures = textures,
|
|
};
|
|
}
|
|
|
|
void _metal_gfx_send_events(_metal_gfx_context *cx) {
|
|
NSEvent *event = [cx->application nextEventMatchingMask:NSEventMaskAny
|
|
untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
|
|
|
|
[cx->application sendEvent:event];
|
|
[cx->application updateWindows];
|
|
}
|
|
|
|
void _metal_gfx_present(_metal_gfx_context *cx) {
|
|
_gfx_context.frame_func();
|
|
|
|
id<CAMetalDrawable> drawable = [cx->metal_layer nextDrawable];
|
|
|
|
id<MTLCommandBuffer> command_buffer = [cx->command_queue commandBuffer];
|
|
MTLRenderPassDescriptor *render_pass_desc = [MTLRenderPassDescriptor renderPassDescriptor];
|
|
render_pass_desc.colorAttachments[0].texture = drawable.texture;
|
|
render_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
|
render_pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(0,0,0,1);
|
|
render_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore;
|
|
|
|
id<MTLRenderCommandEncoder> encoder = [command_buffer renderCommandEncoderWithDescriptor:render_pass_desc];
|
|
|
|
// UI Text
|
|
[encoder setRenderPipelineState:cx->pipelines.data[0]];
|
|
// FIXME: allow these to be described by the user instead of hardcoded
|
|
[encoder setVertexBuffer:cx->buffers.data[0] offset:0 atIndex:0]; // vertices
|
|
[encoder setVertexBuffer:cx->buffers.data[2] offset:0 atIndex:1]; // glyph data
|
|
[encoder setVertexBuffer:cx->buffers.data[3] offset:0 atIndex:2]; // uniforms
|
|
[encoder setFragmentTexture:cx->textures.data[0] atIndex:0];
|
|
// TODO: get instance count properly
|
|
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:6 indexType:MTLIndexTypeUInt16 indexBuffer:cx->buffers.data[1] indexBufferOffset:0 instanceCount:36];
|
|
|
|
// UI Rects
|
|
[encoder setRenderPipelineState:cx->pipelines.data[1]];
|
|
// FIXME: allow these to be described by the user instead of hardcoded
|
|
[encoder setVertexBuffer:cx->buffers.data[0] offset:0 atIndex:0]; // vertices
|
|
[encoder setVertexBuffer:cx->buffers.data[4] offset:0 atIndex:1]; // ui rects
|
|
[encoder setVertexBuffer:cx->buffers.data[3] offset:0 atIndex:2]; // uniforms
|
|
// TODO: get instance count properly
|
|
[encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:6 indexType:MTLIndexTypeUInt16 indexBuffer:cx->buffers.data[1] indexBufferOffset:0 instanceCount:3];
|
|
|
|
[encoder endEncoding];
|
|
[command_buffer presentDrawable:drawable];
|
|
[command_buffer commit];
|
|
|
|
[command_buffer waitUntilScheduled];
|
|
}
|
|
|
|
size_t _metal_gfx_push_texture_buffer(_metal_gfx_context *cx, uint32_t width, uint32_t height, const void *data, size_t len) {
|
|
MTLTextureDescriptor *texture_desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm width:width height:height mipmapped:false];
|
|
_MTLTexture texture = [cx->device newTextureWithDescriptor:texture_desc];
|
|
|
|
MTLRegion region = MTLRegionMake2D(0, 0, width, height);
|
|
[texture replaceRegion:region mipmapLevel:0 slice:0 withBytes:data bytesPerRow:width*sizeof(uint8_t) bytesPerImage:len];
|
|
|
|
pushArray(_MTLTexture, &cx->textures, texture);
|
|
return cx->textures.size-1;
|
|
}
|
|
|
|
void _metal_gfx_resize_texture_buffer(_metal_gfx_context *cx, uint32_t width, size_t texture_index, uint32_t height) {
|
|
[cx->textures.data[texture_index] setPurgeableState:MTLPurgeableStateEmpty];
|
|
[cx->textures.data[texture_index] release];
|
|
|
|
MTLTextureDescriptor *texture_desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm width:width height:height mipmapped:false];
|
|
cx->textures.data[texture_index] = [cx->device newTextureWithDescriptor:texture_desc];
|
|
}
|
|
|
|
size_t _metal_gfx_push_vertex_buffer(_metal_gfx_context *cx, const void *data, size_t len) {
|
|
pushArray(_MTLBuffer, &cx->buffers, [cx->device newBufferWithBytes:data
|
|
length:len
|
|
options:MTLResourceStorageModeShared]
|
|
);
|
|
|
|
return cx->buffers.size-1;
|
|
}
|
|
size_t _metal_gfx_allocate_vertex_buffer(_metal_gfx_context *cx, size_t len) {
|
|
pushArray(_MTLBuffer, &cx->buffers, [cx->device newBufferWithLength:len options:MTLResourceStorageModeShared]
|
|
);
|
|
|
|
return cx->buffers.size-1;
|
|
}
|
|
void _metal_gfx_update_buffer(_metal_gfx_context *cx, size_t buffer_index, const void *data, size_t len) {
|
|
void *buffer_contents = [cx->buffers.data[buffer_index] contents];
|
|
|
|
// FIXME: actually check to see if this will fit in the buffer
|
|
memcpy(buffer_contents, data, len);
|
|
}
|
|
#endif
|
|
|
|
void * gfx_init_context(_gfx_frame_func frame_func, uint32_t width, uint32_t height) {
|
|
__auto_type backend =
|
|
#if defined(__APPLE__)
|
|
_metal_gfx_init_context(width, height);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
|
|
_gfx_context.backend = backend;
|
|
_gfx_context.frame_func = frame_func;
|
|
_gfx_context.frame_width = width;
|
|
_gfx_context.frame_height = height;
|
|
|
|
return &_gfx_context;
|
|
}
|
|
|
|
void gfx_run_events(gfx_context_t *cx) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_send_events(&cx->backend);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_push_texture_buffer(gfx_context_t *cx, uint32_t width, uint32_t height, const void *data, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_push_texture_buffer(&cx->backend, width, height, data, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_push_vertex_buffer(gfx_context_t *cx, const void *data, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_push_vertex_buffer(&cx->backend, data, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
size_t gfx_allocate_vertex_buffer(gfx_context_t *cx, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_allocate_vertex_buffer(&cx->backend, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
// FIXME: abstract different backends
|
|
void gfx_update_buffer(gfx_context_t *cx, size_t buffer_index, const void *data, size_t len) {
|
|
#if defined(__APPLE__)
|
|
return _metal_gfx_update_buffer(&cx->backend, buffer_index, data, len);
|
|
#else
|
|
#error "Unsupported graphics backend"
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
#endif
|