2020-04-25 21:17:54 +00:00
|
|
|
//
|
|
|
|
// Renderer.m
|
|
|
|
// MiniFB
|
|
|
|
//
|
|
|
|
// Created by Carlos Aragones on 22/04/2020.
|
|
|
|
// Copyright © 2020 Carlos Aragones. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import <simd/simd.h>
|
|
|
|
#import <ModelIO/ModelIO.h>
|
|
|
|
|
|
|
|
#import "iOSViewDelegate.h"
|
|
|
|
#include "WindowData_IOS.h"
|
|
|
|
#include <MiniFB.h>
|
|
|
|
#include <MiniFB_ios.h>
|
2020-05-17 16:31:00 +00:00
|
|
|
#include <MiniFB_internal.h>
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
#define kShader(inc, src) @inc#src
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
enum { MaxBuffersInFlight = 3 }; // Number of textures in flight (tripple buffered)
|
|
|
|
|
|
|
|
//--
|
|
|
|
NSString *g_shader_src = kShader(
|
|
|
|
"#include <metal_stdlib>\n",
|
|
|
|
using namespace metal;
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
//-------------
|
2020-04-25 21:17:54 +00:00
|
|
|
struct VertexOutput {
|
|
|
|
float4 pos [[position]];
|
|
|
|
float2 texcoord;
|
|
|
|
};
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
//-------------
|
2020-04-25 21:17:54 +00:00
|
|
|
struct Vertex {
|
|
|
|
float4 position [[position]];
|
|
|
|
};
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
//-------------
|
2020-04-25 21:17:54 +00:00
|
|
|
vertex VertexOutput
|
|
|
|
vertFunc(unsigned int vID[[vertex_id]], const device Vertex *pos [[ buffer(0) ]]) {
|
|
|
|
VertexOutput out;
|
|
|
|
|
|
|
|
out.pos = pos[vID].position;
|
|
|
|
|
|
|
|
out.texcoord.x = (float) (vID / 2);
|
|
|
|
out.texcoord.y = 1.0 - (float) (vID % 2);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
//-------------
|
2020-04-25 21:17:54 +00:00
|
|
|
fragment float4
|
|
|
|
fragFunc(VertexOutput input [[stage_in]], texture2d<half> colorTexture [[ texture(0) ]]) {
|
|
|
|
constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest);
|
|
|
|
|
|
|
|
// Sample the texture to obtain a color
|
|
|
|
const half4 colorSample = colorTexture.sample(textureSampler, input.texcoord);
|
|
|
|
|
|
|
|
// We return the color of the texture
|
|
|
|
return float4(colorSample);
|
|
|
|
};
|
|
|
|
);
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
@implementation iOSViewDelegate {
|
2020-05-17 17:48:59 +00:00
|
|
|
SWindowData *window_data;
|
|
|
|
SWindowData_IOS *window_data_ios;
|
|
|
|
|
|
|
|
id<MTLDevice> metal_device;
|
|
|
|
id<MTLLibrary> metal_library;
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
dispatch_semaphore_t semaphore;
|
|
|
|
id<MTLCommandQueue> command_queue;
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
id<MTLRenderPipelineState> pipeline_state;
|
|
|
|
id<MTLTexture> texture_buffer;
|
|
|
|
|
|
|
|
uint8_t current_buffer;
|
2020-04-25 21:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
2020-04-26 11:16:25 +00:00
|
|
|
-(nonnull instancetype) initWithMetalKitView:(nonnull MTKView *) view windowData:(nonnull SWindowData *) windowData {
|
2020-04-25 21:17:54 +00:00
|
|
|
self = [super init];
|
|
|
|
if (self) {
|
2020-05-17 17:48:59 +00:00
|
|
|
window_data = windowData;
|
|
|
|
window_data_ios = (SWindowData_IOS *) windowData->specific;
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
|
2020-05-17 16:31:00 +00:00
|
|
|
view.sampleCount = 1;
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
metal_device = view.device;
|
|
|
|
|
|
|
|
// Used for syncing the CPU and GPU
|
|
|
|
semaphore = dispatch_semaphore_create(MaxBuffersInFlight);
|
2020-05-17 16:31:00 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
// Setup command queue
|
|
|
|
command_queue = [metal_device newCommandQueue];
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
[self _createShaders];
|
|
|
|
[self _createAssets];
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
- (bool) _createShaders {
|
2020-05-17 17:48:59 +00:00
|
|
|
NSError *error = 0x0;
|
|
|
|
|
|
|
|
metal_library = [metal_device newLibraryWithSource:g_shader_src
|
|
|
|
options:[[MTLCompileOptions alloc] init]
|
|
|
|
error:&error
|
2020-04-25 21:17:54 +00:00
|
|
|
];
|
2020-05-17 17:48:59 +00:00
|
|
|
if (error || !metal_library) {
|
2020-04-25 21:17:54 +00:00
|
|
|
NSLog(@"Unable to create shaders %@", error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
id<MTLFunction> vertex_shader_func = [metal_library newFunctionWithName:@"vertFunc"];
|
|
|
|
id<MTLFunction> fragment_shader_func = [metal_library newFunctionWithName:@"fragFunc"];
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
if (!vertex_shader_func) {
|
|
|
|
NSLog(@"Unable to get vertFunc!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fragment_shader_func) {
|
|
|
|
NSLog(@"Unable to get fragFunc!\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
// Create a reusable pipeline state
|
2020-04-25 21:17:54 +00:00
|
|
|
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
|
|
|
pipelineStateDescriptor.label = @"MiniFB_pipeline";
|
|
|
|
pipelineStateDescriptor.vertexFunction = vertex_shader_func;
|
|
|
|
pipelineStateDescriptor.fragmentFunction = fragment_shader_func;
|
|
|
|
pipelineStateDescriptor.colorAttachments[0].pixelFormat = 80; //bgra8Unorm;
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
pipeline_state = [metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
|
|
|
|
if (!pipeline_state) {
|
2020-04-25 21:17:54 +00:00
|
|
|
NSLog(@"Failed to created pipeline state, error %@", error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
- (void) _createAssets {
|
2020-05-17 17:48:59 +00:00
|
|
|
static Vertex s_vertices[4] = {
|
|
|
|
{-1.0, -1.0, 0, 1},
|
|
|
|
{-1.0, 1.0, 0, 1},
|
|
|
|
{ 1.0, -1.0, 0, 1},
|
|
|
|
{ 1.0, 1.0, 0, 1},
|
|
|
|
};
|
|
|
|
memcpy(window_data_ios->vertices, s_vertices, sizeof(s_vertices));
|
|
|
|
|
2020-04-25 21:17:54 +00:00
|
|
|
MTLTextureDescriptor *td;
|
|
|
|
td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
|
2020-04-26 11:16:25 +00:00
|
|
|
width:window_data->buffer_width
|
|
|
|
height:window_data->buffer_height
|
2020-04-25 21:17:54 +00:00
|
|
|
mipmapped:false];
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
// Create the texture from the device by using the descriptor
|
|
|
|
texture_buffer = [metal_device newTextureWithDescriptor:td];
|
2020-04-25 21:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
2020-05-17 16:31:00 +00:00
|
|
|
- (void) drawInMTKView:(nonnull MTKView *) view {
|
2020-05-17 17:48:59 +00:00
|
|
|
// 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(semaphore, DISPATCH_TIME_FOREVER);
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
current_buffer = (current_buffer + 1) % MaxBuffersInFlight;
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
// Create a new command buffer for each render pass to the current drawable
|
|
|
|
id<MTLCommandBuffer> commandBuffer = [command_queue commandBuffer];
|
2020-04-25 21:17:54 +00:00
|
|
|
commandBuffer.label = @"minifb_command_buffer";
|
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
// Add completion hander which signals semaphore 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 = semaphore;
|
2020-04-25 21:17:54 +00:00
|
|
|
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
|
2020-04-26 11:16:25 +00:00
|
|
|
(void)buffer;
|
|
|
|
dispatch_semaphore_signal(block_sema);
|
2020-04-25 21:17:54 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
// Copy the bytes from our data object into the texture
|
2020-04-26 11:16:25 +00:00
|
|
|
MTLRegion region = { { 0, 0, 0 }, { window_data->buffer_width, window_data->buffer_height, 1 } };
|
2020-05-17 17:48:59 +00:00
|
|
|
[texture_buffer replaceRegion:region mipmapLevel:0 withBytes:window_data->draw_buffer bytesPerRow:window_data->buffer_stride];
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
// Delay getting the currentRenderPassDescriptor until absolutely needed. This avoids
|
|
|
|
// holding onto the drawable and blocking the display pipeline any longer than necessary
|
|
|
|
MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor;
|
|
|
|
if (renderPassDescriptor != nil) {
|
2020-04-26 15:42:23 +00:00
|
|
|
//renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
// 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
|
2020-05-17 17:48:59 +00:00
|
|
|
[renderEncoder setRenderPipelineState:pipeline_state];
|
2020-05-17 16:31:00 +00:00
|
|
|
[renderEncoder setVertexBytes:window_data_ios->vertices length:sizeof(window_data_ios->vertices) atIndex:0];
|
2020-04-25 21:17:54 +00:00
|
|
|
|
2020-05-17 17:48:59 +00:00
|
|
|
//[renderEncoder setFragmentTexture:texture_buffers[current_buffer] atIndex:0];
|
|
|
|
[renderEncoder setFragmentTexture:texture_buffer atIndex:0];
|
2020-04-25 21:17:54 +00:00
|
|
|
|
|
|
|
// Draw the vertices of our quads
|
|
|
|
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
- (void) mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
|
|
|
|
// Respond to drawable size or orientation changes here
|
2020-05-17 16:31:00 +00:00
|
|
|
window_data->window_width = size.width;
|
|
|
|
window_data->window_height = size.height;
|
|
|
|
|
|
|
|
kCall(resize_func, size.width, size.height);
|
2020-04-25 21:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|