metal backend functional

This commit is contained in:
2022-11-19 00:42:33 +11:00
parent 8ce1cca1ef
commit a75e480155
10 changed files with 595 additions and 17 deletions

View File

@ -2,14 +2,24 @@ set(SOURCES
maths.h
draw.h
draw_common.c
$<$<NOT:$<OR:$<BOOL:${USE_OPENGL}>,$<BOOL:${USE_OPENGL_LEGACY}>>>:draw.c>
$<$<NOT:$<OR:$<BOOL:${USE_METAL}>,$<BOOL:${USE_OPENGL}>,$<BOOL:${USE_OPENGL_LEGACY}>>>:draw.c>
$<$<BOOL:${USE_METAL}>:draw_metal.m metal_shader_types.h>
$<$<BOOL:${USE_OPENGL}>:draw_opengl_core.c>
$<$<BOOL:${USE_OPENGL_LEGACY}>:draw_opengl.c>
stick.h
stick.c
analogue.c)
if (USE_OPENGL)
if (USE_METAL)
find_library(METAL Metal REQUIRED)
find_library(FOUNDATION Foundation REQUIRED)
find_library(QUARTZCORE QuartzCore REQUIRED)
include(MetalHelper)
metal_compile(OUTPUT shader.metallib SOURCES shader.metal)
include(BinHelper)
bin2h_compile(OUTPUT metalShader.h BIN ${CMAKE_CURRENT_BINARY_DIR}/shader.metallib)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/metalShader.h)
elseif (USE_OPENGL)
include(GL3WHelper)
add_gl3w(gl3w)
include(BinHelper)
@ -19,14 +29,16 @@ endif()
add_executable(${TARGET} ${SOURCES})
target_include_directories(${TARGET} PRIVATE
$<$<BOOL:${USE_OPENGL}>:${CMAKE_CURRENT_BINARY_DIR}>)
$<$<OR:$<BOOL:${USE_METAL}>,$<BOOL:${USE_OPENGL}>>:${CMAKE_CURRENT_BINARY_DIR}>)
target_link_libraries(${TARGET}
$<$<PLATFORM_ID:Windows>:SDL2::SDL2main>
SDL2::SDL2
$<$<BOOL:${USE_METAL}>:${METAL} ${QUARTZCORE} ${FOUNDATION}>
$<$<OR:$<BOOL:${USE_OPENGL}>,$<BOOL:${USE_OPENGL_LEGACY}>>:OpenGL::GL>
$<$<BOOL:${USE_OPENGL}>:gl3w>
$<$<BOOL:${GNU}>:m>)
target_compile_options(${TARGET} PRIVATE
$<$<BOOL:${GNU}>:-Wall -Wextra -pedantic -Wno-unused-parameter>)
target_compile_definitions(${TARGET} PRIVATE
$<$<BOOL:${USE_METAL}>:USE_METAL>
$<$<OR:$<BOOL:${USE_OPENGL}>,$<BOOL:${USE_OPENGL_LEGACY}>>:USE_OPENGL>)

View File

@ -37,6 +37,8 @@ int main(int argc, char** argv)
const int winpos = SDL_WINDOWPOS_CENTERED;
#ifdef USE_OPENGL
const int winflg = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
#elif defined USE_METAL
const int winflg = SDL_WINDOW_RESIZABLE | SDL_WINDOW_METAL | SDL_WINDOW_ALLOW_HIGHDPI;
#else
const int winflg = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
#endif

430
src/draw_metal.m Normal file
View File

