From 79608b38a27fda90407049e598d292c26b8dc9a7 Mon Sep 17 00:00:00 2001 From: Daniel Collin Date: Tue, 23 Oct 2018 20:51:50 +0200 Subject: [PATCH] merge macos-metal -> master commit 2d8e0c1692120e9997bd8d376be8a7cc2536429d Author: Daniel Collin Date: Tue Oct 23 20:51:09 2018 +0200 Bump to 0.11 and added info about macOS Metal commit 0f77968317194f2f72b86da287c04484aeee762a Author: Daniel Collin Date: Sun Oct 21 17:44:26 2018 +0200 Tripple buffer textures commit d268c10576c7e9063bd4605aed4a939d956eeb6b Author: Daniel Collin Date: Sun Oct 21 16:59:20 2018 +0200 Some cleanup commit 5544773e62afec6f69d0c3b6309e60a543905fef Author: Daniel Collin Date: Sun Oct 21 16:24:45 2018 +0200 Working but no proper sync yet commit 843a6625cfddac5a6e2d8e61170f796e763e4002 Author: Daniel Collin Date: Sun Oct 21 15:26:45 2018 +0200 Working texture commit 1d4446f1f10c497dde07e0d20c92eb8bb6f836ec Author: Daniel Collin Date: Sun Oct 21 14:45:52 2018 +0200 Removed vertex data We construct this in the shader instead commit 2e8027d73a1635e26e412dbda6494193fd04a060 Author: Daniel Collin Date: Sun Oct 21 14:42:36 2018 +0200 Fullscreen triangle setup commit 5efd974704c11577944ffdeff9eb9dd4fa40bde0 Author: Daniel Collin Date: Sun Oct 21 14:16:23 2018 +0200 Triangle on screen commit d9cf4eb7557f73adfbe1764f3da94a837a4ef8d3 Author: Daniel Collin Date: Sun Oct 21 12:17:04 2018 +0200 Some hacky metal with clear screen --- CHANGELOG.md | 4 + Cargo.toml | 2 +- README.md | 2 +- build.rs | 3 + src/native/macosx/MacMiniFB.m | 144 ++++++++++++++++++++++++ src/native/macosx/OSXWindow.m | 12 -- src/native/macosx/OSXWindowFrameView.h | 29 ++++- src/native/macosx/OSXWindowFrameView.m | 147 ++++++++++++++----------- 8 files changed, 258 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc92aa..818f863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This project follows semantic versioning. +### v0.11 (2018-10-23) + +- [changed] macOS now uses Metal for rendering the buffer. + ### v0.10.7 (2018-08-10) Thanks to Lukas Kalbertodt for these changes! diff --git a/Cargo.toml b/Cargo.toml index 54de6d3..9cf19df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minifb" -version = "0.10.7" +version = "0.11" license = "MIT/Apache-2.0" authors = ["Daniel Collin "] description = "Cross-platform window setup with optional bitmap rendering" diff --git a/README.md b/README.md index 09f0f3b..64423c9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Usage ```toml # Cargo.toml [dependencies] -minifb = "0.10" +minifb = "0.11" ``` Example diff --git a/build.rs b/build.rs index 7cd23bd..0a27206 100644 --- a/build.rs +++ b/build.rs @@ -5,10 +5,13 @@ fn main() { let env = env::var("TARGET").unwrap(); if env.contains("darwin") { cc::Build::new() + .flag("-mmacosx-version-min=10.10") .file("src/native/macosx/MacMiniFB.m") .file("src/native/macosx/OSXWindow.m") .file("src/native/macosx/OSXWindowFrameView.m") .compile("libminifb_native.a"); + println!("cargo:rustc-link-lib=framework=Metal"); + println!("cargo:rustc-link-lib=framework=MetalKit"); } else if env.contains("linux") { cc::Build::new() .file("src/native/x11/X11MiniFB.c") diff --git a/src/native/macosx/MacMiniFB.m b/src/native/macosx/MacMiniFB.m index 0a709a5..a627b57 100644 --- a/src/native/macosx/MacMiniFB.m +++ b/src/native/macosx/MacMiniFB.m @@ -2,8 +2,56 @@ #include "OSXWindowFrameView.h" #include #include +#include #include +extern id g_metal_device; +extern id g_command_queue; +extern id g_library; +extern id g_pipeline_state; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +NSString* g_shadersSrc = @ +" #include \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 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"; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static bool s_init = false; // window_handler.rs @@ -49,6 +97,57 @@ void cursor_init() { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static bool create_shaders() { + // Error + NSError* nsError = NULL; + NSError** nsErrorPtr = &nsError; + + id 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; + } + + NSLog(@"Names %@", [g_library functionNames]); + + g_library = library; + + id vertex_shader_func = [g_library newFunctionWithName:@"vertFunc"]; + id 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; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void* mfb_open(const char* name, int width, int height, uint32_t flags, int scale) { bool prev_init = s_init; @@ -60,6 +159,17 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; create_standard_menu(); cursor_init(); + g_metal_device = MTLCreateSystemDefaultDevice(); + + if (!g_metal_device) { + printf("Your device/OS doesn't support Metal."); + return 0; + } + + if (!create_shaders()) { + return 0; + } + s_init = true; } @@ -86,6 +196,40 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal if (!window->draw_buffer) return 0; + // 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 = 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; + [window.contentView addSubview:view]; + window->width = width; window->height = height; window->scale = scale; diff --git a/src/native/macosx/OSXWindow.m b/src/native/macosx/OSXWindow.m index c188016..0f5d450 100644 --- a/src/native/macosx/OSXWindow.m +++ b/src/native/macosx/OSXWindow.m @@ -144,10 +144,6 @@ object:self]; frameView = [[[OSXWindowFrameView alloc] initWithFrame:bounds] autorelease]; - frameView->width = width; - frameView->height = height; - frameView->draw_buffer = draw_buffer; - frameView->scale = scale; [super setContentView:frameView]; } @@ -195,14 +191,6 @@ - (void)updateSize { - OSXWindowFrameView* frameView = [super contentView]; - if (frameView) - { - frameView->width = width; - frameView->height = height; - frameView->draw_buffer = draw_buffer; - frameView->scale = scale; - } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/native/macosx/OSXWindowFrameView.h b/src/native/macosx/OSXWindowFrameView.h index d07d6a5..96062c4 100644 --- a/src/native/macosx/OSXWindowFrameView.h +++ b/src/native/macosx/OSXWindowFrameView.h @@ -1,11 +1,32 @@ #import +#import + +// Number of textures in flight (tripple buffered) +const int MaxBuffersInFlight = 3; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface WindowViewController : NSViewController +{ + @public id 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 + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface OSXWindowFrameView : NSView { - @public int scale; - @public int width; - @public int height; - @public void* draw_buffer; + //@public int scale; + //@public int width; + //@public int height; + //@public void* draw_buffer; @private NSTrackingArea* trackingArea; } diff --git a/src/native/macosx/OSXWindowFrameView.m b/src/native/macosx/OSXWindowFrameView.m index 1c7eb17..637557f 100644 --- a/src/native/macosx/OSXWindowFrameView.m +++ b/src/native/macosx/OSXWindowFrameView.m @@ -1,5 +1,85 @@ #import "OSXWindowFrameView.h" #import "OSXWindow.h" +#import + +id g_metal_device; +id g_command_queue; +id g_library; +id 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 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 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 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 @implementation OSXWindowFrameView @@ -22,73 +102,6 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)drawRect:(NSRect)rect -{ - (void)rect; - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; - - printf("drawRect\n"); - - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, draw_buffer, width * height * 4, NULL); - - CGImageRef img = CGImageCreate(width, height, 8, 32, width * 4, space, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, - provider, NULL, false, kCGRenderingIntentDefault); - - CGColorSpaceRelease(space); - CGDataProviderRelease(provider); - - CGContextDrawImage(context, CGRectMake(0, 0, width * scale, height * scale), img); - - CGImageRelease(img); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/* -- (BOOL)wantsUpdateLayer -{ - return TRUE; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - --(void)updateLayer -{ - printf("update layer\n"); - // Force the graphics context to clear to black so we don't get a flash of - // white until the app is ready to draw. In practice on modern macOS, this - // only gets called for window creation and other extraordinary events. - self.layer.backgroundColor = NSColor.blackColor.CGColor; - //NSGraphicsContext* context = [NSGraphicsContext currentContext]; - //[context scheduleUpdate]; - - //(void)rect; - CGContextRef context = [[NSGraphicsContext currentContext] CGContext]; - - //printf("drawRect\n"); - - CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, draw_buffer, width * height * 4, NULL); - - CGImageRef img = CGImageCreate(width, height, 8, 32, width * 4, space, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, - provider, NULL, false, kCGRenderingIntentDefault); - - CGColorSpaceRelease(space); - CGDataProviderRelease(provider); - - CGContextDrawImage(context, CGRectMake(0, 0, width * scale, height * scale), img); - - CGImageRelease(img); - - //ScheduleContextUpdates((SDL_WindowData *) _sdlWindow->driverdata); - //SDL_SendWindowEvent(_sdlWindow, SDL_WINDOWEVENT_EXPOSED, 0, 0); - //[context scheduleUpdate]; -} -*/ - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - (void)mouseDown:(NSEvent*)event { (void)event;