This commit is contained in:
Carlos Aragones 2020-04-25 23:17:54 +02:00
parent 4bd40299eb
commit 8e1a981085
11 changed files with 721 additions and 17 deletions

View File

@ -5,6 +5,12 @@ project(${PROJECT_NAME})
message("Processing " ${PROJECT_NAME}) message("Processing " ${PROJECT_NAME})
if(NOT DEFINED IOS)
if(TOLOWER CMAKE_SYSTEM_NAME STREQUAL "ios")
set(IOS true)
endif()
endif()
set(SrcLib set(SrcLib
include/MiniFB.h include/MiniFB.h
include/MiniFB_cpp.h include/MiniFB_cpp.h
@ -32,6 +38,16 @@ set(SrcMacOSX
src/macosx/WindowData_OSX.h src/macosx/WindowData_OSX.h
) )
set(SrcIOS
src/ios/WindowData_IOS.h
src/ios/iOSMiniFB.m
src/ios/iOSViewController.h
src/ios/iOSViewController.m
src/ios/iOSViewDelegate.h
src/ios/iOSViewDelegate.m
include/MiniFB_ios.h
)
set(SrcWayland set(SrcWayland
src/wayland/WaylandMiniFB.c src/wayland/WaylandMiniFB.c
src/wayland/WindowData_Way.h src/wayland/WindowData_Way.h
@ -93,7 +109,7 @@ endif()
# Set default cmake flags # Set default cmake flags
#-------------------------------------- #--------------------------------------
if (APPLE) if (APPLE AND NOT IOS)
OPTION(USE_METAL_API "Build the project using metal API code" ON) OPTION(USE_METAL_API "Build the project using metal API code" ON)
elseif (UNIX) elseif (UNIX)
OPTION(USE_WAYLAND_API "Build the project using wayland API code" OFF) OPTION(USE_WAYLAND_API "Build the project using wayland API code" OFF)
@ -110,6 +126,8 @@ elseif (MINGW)
add_definitions(-D_WIN32_WINNT=0x0600) add_definitions(-D_WIN32_WINNT=0x0600)
list(APPEND SrcLib ${SrcWindows}) list(APPEND SrcLib ${SrcWindows})
elseif (IOS)
list(APPEND SrcLib ${SrcIOS})
elseif (APPLE) elseif (APPLE)
if(USE_METAL_API) if(USE_METAL_API)
add_definitions(-DUSE_METAL_API) add_definitions(-DUSE_METAL_API)
@ -142,10 +160,13 @@ add_library(minifb STATIC
#-------------------------------------- #--------------------------------------
if (APPLE) if (APPLE)
if(IOS)
else()
target_link_libraries(minifb "-framework Cocoa") target_link_libraries(minifb "-framework Cocoa")
target_link_libraries(minifb "-framework QuartzCore") target_link_libraries(minifb "-framework QuartzCore")
target_link_libraries(minifb "-framework Metal") target_link_libraries(minifb "-framework Metal")
target_link_libraries(minifb "-framework MetalKit") target_link_libraries(minifb "-framework MetalKit")
endif()
elseif (UNIX) elseif (UNIX)
if(USE_WAYLAND_API) if(USE_WAYLAND_API)
target_link_libraries(minifb "-lwayland-client") target_link_libraries(minifb "-lwayland-client")
@ -164,20 +185,39 @@ link_libraries(minifb)
# Examples # Examples
#-------------------------------------- #--------------------------------------
add_executable(noise if(NOT IOS)
add_executable(noise
tests/noise.c tests/noise.c
) )
add_executable(input_events add_executable(input_events
tests/input_events.c tests/input_events.c
) )
add_executable(input_events_cpp add_executable(input_events_cpp
tests/input_events_cpp.cpp tests/input_events_cpp.cpp
) )
add_executable(multiple_windows add_executable(multiple_windows
tests/multiple_windows.c tests/multiple_windows.c
) )
else()
add_executable(noise
tests/ios/main.m
tests/ios/AppDelegate.h
tests/ios/AppDelegate.m
)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Set CMake deployment target" ${FORCE_CACHE})
target_include_directories(minifb PRIVATE src)
add_definitions(-DTVOS_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET})
target_include_directories(noise PRIVATE src/ios)
target_link_libraries(noise "-framework UIKit")
target_link_libraries(noise "-framework Metal")
endif()
message("Done " ${PROJECT_NAME}) message("Done " ${PROJECT_NAME})

7
include/MiniFB_ios.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <MiniFB_enums.h>
void user_implemented_init(struct mfb_window *window);
void user_implemented_update(struct mfb_window *window);

15
src/ios/WindowData_IOS.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <MiniFB_enums.h>
#include <WindowData.h>
#include <MetalKit/MetalKit.h>
typedef struct Vertex {
float x, y, z, w;
} Vertex;
typedef struct {
id<MTLCommandQueue> command_queue;
id<MTLRenderPipelineState> pipeline_state;
} SWindowData_IOS;

170
src/ios/iOSMiniFB.m Normal file
View File

@ -0,0 +1,170 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <mach/mach_time.h>
#include <MiniFB.h>
#include "MiniFB_internal.h"
#include "WindowData.h"
#include "WindowData_IOS.h"
//-------------------------------------
struct mfb_window *
mfb_open(const char *title, unsigned width, unsigned height) {
return mfb_open_ex(title, width, height, 0);
}
//-------------------------------------
struct mfb_window *
mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) {
kUnused(title);
kUnused(flags);
SWindowData *window_data;
window_data = malloc(sizeof(SWindowData));
memset(window_data, 0, sizeof(SWindowData));
SWindowData_IOS *window_data_ios = malloc(sizeof(SWindowData_IOS));
memset((void *) window_data_ios, 0, sizeof(SWindowData_IOS));
window_data->specific = window_data_ios;
window_data->window_width = width;
window_data->window_height = height;
window_data->dst_width = width;
window_data->dst_height = height;
window_data->buffer_width = width;
window_data->buffer_height = height;
window_data->buffer_stride = width * 4;
window_data->draw_buffer = malloc(width * height * 4);
if (!window_data->draw_buffer) {
NSLog(@"Unable to create draw buffer");
}
return (struct mfb_window *) window_data;
}
//-------------------------------------
static void
destroy_window_data(SWindowData *window_data) {
if(window_data == 0x0)
return;
@autoreleasepool {
SWindowData_IOS *window_data_ios = (SWindowData_IOS *) window_data->specific;
if(window_data_ios != 0x0) {
memset((void *) window_data_ios, 0, sizeof(SWindowData_IOS));
free(window_data_ios);
}
memset(window_data, 0, sizeof(SWindowData));
free(window_data);
}
}
//-------------------------------------
mfb_update_state
mfb_update(struct mfb_window *window, void *buffer) {
if(window == 0x0) {
return STATE_INVALID_WINDOW;
}
SWindowData *window_data = (SWindowData *) window;
if(window_data->close) {
destroy_window_data(window_data);
return STATE_EXIT;
}
if(buffer == 0x0) {
return STATE_INVALID_BUFFER;
}
memcpy(window_data->draw_buffer, buffer, window_data->buffer_width * window_data->buffer_height * 4);
return STATE_OK;
}
//-------------------------------------
mfb_update_state
mfb_update_events(struct mfb_window *window) {
return STATE_OK;
}
//-------------------------------------
extern double g_time_for_frame;
bool
mfb_wait_sync(struct mfb_window *window) {
return true;
}
extern Vertex g_vertices[4];
//-------------------------------------
bool
mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height) {
SWindowData *window_data = (SWindowData *) window;
if(offset_x + width > window_data->window_width) {
return false;
}
if(offset_y + height > window_data->window_height) {
return false;
}
window_data->dst_offset_x = offset_x;
window_data->dst_offset_y = offset_y;
window_data->dst_width = width;
window_data->dst_height = height;
float x1 = ((float) offset_x / window_data->window_width) * 2.0f - 1.0f;
float x2 = (((float) offset_x + width) / window_data->window_width) * 2.0f - 1.0f;
float y1 = ((float) offset_y / window_data->window_height) * 2.0f - 1.0f;
float y2 = (((float) offset_y + height) / window_data->window_height) * 2.0f - 1.0f;
g_vertices[0].x = x1;
g_vertices[0].y = y1;
g_vertices[1].x = x1;
g_vertices[1].y = y2;
g_vertices[2].x = x2;
g_vertices[2].y = y1;
g_vertices[3].x = x2;
g_vertices[3].y = y2;
return true;
}
//-------------------------------------
extern double g_timer_frequency;
extern double g_timer_resolution;
uint64_t
mfb_timer_tick() {
static mach_timebase_info_data_t timebase = { 0 };
if (timebase.denom == 0) {
(void) mach_timebase_info(&timebase);
}
uint64_t time = mach_absolute_time();
//return (time * s_timebase_info.numer) / s_timebase_info.denom;
// Perform the arithmetic at 128-bit precision to avoid the overflow!
uint64_t high = (time >> 32) * timebase.numer;
uint64_t highRem = ((high % timebase.denom) << 32) / timebase.denom;
uint64_t low = (time & 0xFFFFFFFFull) * timebase.numer / timebase.denom;
high /= timebase.denom;
return (high << 32) + highRem + low;
}
//-------------------------------------
void
mfb_timer_init() {
g_timer_frequency = 1e+9;
g_timer_resolution = 1.0 / g_timer_frequency;
}

View File

@ -0,0 +1,14 @@
//
// iOSViewController.h
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import <UIKit/UIKit.h>
#include "iOSViewDelegate.h"
@interface iOSViewController : UIViewController
@end

View File

@ -0,0 +1,49 @@
//
// iOSViewController.m
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import "iOSViewController.h"
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#import "iOSViewDelegate.h"
//-------------------------------------
@implementation iOSViewController
{
MTKView *_view;
iOSViewDelegate *_renderer;
}
//-------------------------------------
- (void) loadView {
UIView *view = [[MTKView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self setView:view];
[view release];
}
//-------------------------------------
- (void) viewDidLoad
{
[super viewDidLoad];
_view = (MTKView *)self.view;
_view.device = MTLCreateSystemDefaultDevice();
_view.backgroundColor = UIColor.blackColor;
if(!_view.device) {
NSLog(@"Metal is not supported on this device");
self.view = [[UIView alloc] initWithFrame:self.view.frame];
return;
}
_renderer = [[iOSViewDelegate alloc] initWithMetalKitView:_view];
[_renderer mtkView:_view drawableSizeWillChange:_view.bounds.size];
_view.delegate = _renderer;
}
@end

23
src/ios/iOSViewDelegate.h Normal file
View File

@ -0,0 +1,23 @@
//
// Renderer.h
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import <MetalKit/MetalKit.h>
#include "WindowData.h"
// Our platform independent renderer class.
// Implements the MTKViewDelegate protocol which allows it to accept per-frame
// update and drawable resize callbacks.
@interface iOSViewDelegate : NSObject <MTKViewDelegate>
{
@public SWindowData *m_window_data;
}
-(nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)view;
@end

222
src/ios/iOSViewDelegate.m Normal file
View File

@ -0,0 +1,222 @@
//
// 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>
//-------------------------------------
#define kShader(inc, src) @inc#src
//-------------------------------------
enum { MaxBuffersInFlight = 3 }; // Number of textures in flight (tripple buffered)
id<MTLDevice> g_metal_device = nil;
id<MTLLibrary> g_library = nil;
//--
Vertex g_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},
};
//--
NSString *g_shader_src = kShader(
"#include <metal_stdlib>\n",
using namespace metal;
//---------------------
struct VertexOutput {
float4 pos [[position]];
float2 texcoord;
};
struct Vertex {
float4 position [[position]];
};
//---------------------
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;
}
//---------------------
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 {
dispatch_semaphore_t m_semaphore;
id <MTLCommandQueue> m_command_queue;
id <MTLRenderPipelineState> m_pipeline_state;
id <MTLTexture> m_texture_buffer;
uint8_t m_current_buffer;
}
//-------------------------------------
-(nonnull instancetype) initWithMetalKitView:(nonnull MTKView *) view {
self = [super init];
if (self) {
g_metal_device = view.device;
view.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
view.sampleCount = 1;
uint32_t width = view.bounds.size.width;
uint32_t height = view.bounds.size.height;
m_window_data = (SWindowData *) mfb_open_ex("", width, height, 0);
m_semaphore = dispatch_semaphore_create(MaxBuffersInFlight);
m_command_queue = [g_metal_device newCommandQueue];
[self _createShaders];
[self _createAssets];
user_implemented_init((struct mfb_window *) m_window_data);
}
return self;
}
//-------------------------------------
- (bool) _createShaders {
NSError *error = nil;
g_library = [g_metal_device newLibraryWithSource:g_shader_src
options:[[MTLCompileOptions alloc] init]
error:&error
];
if (error || !g_library) {
NSLog(@"Unable to create shaders %@", error);
return false;
}
id<MTLFunction> vertex_shader_func = [g_library newFunctionWithName:@"vertFunc"];
id<MTLFunction> fragment_shader_func = [g_library newFunctionWithName:@"fragFunc"];
if (!vertex_shader_func) {
NSLog(@"Unable to get vertFunc!\n");
return false;
}
if (!fragment_shader_func) {
NSLog(@"Unable to get fragFunc!\n");
return false;
}
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;
m_pipeline_state = [g_metal_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
if (!m_pipeline_state) {
NSLog(@"Failed to created pipeline state, error %@", error);
return false;
}
return true;
}
//-------------------------------------
- (void) _createAssets {
MTLTextureDescriptor *td;
td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:m_window_data->window_width
height:m_window_data->window_height
mipmapped:false];
m_texture_buffer = [g_metal_device newTextureWithDescriptor:td];
}
//-------------------------------------
- (void) drawInMTKView:(nonnull MTKView *) view
{
// Per frame updates here
user_implemented_update((struct mfb_window *) m_window_data);
dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
m_current_buffer = (m_current_buffer + 1) % MaxBuffersInFlight;
id <MTLCommandBuffer> commandBuffer = [m_command_queue commandBuffer];
commandBuffer.label = @"minifb_command_buffer";
__block dispatch_semaphore_t block_sema = m_semaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
dispatch_semaphore_signal(block_sema);
}];
// Copy the bytes from our data object into the texture
MTLRegion region = { { 0, 0, 0 }, { m_window_data->window_width, m_window_data->window_height, 1 } };
[m_texture_buffer replaceRegion:region mipmapLevel:0 withBytes:m_window_data->draw_buffer bytesPerRow:m_window_data->buffer_stride];
// 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) {
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.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:m_pipeline_state];
[renderEncoder setVertexBytes:g_vertices length:sizeof(g_vertices) atIndex:0];
//[renderEncoder setFragmentTexture:m_texture_buffers[m_current_buffer] atIndex:0];
[renderEncoder setFragmentTexture:m_texture_buffer atIndex:0];
// 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
}
@end