@ -0,0 +1,430 @@
#include "draw.h"
#include "metalShader.h"
#include "metal_shader_types.h"
#include "maths.h"
#include <SDL_metal.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
@interface MetalRenderer : NSObject
@property (nonatomic, readwrite) uint32_t drawColour;
- (id) init:(SDL_Window*)window;
- (void) dealloc;
- (unsigned) resizeBuffer:(void**)buf itemSize:(unsigned)nsz reserveLen:(unsigned)reserve requiredNum:(unsigned)count;
- (void) resizeMtlBuffer:(id <MTLBuffer>*)buf length:(unsigned)length;
- (size) getDrawSize;
- (void) setClear:(uint32_t)colour;
- (void) setView:(size)viewportSize;
- (void) clearVerticesAndIndices;
- (void) reserveVertices:(unsigned)count;
- (void) reserveIndices:(unsigned)count;
- (uint16_t) queueVertex:(int)x :(int)y;
- (uint16_t) queueVertexF:(float)x :(float)y;
- (uint16_t) queueIndex:(uint16_t)idx;
- (void) queueIndices:(uint16_t*)idcs count:(unsigned)count;
- (void) present;
@end
#define DRAWLIST_CHUNK_SIZE 480
#define DRAWLIST_INIT_SIZE (DRAWLIST_CHUNK_SIZE)
@implementation MetalRenderer
{
SDL_Window* _window;
SDL_MetalView _view;
id<MTLDevice> _dev;
CAMetalLayer* _layer;
id<MTLCommandQueue> _queue;
MTLRenderPassDescriptor* _passDesc;
id<MTLRenderPipelineState> _pso; // gonna fine u on the belgrave & lillydale line
MTLViewport _viewport;
vector_float4 _drawColourF;
ShaderVertex* _vtxList;
uint16_t* _idxList;
unsigned _vtxListCount, _vtxListReserve, _idxListCount, _idxListReserve;
id<MTLBuffer> _vtxMtlBuffer, _idxMtlBuffer;
}
- (id) init:(SDL_Window*)window
{
if (!(self = [super init]))
return nil;
self.drawColour = BLACK;
_vtxList = NULL;
_idxList = NULL;
_vtxListCount = 0;
_idxListCount = 0;
_vtxListReserve = 0;
_idxListReserve = 0;
_vtxMtlBuffer = nil;
_idxMtlBuffer = nil;
// Create Metal view
_window = window;
_view = SDL_Metal_CreateView(_window);
// Get Metal device
#if 1
_dev = MTLCreateSystemDefaultDevice();
fprintf(stderr, "Default MTL device \"%s\"\n", [_dev.name UTF8String]);
#else
NSArray<id<MTLDevice>>* devices = MTLCopyAllDevices();
for (id<MTLDevice> i in devices)
{
if (!i.supportsRaytracing) continue;
if (_dev && i.isLowPower) continue;
_dev = i;
}
fprintf(stderr, "You have selected funny device \"%s\"\n", [_dev.name UTF8String]);
#endif
// Setup Metal layer
_layer = (__bridge CAMetalLayer*)SDL_Metal_GetLayer(_view);
_layer.device = _dev;
_layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
_queue = [_dev newCommandQueue];
_passDesc = [MTLRenderPassDescriptor new];
_passDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
_passDesc.colorAttachments[0].storeAction = MTLStoreActionStore;
[self setClear:self.drawColour]; // passDesc.colorAttachments[0].clearColor = curColour
// Compile shady bois
__autoreleasing NSError* booboo = nil;
dispatch_data_t shaderData = dispatch_data_create(shader_metallib, SHADER_METALLIB_SIZE, nil, nil);
id<MTLLibrary> lib = [_dev newLibraryWithData:shaderData error:&booboo];
if (!lib)
{
fprintf(stderr, "Metal shader compilation failed:\n%s\n", [[booboo localizedDescription] UTF8String]);
return nil;
}
id<MTLFunction> vertPrg = [lib newFunctionWithName:@"vertexMain"];
id<MTLFunction> fragPrg = [lib newFunctionWithName:@"fragmentMain"];
// Setup render pipeline state
MTLRenderPipelineDescriptor* pipeDesc = [[MTLRenderPipelineDescriptor alloc] init];
pipeDesc.vertexFunction = vertPrg;
pipeDesc.fragmentFunction = fragPrg;
pipeDesc.colorAttachments[0].pixelFormat = _layer.pixelFormat;
_pso = [_dev newRenderPipelineStateWithDescriptor:pipeDesc error:&booboo];
if (!_pso)
{
fprintf(stderr, "Your Myki has expired and u have been slapped with a $237 fine: %s\n", [[booboo localizedDescription] UTF8String]);
return nil;
}
[pipeDesc release];
// Set viewport
[self setView:[self getDrawSize]];
return self;
}
- (void) dealloc
{
SDL_Metal_DestroyView(_view);
[super dealloc];
}
- (unsigned) resizeBuffer:(void**)buf itemSize:(unsigned)nsz reserveLen:(unsigned)reserve requiredNum:(unsigned)count
{
if (*buf && count * nsz <= reserve)
return reserve;
// Calculate new capacity
unsigned newCapacity = (count + DRAWLIST_CHUNK_SIZE - 1) / DRAWLIST_CHUNK_SIZE * DRAWLIST_CHUNK_SIZE;
if (!*buf)
newCapacity = MAX(newCapacity, DRAWLIST_INIT_SIZE);
// (Re)allocate and return new reserve size
unsigned newReserve = newCapacity * nsz;
*buf = realloc(*buf, newReserve);
printf("buffer %p %u -> %u\n", *buf, reserve, newReserve);
return newReserve;
}
- (void) resizeMtlBuffer:(id <MTLBuffer>*)buf length:(unsigned)length
{
if (*buf && length <= [*buf length])
return;
printf("mtlBuf %p %lu -> %u\n", (void*)buf, (unsigned long)[*buf length], length);
id<MTLBuffer> new = [_dev newBufferWithLength:length options:MTLResourceStorageModeManaged];
if (*buf)
{
[*buf setPurgeableState:MTLPurgeableStateEmpty];
[*buf release];
}
*buf = new;
}
- (size) getDrawSize
{
size out;
SDL_Metal_GetDrawableSize(_window, &out.w, &out.h);
return out;
}
- (void)setDrawColour:(uint32_t)colour
{
_drawColour = colour;
const float mul = 1.0f / 255.0f;
_drawColourF = (vector_float4){
(float)((colour & 0xFF000000) >> 24) * mul,
(float)((colour & 0x00FF0000) >> 16) * mul,
(float)((colour & 0x0000FF00) >> 8) * mul,
(float)((colour & 0x000000FF)) * mul };
}
- (void) setClear:(uint32_t)colour
{
const double mul = 1.0 / 255.0;
_passDesc.colorAttachments[0].clearColor = MTLClearColorMake(
(double)((colour & 0xFF000000) >> 24) * mul,
(double)((colour & 0x00FF0000) >> 16) * mul,
(double)((colour & 0x0000FF00) >> 8) * mul,
(double)((colour & 0x000000FF)) * mul);
}
- (void) setView:(size)viewportSize
{
_viewport = (MTLViewport){
.originX = 0.0, .originY = 0.0,
.width = viewportSize.w, .height = viewportSize.h,
.znear = 1.0, .zfar = -1.0 };
}
- (void) clearVerticesAndIndices
{
_vtxListCount = 0;
_idxListCount = 0;
}
- (void) reserveVertices:(unsigned)count
{
count += _vtxListCount;
if (count * sizeof(ShaderVertex) > _vtxListReserve)
_vtxListReserve = [self resizeBuffer:(void**)&_vtxList
itemSize:sizeof(ShaderVertex) reserveLen:_vtxListReserve requiredNum:count];
}
- (void) reserveIndices:(unsigned)count
{
count += _idxListCount;
if (count * sizeof(uint16_t) > _idxListReserve)
_idxListReserve = [self resizeBuffer:(void**)&_idxList
itemSize:sizeof(uint16_t) reserveLen:_idxListReserve requiredNum:count];
}
- (uint16_t) queueVertex:(int)x :(int)y
{
if (_vtxListCount * sizeof(ShaderVertex) >= _vtxListReserve)
[self reserveVertices:1];
_vtxList[_vtxListCount] = (ShaderVertex){
.position = { (float)x, (float)y },
.colour = _drawColourF };
return _vtxListCount++;
}
- (uint16_t) queueVertexF:(float)x :(float)y
{
if (_vtxListCount * sizeof(ShaderVertex) >= _vtxListReserve)
[self reserveVertices:1];
_vtxList[_vtxListCount] = (ShaderVertex){
.position = { x, y },
.colour = _drawColourF };
return _vtxListCount++;
}
- (uint16_t) queueIndex:(uint16_t)idx
{
if (_idxListCount * sizeof(uint16_t) >= _idxListReserve)
[self reserveIndices:1];
_idxList[_idxListCount++] = idx;
return idx;
}
- (void) queueIndices:(uint16_t*)idcs count:(unsigned)count
{
if ((_idxListCount + count) * sizeof(uint16_t) >= _idxListReserve)
[self reserveIndices:count];
memcpy(&_idxList[_idxListCount], idcs, count * sizeof(uint16_t));
_idxListCount += count;
}
- (void) present
{
// Create or recreate buffers if needed & fill with data
if (_vtxListCount)
{
unsigned copyLen = _vtxListCount * sizeof(ShaderVertex);
if (_vtxMtlBuffer == nil || [_vtxMtlBuffer length] < copyLen)
[self resizeMtlBuffer:&_vtxMtlBuffer length:_vtxListReserve];
memcpy(_vtxMtlBuffer.contents, _vtxList, copyLen);
}
if (_idxListCount)
{
unsigned copyLen = _idxListCount * sizeof(uint16_t);
if (_idxMtlBuffer == nil || [_idxMtlBuffer length] < copyLen)
[self resizeMtlBuffer:&_idxMtlBuffer length:_idxListReserve];
memcpy(_idxMtlBuffer.contents, _idxList, copyLen);
}
@autoreleasepool
{
id<CAMetalDrawable> rt = [_layer nextDrawable];
_passDesc.colorAttachments[0].texture = rt.texture;
id<MTLCommandBuffer> cmdBuf = [_queue commandBuffer];
id<MTLRenderCommandEncoder> enc = [cmdBuf renderCommandEncoderWithDescriptor:_passDesc];
[enc setViewport:_viewport];
[enc setCullMode:MTLCullModeNone];
[enc setRenderPipelineState:_pso];
if (_vtxMtlBuffer != nil && _idxMtlBuffer != nil)
{
[enc setVertexBuffer:_vtxMtlBuffer offset:0 atIndex:ShaderInputIdxVerticies];
const vector_float2 viewportScale = { (float)(1.0 / _viewport.width), (float)(1.0 / _viewport.height) };
[enc setVertexBytes:&viewportScale length:sizeof(vector_float2) atIndex:ShaderInputViewportScale];
[enc drawIndexedPrimitives:MTLPrimitiveTypeLine
indexCount:_idxListCount indexType:MTLIndexTypeUInt16
indexBuffer:_idxMtlBuffer indexBufferOffset:0];
}
[enc endEncoding];
[cmdBuf presentDrawable:rt]; // u go to software premieres
[cmdBuf commit]; // is that linus torvolds?
// hear songs on pandora radiowo
[self clearVerticesAndIndices];
}
}
@end
static MetalRenderer* renderer = nil;
void DrawWindowHints(void) {}
int InitDraw(SDL_Window* window)
{
renderer = [[MetalRenderer alloc] init:window];
if (!renderer)
return -1;
return 0;
}
void QuitDraw(void)
{
[renderer release];
}
size GetDrawSizeInPixels(void)
{
return renderer ? [renderer getDrawSize] : (size){ 0, 0 };
}
void SetDrawViewport(size size)
{
[renderer setView:size];
}
void SetDrawColour(uint32_t c)
{
renderer.drawColour = c;
}
void DrawClear(void)
{
[renderer setClear:renderer.drawColour];
[renderer clearVerticesAndIndices];
}
void DrawPoint(int x, int y)
{
DrawCircleSteps(x, y, 1, 4);
}
void DrawRect(int x, int y, int w, int h)
{
[renderer reserveVertices:4];
uint16_t i00 = [renderer queueVertex:x :y];
uint16_t i10 = [renderer queueVertex:x + w :y];
uint16_t i11 = [renderer queueVertex:x + w :y + h];
uint16_t i01 = [renderer queueVertex:x :y + h];
uint16_t indices[] = { i00, i10, i10, i11, i11, i01, i01, i00 };
[renderer queueIndices:indices count:sizeof(indices) / sizeof(uint16_t)];
}
void DrawLine(int x1, int y1, int x2, int y2)
{
[renderer queueIndex:[renderer queueVertex:x1 :y1]];
[renderer queueIndex:[renderer queueVertex:x2 :y2]];
}
void DrawCircleSteps(int x, int y, int r, int steps)
{
const float fx = (float)x, fy = (float)y;
const float stepSz = (float)TAU / (float)abs(steps);
const float mag = (float)r;
// Draw whole circle in a single loop
[renderer reserveVertices:steps];
[renderer reserveIndices:steps * 2];
uint16_t base = [renderer queueIndex:[renderer queueVertex:x + r :y]];
for (int i = 1; i < steps; ++i)
{
const float theta = stepSz * (float)i;
uint16_t ii = [renderer queueVertexF:fx + cosf(theta) * mag :fy + sinf(theta) * mag];
[renderer queueIndices:(uint16_t[]){ ii, ii } count:2];
}
[renderer queueIndex:base];
}
void DrawArcSteps(int x, int y, int r, int startAng, int endAng, int steps)
{
const float fx = (float)x, fy = (float)y;
const float magw = (float)r, magh = (float)r;
const float start = (float)startAng * (float)DEG2RAD;
const float stepSz = (float)(endAng - startAng) / (float)abs(steps) * (float)DEG2RAD;
[renderer reserveVertices:steps];
[renderer reserveIndices:steps * 2];
uint16_t ii = [renderer queueVertexF:fx + cosf(start) * magw :fy - sinf(start) * magh];
for (int i = 1; i <= steps; ++i)
{
const float theta = start + stepSz * (float)i;
uint16_t iii = [renderer queueVertexF:fx + cosf(theta) * magw :fy - sinf(theta) * magh];
[renderer queueIndices:(uint16_t[]){ ii, iii } count:2];
ii = iii;
}
}
void DrawPresent(void)
{
[renderer present];
}

