an_editor/src/gfx.h

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