20
tests/ios/AppDelegate.h Normal file
View File

@ -0,0 +1,20 @@
//
// AppDelegate.h
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
CADisplayLink *mDisplayLink;
}
@property (strong, nonatomic) UIWindow *window;
- (void) on_update;
@end

128
tests/ios/AppDelegate.m Normal file
View File

@ -0,0 +1,128 @@
//
// AppDelegate.m
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import "AppDelegate.h"
#include <MiniFB.h>
#include <iOSViewController.h>
static uint32_t *g_buffer = 0x0;
static uint32_t g_width = 0;
static uint32_t g_height = 0;
//-------------------------------------
void
user_implemented_init(struct mfb_window *window) {
g_width = mfb_get_window_width(window);
g_height = mfb_get_window_height(window);
g_buffer = malloc(g_width * g_height * 4);
// mfb_set_viewport(window, 50, 50, 200, 200);
}
//-------------------------------------
void
user_implemented_update(struct mfb_window *window) {
static int seed = 0xbeef;
int noise, carry;
if(g_buffer != 0x0) {
for (uint32_t i = 0; i < g_width * g_height; ++i) {
noise = seed;
noise >>= 3;
noise ^= seed;
carry = noise & 1;
noise >>= 1;
seed >>= 1;
seed |= (carry << 30);
noise &= 0xFF;
g_buffer[i] = MFB_RGB(noise, noise, noise);
}
}
mfb_update_state state = mfb_update(window, g_buffer);
if (state != STATE_OK) {
free(g_buffer);
g_buffer = 0x0;
g_width = 0;
g_height = 0;
}
}
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (void) on_update {
NSLog(@"si");
}
- (void) applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
UIWindow *window;
NSArray *pWindows;
size_t numWindows;
pWindows = [[UIApplication sharedApplication] windows];
numWindows = [pWindows count];
//iOSViewController *controller = [[iOSViewController alloc] initWithFrame: [UIScreen mainScreen].bounds];
iOSViewController *controller = [[iOSViewController alloc] init];
if(numWindows > 0)
{
window = [pWindows objectAtIndex:0];
}
else
{
// Notice that you need to set "Launch Screen File" in:
// project > executable > general to get the real size
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
NSLog(@"UIApplication has no window. We create one (%f, %f).", [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
}
[window setRootViewController:controller];
[controller release];
controller = (iOSViewController *) window.rootViewController;
[window makeKeyAndVisible];
mDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(on_update)];
[mDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[mDisplayLink invalidate];
}
@end

16
tests/ios/main.m Normal file
View File

@ -0,0 +1,16 @@
//
// main.m
// MiniFB
//
// Created by Carlos Aragones on 22/04/2020.
// Copyright © 2020 Carlos Aragones. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}