Implemented platform support for MacOS
This commit is contained in:
parent
7836ebfcd3
commit
1e652006b0
3 changed files with 275 additions and 0 deletions
20
build.zig
20
build.zig
|
|
@ -86,6 +86,26 @@ pub fn build(b: *std.Build) void {
|
|||
}
|
||||
b.installArtifact(exe);
|
||||
},
|
||||
.macos => {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "sideros",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/macos.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
exe.root_module.addIncludePath(b.path("src"));
|
||||
exe.linkSystemLibrary("vulkan");
|
||||
exe.root_module.addSystemIncludePath(.{ .cwd_relative = "/usr/local/include" });
|
||||
exe.root_module.addLibraryPath(.{ .cwd_relative = "/usr/local/lib" });
|
||||
exe.root_module.addCSourceFile(.{.file = b.path("src/window.m")});
|
||||
exe.root_module.linkFramework("Cocoa", .{});
|
||||
exe.root_module.linkFramework("Metal", .{});
|
||||
exe.root_module.linkFramework("QuartzCore", .{});
|
||||
exe.linkLibrary(sideros);
|
||||
b.installArtifact(exe);
|
||||
},
|
||||
else => {
|
||||
std.debug.panic("Compilation not implemented for OS: {any}\n", .{target.result.os.tag});
|
||||
},
|
||||
|
|
|
|||
168
src/macos.zig
Normal file
168
src/macos.zig
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
const std = @import("std");
|
||||
const sideros = @cImport({
|
||||
@cInclude("sideros_api.h");
|
||||
});
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("vulkan/vulkan.h");
|
||||
@cInclude("vulkan/vulkan_macos.h");
|
||||
@cInclude("vulkan/vulkan_metal.h");
|
||||
});
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const debug = (builtin.mode == .Debug);
|
||||
|
||||
const validation_layers: []const [*c]const u8 = if (!debug) &[0][*c]const u8{} else &[_][*c]const u8{
|
||||
"VK_LAYER_KHRONOS_validation",
|
||||
};
|
||||
|
||||
const device_extensions: []const [*c]const u8 = &[_][*c]const u8{
|
||||
c.VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
};
|
||||
|
||||
const Error = error{
|
||||
initialization_failed,
|
||||
extension_not_present,
|
||||
incompatible_driver,
|
||||
layer_not_present,
|
||||
out_of_memory,
|
||||
unknown_error,
|
||||
};
|
||||
|
||||
fn mapError(result: c_int) !void {
|
||||
return switch (result) {
|
||||
c.VK_SUCCESS => {},
|
||||
c.VK_ERROR_INITIALIZATION_FAILED => Error.initialization_failed,
|
||||
c.VK_ERROR_EXTENSION_NOT_PRESENT => Error.extension_not_present,
|
||||
c.VK_ERROR_INCOMPATIBLE_DRIVER => Error.incompatible_driver,
|
||||
c.VK_ERROR_LAYER_NOT_PRESENT => Error.layer_not_present,
|
||||
c.VK_ERROR_OUT_OF_DEVICE_MEMORY => Error.out_of_memory,
|
||||
else => Error.unknown_error,
|
||||
};
|
||||
}
|
||||
|
||||
extern fn create_window() void;
|
||||
extern fn poll_cocoa_events() void;
|
||||
extern fn is_window_closed() bool;
|
||||
extern fn get_metal_layer() *anyopaque;
|
||||
|
||||
fn vulkan_init_instance(allocator: std.mem.Allocator, handle: *c.VkInstance) !void {
|
||||
const extensions = [_][*c]const u8{ c.VK_MVK_MACOS_SURFACE_EXTENSION_NAME, c.VK_KHR_SURFACE_EXTENSION_NAME, c.VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME };
|
||||
|
||||
// Querry avaliable extensions size
|
||||
var avaliableExtensionsCount: u32 = 0;
|
||||
_ = c.vkEnumerateInstanceExtensionProperties(null, &avaliableExtensionsCount, null);
|
||||
// Actually querry avaliable extensions
|
||||
const avaliableExtensions = try allocator.alloc(c.VkExtensionProperties, avaliableExtensionsCount);
|
||||
defer allocator.free(avaliableExtensions);
|
||||
_ = c.vkEnumerateInstanceExtensionProperties(null, &avaliableExtensionsCount, avaliableExtensions.ptr);
|
||||
|
||||
// Check the extensions we want against the extensions the user has
|
||||
for (extensions) |need_ext| {
|
||||
var found = false;
|
||||
const needName = std.mem.sliceTo(need_ext, 0);
|
||||
for (avaliableExtensions) |useable_ext| {
|
||||
const extensionName = useable_ext.extensionName[0..std.mem.indexOf(u8, &useable_ext.extensionName, &[_]u8{0}).?];
|
||||
|
||||
if (std.mem.eql(u8, needName, extensionName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
std.debug.panic("ERROR: Needed vulkan extension {s} not found\n", .{need_ext});
|
||||
}
|
||||
}
|
||||
|
||||
// Querry avaliable layers size
|
||||
var avaliableLayersCount: u32 = 0;
|
||||
_ = c.vkEnumerateInstanceLayerProperties(&avaliableLayersCount, null);
|
||||
// Actually querry avaliable layers
|
||||
const availableLayers = try allocator.alloc(c.VkLayerProperties, avaliableLayersCount);
|
||||
defer allocator.free(availableLayers);
|
||||
_ = c.vkEnumerateInstanceLayerProperties(&avaliableLayersCount, availableLayers.ptr);
|
||||
|
||||
// Every layer we do have we add to this list, if we don't have it no worries just print a message and continue
|
||||
var newLayers = std.ArrayList([*c]const u8).init(allocator);
|
||||
defer newLayers.deinit();
|
||||
// Loop over layers we want
|
||||
for (validation_layers) |want_layer| {
|
||||
var found = false;
|
||||
for (availableLayers) |useable_validation| {
|
||||
const layer_name: [*c]const u8 = &useable_validation.layerName;
|
||||
if (std.mem.eql(u8, std.mem.sliceTo(want_layer, 0), std.mem.sliceTo(layer_name, 0))) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
std.debug.print("WARNING: Compiled in debug mode, but wanted validation layer {s} not found.\n", .{want_layer});
|
||||
std.debug.print("NOTE: Validation layer will be removed from the wanted validation layers\n", .{});
|
||||
} else {
|
||||
try newLayers.append(want_layer);
|
||||
}
|
||||
}
|
||||
|
||||
const app_info: c.VkApplicationInfo = .{
|
||||
.sType = c.VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.pApplicationName = "sideros",
|
||||
.applicationVersion = c.VK_MAKE_VERSION(1, 0, 0),
|
||||
.engineVersion = c.VK_MAKE_VERSION(1, 0, 0),
|
||||
.pEngineName = "sideros",
|
||||
.apiVersion = c.VK_MAKE_VERSION(1, 3, 0),
|
||||
};
|
||||
|
||||
const instance_info: c.VkInstanceCreateInfo = .{
|
||||
.sType = c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
.pApplicationInfo = &app_info,
|
||||
.enabledExtensionCount = @intCast(extensions.len),
|
||||
.flags = c.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR,
|
||||
.ppEnabledExtensionNames = @ptrCast(extensions[0..]),
|
||||
.enabledLayerCount = @intCast(newLayers.items.len),
|
||||
.ppEnabledLayerNames = newLayers.items.ptr,
|
||||
};
|
||||
|
||||
try mapError(c.vkCreateInstance(&instance_info, null, handle));
|
||||
}
|
||||
|
||||
fn vulkan_init_surface(instance: c.VkInstance, layer: *anyopaque, handle: *c.VkSurfaceKHR) !void {
|
||||
const create_info: c.VkMacOSSurfaceCreateInfoMVK = .{
|
||||
.sType = c.VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK,
|
||||
.pView = layer,
|
||||
};
|
||||
try mapError(c.vkCreateMacOSSurfaceMVK(instance, &create_info, null, handle));
|
||||
}
|
||||
|
||||
fn vulkan_init(allocator: std.mem.Allocator, layer: *anyopaque) !sideros.GameInit {
|
||||
var gameInit: sideros.GameInit = undefined;
|
||||
|
||||
try vulkan_init_instance(allocator, &gameInit.instance);
|
||||
try vulkan_init_surface(@ptrCast(gameInit.instance), layer, &gameInit.surface);
|
||||
|
||||
return gameInit;
|
||||
}
|
||||
|
||||
// TODO: actually clean up these
|
||||
fn vulkan_cleanup(gameInit: sideros.GameInit) void {
|
||||
_ = gameInit;
|
||||
//c.vkDestroySurfaceKHR(gameInit.instance, gameInit.surface, null);
|
||||
//c.vkDestroyInstance(gameInit.instance, null);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
create_window();
|
||||
const layer = get_metal_layer();
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer if (gpa.deinit() != .ok) @panic("Memory leaked");
|
||||
|
||||
const gameInit = try vulkan_init(allocator, layer);
|
||||
defer vulkan_cleanup(gameInit);
|
||||
|
||||
sideros.sideros_init(gameInit);
|
||||
|
||||
while (!is_window_closed()) {
|
||||
poll_cocoa_events();
|
||||
}
|
||||
}
|
||||
87
src/window.m
Normal file
87
src/window.m
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#import <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
static NSWindow *window = nil;
|
||||
static bool window_closed = false;
|
||||
|
||||
@interface WindowDelegate : NSObject <NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@implementation WindowDelegate
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
window_closed = true;
|
||||
}
|
||||
@end
|
||||
|
||||
static WindowDelegate *window_delegate = nil;
|
||||
|
||||
@interface MetalView : NSView
|
||||
@end
|
||||
|
||||
@implementation MetalView
|
||||
+ (Class)layerClass {
|
||||
return [CAMetalLayer class];
|
||||
}
|
||||
- (instancetype)initWithFrame:(NSRect)frame {
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
self.wantsLayer = YES;
|
||||
self.layer = [CAMetalLayer layer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
static MetalView *metal_view = nil;
|
||||
|
||||
static void initialize_app(void) {
|
||||
static BOOL initialized = NO;
|
||||
if (!initialized) {
|
||||
[NSApplication sharedApplication];
|
||||
initialized = YES;
|
||||
}
|
||||
}
|
||||
|
||||
void create_window(void) {
|
||||
initialize_app();
|
||||
window_closed = false;
|
||||
if (window != nil) {
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSRect frame = NSMakeRect(100, 100, 800, 600);
|
||||
|
||||
window = [[NSWindow alloc]
|
||||
initWithContentRect:frame
|
||||
styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskResizable)
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
[window setTitle:@"Sideros"];
|
||||
|
||||
metal_view = [[MetalView alloc] initWithFrame:frame];
|
||||
[window setContentView:metal_view];
|
||||
NSLog(@"Layer class: %@", NSStringFromClass([metal_view.layer class]));
|
||||
|
||||
window_delegate = [[WindowDelegate alloc] init];
|
||||
[window setDelegate:window_delegate];
|
||||
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
[window orderFrontRegardless];
|
||||
}
|
||||
|
||||
void poll_cocoa_events(void) {
|
||||
@autoreleasepool {
|
||||
NSEvent *event;
|
||||
while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:[NSDate distantPast]
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES])) {
|
||||
[NSApp sendEvent:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_window_closed(void) { return window_closed; }
|
||||
void *get_metal_layer(void) { return (__bridge void *)metal_view.layer; }
|
||||
Loading…
Add table
Add a link
Reference in a new issue