Skip to content

Instantly share code, notes, and snippets.

@xenobrain
Last active June 28, 2023 14:07
Show Gist options
  • Save xenobrain/00e27d2ef03b9cce274f7e3b9a5dee2f to your computer and use it in GitHub Desktop.
Save xenobrain/00e27d2ef03b9cce274f7e3b9a5dee2f to your computer and use it in GitHub Desktop.
Cocoa Window with Metal layer in pure C++
cmake_minimum_required(VERSION 3.25)
project(CocoaWindow)
set(CMAKE_CXX_STANDARD 20)
# Install CPM ##########################################################################################################
set(CPM_VERSION 0.38.1)
set(CPM_SOURCE_CACHE ${CMAKE_SOURCE_DIR}/libraries)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION}))
file(DOWNLOAD https://github.com/TheLartians/CPM.cmake/releases/download/v${CPM_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION})
endif()
include(${CPM_DOWNLOAD_LOCATION})
# Build Game Executable ###############################################################################################
add_executable(CocoaWindow main.cpp)
target_link_libraries(CocoaWindow PRIVATE "-framework System -framework Cocoa")
CPMAddPackage("gh:KhronosGroup/Vulkan-Headers@1.3.255")
CPMAddPackage("gh:bkaradzic/metal-cpp#metal-cpp_macOS13.3_iOS16.4")
target_include_directories(CocoaWindow PRIVATE ${metal-cpp_SOURCE_DIR})
target_include_directories(CocoaWindow PRIVATE ${Vulkan-Headers_SOURCE_DIR}/include)
target_compile_definitions(CocoaWindow PRIVATE VK_NO_PROTOTYPES VK_USE_PLATFORM_METAL_EXT)
target_compile_options(CocoaWindow PRIVATE -fno-rtti)
target_link_options(CocoaWindow PRIVATE -e _entry -nostartfiles -nodefaultlibs)
#include <objc/objc-runtime.h>
#include <objc/NSObjCRuntime.h>
#include <CoreGraphics/CoreGraphics.h>
auto static constexpr WINDOW_TITLE = "Prototype";
auto static constexpr WINDOW_WIDTH = 1280;
auto static constexpr WINDOW_HEIGHT = 720;
template<typename R, typename... Args> auto send(id obj, const char* selector, Args... args) {
return reinterpret_cast<R(*)(id, SEL, Args...)>(objc_msgSend)(obj, sel_getUid(selector), args...);
}
template<typename T> auto get_class(const char* className) { return reinterpret_cast<T>(objc_getClass(className)); }
extern id NSApp;
extern id NSDefaultRunLoopMode;
id static window;
id static metalLayer;
objc_class* windowDelegate;
auto static running = true;
namespace xc::platform {
auto windowWillClose(id self, SEL _cmd, id sender) -> void {
send<void>(NSApp, "terminate:", nil);
running = false;
}
auto create_metal_layer() -> void {
metalLayer = send<id>(get_class<id>("CAMetalLayer"), "new");
send<void>(metalLayer, "setDevice:", send<id>(get_class<id>("MTLCreateSystemDefaultDevice"), "new"));
send<void>(metalLayer, "setPixelFormat:", 70); // MTLPixelFormatBGRA8Unorm
send<void>(metalLayer, "setFrame:", CGRectMake(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT));
auto contentView = send<id>(window, "contentView");
send<void>(contentView, "setWantsLayer:", YES);
send<void>(contentView, "setLayer:", metalLayer);
}
auto initialize() -> bool {
NSApp = send<id>(get_class<id>("NSApplication"), "sharedApplication");
send<void>(NSApp, "setActivationPolicy:", 0);
send<void>(NSApp, "activateIgnoringOtherApps:", YES);
windowDelegate = objc_allocateClassPair(objc_getClass("NSResponder"), "WindowDelegate", 0);
class_addMethod(windowDelegate, sel_registerName("windowWillClose:"), reinterpret_cast<IMP>(windowWillClose), "v@:@");
objc_registerClassPair(windowDelegate);
window = send<id>(get_class<id>("NSWindow"), "alloc");
send<void>(window, "initWithContentRect:styleMask:backing:defer:", CGRectMake(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT), 15, 2, NO);
send<void>(window, "setTitle:", send<id>(get_class<id>("NSString"), "stringWithUTF8String:", WINDOW_TITLE));
send<void>(window, "center");
send<void>(window, "setDelegate:", send<id>(get_class<id>("WindowDelegate"), "new"));
send<void>(window, "makeKeyAndOrderFront:", nil);
create_metal_layer();
return true;
}
auto uninitialize() -> void {
objc_disposeClassPair(windowDelegate);
send<void>(metalLayer, "release");
send<void>(window, "release");
}
auto tick() -> void {
while (running) {
send<void>(send<id>(window, "contentView"), "setNeedsDisplay:", YES);
id event = send<id>(NSApp, "nextEventMatchingMask:untilDate:inMode:dequeue:", ULONG_MAX, nil, NSDefaultRunLoopMode, YES);
send<void>(NSApp, "sendEvent:", event);
}
}
}
extern "C" auto entry() -> void {
xc::platform::initialize();
while (running) {
xc::platform::tick();
}
xc::platform::uninitialize();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment