added metal support for macosx version (based on rust_minifb) (#17)
This commit is contained in:
parent
cb49ea94a0
commit
d6a52459ef
@ -1,15 +1,121 @@
|
||||
|
||||
#include "OSXWindow.h"
|
||||
#include "OSXWindowFrameView.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#if defined(USE_METAL_API)
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <MetalKit/MetalKit.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include "MiniFB.h"
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
extern id<MTLDevice> g_metal_device;
|
||||
extern id<MTLCommandQueue> g_command_queue;
|
||||
extern id<MTLLibrary> g_library;
|
||||
extern id<MTLRenderPipelineState> g_pipeline_state;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NSString* g_shadersSrc = @
|
||||
" #include <metal_stdlib>\n"
|
||||
"using namespace metal;\n"
|
||||
|
||||
"struct VertexOutput {\n"
|
||||
"float4 pos [[position]];\n"
|
||||
"float2 texcoord;\n"
|
||||
"};\n"
|
||||
|
||||
"vertex VertexOutput vertFunc(\n"
|
||||
"unsigned int vID[[vertex_id]])\n"
|
||||
"{\n"
|
||||
"VertexOutput out;\n"
|
||||
|
||||
"out.pos.x = (float)(vID / 2) * 4.0 - 1.0;\n"
|
||||
"out.pos.y = (float)(vID % 2) * 4.0 - 1.0;\n"
|
||||
"out.pos.z = 0.0;\n"
|
||||
"out.pos.w = 1.0;\n"
|
||||
|
||||
"out.texcoord.x = (float)(vID / 2) * 2.0;\n"
|
||||
"out.texcoord.y = 1.0 - (float)(vID % 2) * 2.0;\n"
|
||||
|
||||
"return out;\n"
|
||||
"}\n"
|
||||
|
||||
"fragment float4 fragFunc(VertexOutput input [[stage_in]],\n"
|
||||
"texture2d<half> colorTexture [[ texture(0) ]])\n"
|
||||
"{\n"
|
||||
"constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest);\n"
|
||||
|
||||
// Sample the texture to obtain a color
|
||||
"const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord);\n"
|
||||
|
||||
// We return the color of the texture
|
||||
"return float4(colorSample);\n"
|
||||
//"return half4(input.texcoord.x, input.texcoord.y, 0.0, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(USE_METAL_API)
|
||||
void* g_updateBuffer = 0;
|
||||
int g_width = 0;
|
||||
int g_height = 0;
|
||||
static OSXWindow* window_;
|
||||
#endif
|
||||
static OSXWindow *s_window;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
static bool create_shaders() {
|
||||
// Error
|
||||
NSError* nsError = NULL;
|
||||
NSError** nsErrorPtr = &nsError;
|
||||
|
||||
id<MTLLibrary> library = [g_metal_device newLibraryWithSource:g_shadersSrc
|
||||
options:[[MTLCompileOptions alloc] init]
|
||||
error:nsErrorPtr];
|
||||
|
||||
// Error update
|
||||
if (nsError || !library) {
|
||||
NSLog(@"Unable to create shaders %@", nsError);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_library = library;
|
||||
NSLog(@"Names %@", [g_library functionNames]);
|
||||
|
||||
id<MTLFunction> vertex_shader_func = [g_library newFunctionWithName:@"vertFunc"];
|
||||
id<MTLFunction> fragment_shader_func = [g_library newFunctionWithName:@"fragFunc"];
|
||||
|
||||
if (!vertex_shader_func) {
|
||||
printf("Unable to get vertFunc!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fragment_shader_func) {
|
||||
printf("Unable to get fragFunc!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a reusable pipeline state
|
||||
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||
pipelineStateDescriptor.label = @"MyPipeline";
|
||||
pipelineStateDescriptor.vertexFunction = vertex_shader_func;
|
||||
pipelineStateDescriptor.fragmentFunction = fragment_shader_func;
|
||||
pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm;
|
||||
|
||||
NSError *error = NULL;
|
||||
g_pipeline_state = [g_metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
|
||||
if (!g_pipeline_state)
|
||||
{
|
||||
NSLog(@"Failed to created pipeline state, error %@", error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -17,28 +123,91 @@ int mfb_open(const char* name, int width, int height)
|
||||
{
|
||||
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
#if !defined(USE_METAL_API)
|
||||
g_width = width;
|
||||
g_height = height;
|
||||
|
||||
#endif
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
g_metal_device = MTLCreateSystemDefaultDevice();
|
||||
|
||||
if (!g_metal_device) {
|
||||
printf("Your device/OS doesn't support Metal.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!create_shaders()) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
NSWindowStyleMask styles = NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskTitled;
|
||||
|
||||
NSRect rectangle = NSMakeRect(0, 0, width, height);
|
||||
window_ = [[OSXWindow alloc] initWithContentRect:rectangle styleMask:styles backing:NSBackingStoreBuffered defer:NO];
|
||||
s_window = [[OSXWindow alloc] initWithContentRect:rectangle styleMask:styles backing:NSBackingStoreBuffered defer:NO];
|
||||
|
||||
if (!window_)
|
||||
return 0;
|
||||
if (!s_window)
|
||||
return -3;
|
||||
|
||||
[window_ setTitle:[NSString stringWithUTF8String:name]];
|
||||
[window_ setReleasedWhenClosed:NO];
|
||||
[window_ performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:nil waitUntilDone:YES];
|
||||
#if defined(USE_METAL_API)
|
||||
s_window->draw_buffer = malloc(width * height * 4);
|
||||
|
||||
[window_ center];
|
||||
if (!s_window->draw_buffer)
|
||||
return -4;
|
||||
|
||||
// Setup command queue
|
||||
g_command_queue = [g_metal_device newCommandQueue];
|
||||
|
||||
WindowViewController* viewController = [WindowViewController new];
|
||||
|
||||
MTLTextureDescriptor* textureDescriptor = [[MTLTextureDescriptor alloc] init];
|
||||
|
||||
// Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is
|
||||
// an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)
|
||||
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
|
||||
// Set the pixel dimensions of the texture
|
||||
textureDescriptor.width = width;
|
||||
textureDescriptor.height = height;
|
||||
|
||||
// Create the texture from the device by using the descriptor
|
||||
|
||||
for (int i = 0; i < MaxBuffersInFlight; ++i) {
|
||||
viewController->m_texture_buffers[i] = [g_metal_device newTextureWithDescriptor:textureDescriptor];
|
||||
}
|
||||
|
||||
// Used for syncing the CPU and GPU
|
||||
viewController->m_semaphore = dispatch_semaphore_create(MaxBuffersInFlight);
|
||||
viewController->m_draw_buffer = s_window->draw_buffer;
|
||||
viewController->m_width = width;
|
||||
viewController->m_height = height;
|
||||
|
||||
MTKView* view = [[MTKView alloc] initWithFrame:rectangle];
|
||||
view.device = g_metal_device;
|
||||
view.delegate = viewController;
|
||||
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[s_window.contentView addSubview:view];
|
||||
|
||||
s_window->width = width;
|
||||
s_window->height = height;
|
||||
|
||||
//[s_window updateSize];
|
||||
#endif
|
||||
|
||||
[s_window setTitle:[NSString stringWithUTF8String:name]];
|
||||
[s_window setReleasedWhenClosed:NO];
|
||||
[s_window performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:nil waitUntilDone:YES];
|
||||
|
||||
[s_window center];
|
||||
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
[NSApp finishLaunching];
|
||||
#endif
|
||||
|
||||
[pool drain];
|
||||
|
||||
return 1;
|
||||
@ -50,8 +219,8 @@ void mfb_close()
|
||||
{
|
||||
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if (window_)
|
||||
[window_ close];
|
||||
if (s_window)
|
||||
[s_window close];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
@ -83,7 +252,7 @@ static int updateEvents()
|
||||
}
|
||||
[pool release];
|
||||
|
||||
if (window_->closed)
|
||||
if (s_window->closed)
|
||||
state = -1;
|
||||
|
||||
return state;
|
||||
@ -93,8 +262,12 @@ static int updateEvents()
|
||||
|
||||
int mfb_update(void* buffer)
|
||||
{
|
||||
#if defined(USE_METAL_API)
|
||||
memcpy(s_window->draw_buffer, buffer, s_window->width * s_window->height * 4);
|
||||
#else
|
||||
g_updateBuffer = buffer;
|
||||
#endif
|
||||
int state = updateEvents();
|
||||
[[window_ contentView] setNeedsDisplay:YES];
|
||||
[[s_window contentView] setNeedsDisplay:YES];
|
||||
return state;
|
||||
}
|
||||
|
@ -6,6 +6,11 @@
|
||||
{
|
||||
NSView* childContentView;
|
||||
@public bool closed;
|
||||
#if defined(USE_METAL_API)
|
||||
@public int width;
|
||||
@public int height;
|
||||
@public void* draw_buffer;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(USE_METAL_API)
|
||||
- (id)initWithContentRect:(NSRect)contentRect
|
||||
styleMask:(NSWindowStyleMask)windowStyle
|
||||
backing:(NSBackingStoreType)bufferingType
|
||||
@ -42,6 +43,7 @@
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -1,7 +1,34 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
#import <MetalKit/MetalKit.h>
|
||||
|
||||
// Number of textures in flight (tripple buffered)
|
||||
static const int MaxBuffersInFlight = 3;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface WindowViewController : NSViewController<MTKViewDelegate>
|
||||
{
|
||||
@public id<MTLTexture> m_texture_buffers[MaxBuffersInFlight];
|
||||
@public int m_current_buffer;
|
||||
@public void* m_draw_buffer;
|
||||
@public int m_width;
|
||||
@public int m_height;
|
||||
// Used for syncing with CPU/GPU
|
||||
@public dispatch_semaphore_t m_semaphore;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface OSXWindowFrameView : NSView
|
||||
{
|
||||
#if defined(USE_METAL_API)
|
||||
@private NSTrackingArea* trackingArea;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,7 +1,109 @@
|
||||
#import "OSXWindowFrameView.h"
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
#import <MetalKit/MetalKit.h>
|
||||
|
||||
id<MTLDevice> g_metal_device;
|
||||
id<MTLCommandQueue> g_command_queue;
|
||||
id<MTLLibrary> g_library;
|
||||
id<MTLRenderPipelineState> g_pipeline_state;
|
||||
|
||||
@implementation WindowViewController
|
||||
|
||||
-(void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
|
||||
{
|
||||
(void)view;
|
||||
(void)size;
|
||||
// resize
|
||||
}
|
||||
|
||||
-(void)drawInMTKView:(nonnull MTKView *)view
|
||||
{
|
||||
// Wait to ensure only MaxBuffersInFlight number of frames are getting proccessed
|
||||
// by any stage in the Metal pipeline (App, Metal, Drivers, GPU, etc)
|
||||
dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
|
||||
|
||||
// Iterate through our Metal buffers, and cycle back to the first when we've written to MaxBuffersInFlight
|
||||
m_current_buffer = (m_current_buffer + 1) % MaxBuffersInFlight;
|
||||
|
||||
// Calculate the number of bytes per row of our image.
|
||||
NSUInteger bytesPerRow = 4 * m_width;
|
||||
MTLRegion region = { { 0, 0, 0 }, { m_width, m_height, 1 } };
|
||||
|
||||
// Copy the bytes from our data object into the texture
|
||||
[m_texture_buffers[m_current_buffer] replaceRegion:region
|
||||
mipmapLevel:0 withBytes:m_draw_buffer bytesPerRow:bytesPerRow];
|
||||
|
||||
// Create a new command buffer for each render pass to the current drawable
|
||||
id<MTLCommandBuffer> commandBuffer = [g_command_queue commandBuffer];
|
||||
commandBuffer.label = @"minifb_command_buffer";
|
||||
|
||||
// Add completion hander which signals _inFlightSemaphore when Metal and the GPU has fully
|
||||
// finished processing the commands we're encoding this frame. This indicates when the
|
||||
// dynamic buffers filled with our vertices, that we're writing to this frame, will no longer
|
||||
// be needed by Metal and the GPU, meaning we can overwrite the buffer contents without
|
||||
// corrupting the rendering.
|
||||
__block dispatch_semaphore_t block_sema = m_semaphore;
|
||||
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
dispatch_semaphore_signal(block_sema);
|
||||
}];
|
||||
|
||||
MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor;
|
||||
|
||||
if (renderPassDescriptor != nil)
|
||||
{
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1.0, 0.0, 0.0, 1.0);
|
||||
|
||||
// Create a render command encoder so we can render into something
|
||||
id<MTLRenderCommandEncoder> renderEncoder =
|
||||
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
renderEncoder.label = @"minifb_command_encoder";
|
||||
|
||||
// Set render command encoder state
|
||||
[renderEncoder setRenderPipelineState:g_pipeline_state];
|
||||
|
||||
[renderEncoder setFragmentTexture:m_texture_buffers[m_current_buffer] atIndex:0];
|
||||
|
||||
// Draw the vertices of our quads
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
|
||||
vertexStart:0
|
||||
vertexCount:3];
|
||||
|
||||
// We're done encoding commands
|
||||
[renderEncoder endEncoding];
|
||||
|
||||
// Schedule a present once the framebuffer is complete using the current drawable
|
||||
[commandBuffer presentDrawable:view.currentDrawable];
|
||||
}
|
||||
|
||||
// Finalize rendering here & push the command buffer to the GPU
|
||||
[commandBuffer commit];
|
||||
}
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation OSXWindowFrameView
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(USE_METAL_API)
|
||||
-(void)updateTrackingAreas
|
||||
{
|
||||
if(trackingArea != nil) {
|
||||
[self removeTrackingArea:trackingArea];
|
||||
[trackingArea release];
|
||||
}
|
||||
|
||||
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
|
||||
trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:opts
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:trackingArea];
|
||||
}
|
||||
#else
|
||||
extern void* g_updateBuffer;
|
||||
extern int g_width;
|
||||
extern int g_height;
|
||||
@ -47,6 +149,7 @@ extern int g_height;
|
||||
|
||||
CGImageRelease(img);
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user