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);
|
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 => {
|
else => {
|
||||||
std.debug.panic("Compilation not implemented for OS: {any}\n", .{target.result.os.tag});
|
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