18
src/metal_shader_types.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef METAL_SHADER_TYPES_H
#define METAL_SHADER_TYPES_H
#include <simd/simd.h>
typedef enum
{
ShaderInputIdxVerticies = 0,
ShaderInputViewportScale = 1
} ShaderInputIdx;
typedef struct
{
vector_float2 position;
vector_float4 colour;
} ShaderVertex;
#endif//METAL_SHADER_TYPES_H

27
src/shader.metal Normal file
View File

@ -0,0 +1,27 @@
#include "metal_shader_types.h"
#include <metal_stdlib>
using namespace metal;
struct FragmentInput
{
float4 position [[position]];
float4 colour;
};
vertex FragmentInput vertexMain(
uint vertexId [[vertex_id]],
device const ShaderVertex* v [[buffer(ShaderInputIdxVerticies)]],
constant vector_float2& viewportScale [[buffer(ShaderInputViewportScale)]])
{
float2 eyePos = v[vertexId].position * viewportScale * float2(2.0f, -2.0f) - float2(1.0f, -1.0f);
FragmentInput o;
o.position = float4(eyePos, 0.0f, 1.0f);
o.colour = v[vertexId].colour;
return o;
}
fragment float4 fragmentMain(FragmentInput in [[stage_in]])
{
return in.colour;
}

View File

@ -20,7 +20,8 @@ typedef struct { int x, y, w, h; } rect;
#define MKRGB(C) (uint32_t)(((C) << 8) | 0x000000FF)
#define WHITE 0xFFFFFFFF
#define BLACK MKRGB(0x000000)
#define WHITE MKRGB(0xFFFFFF)
#define GREY1 MKGREY(0x1F, 0xFF)
#define GREY2 MKGREY(0x37, 0xFF)
#define GREY3 MKGREY(0x4F, 0xFF)