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/[email protected]")
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 <CoreGraphics/CoreGraphics.h>
auto static constexpr WINDOW_TITLE = "Prototype";
auto static constexpr WINDOW_WIDTH = 1280;
auto static constexpr WINDOW_HEIGHT = 720;
template <typename... Args> auto send(id obj, char const* selector, Args... args) {
return reinterpret_cast<id (*)(id, SEL, Args...)>(objc_msgSend)(obj, sel_registerName(selector), args...);
}
template <typename... Args> auto send(char const* cls, char const* selector, Args... args) {
return send(reinterpret_cast<id>(objc_getClass(cls)), selector, args...);
}
extern id NSDefaultRunLoopMode;
auto static running = true;
class Window {
public:
auto run() -> int {
send(send("NSApplication","sharedApplication"), "setActivationPolicy:", 0);
auto windowDelegate = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0);
class_addMethod(windowDelegate, sel_registerName("windowWillClose:"), reinterpret_cast<IMP>(windowWillClose), "v@:@");
objc_registerClassPair(windowDelegate);
_window = send("NSWindow", "alloc");
send(_window, "initWithContentRect:styleMask:backing:defer:", CGRectMake(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT), 15, 2, NO);
send(_window, "setTitle:", send("NSString", "stringWithUTF8String:", WINDOW_TITLE));
send(_window, "center");
send(_window, "setDelegate:", send("WindowDelegate", "new"));
send(_window, "makeKeyAndOrderFront:", nil);
send(send("NSApplication","sharedApplication"), "activateIgnoringOtherApps:", YES);
createMetalLayer();
while (running) {
send(send(_window, "contentView"), "setNeedsDisplay:", YES);
id event = send(send("NSApplication","sharedApplication"), "nextEventMatchingMask:untilDate:inMode:dequeue:", ULONG_MAX, nil, NSDefaultRunLoopMode, YES);
send(send("NSApplication","sharedApplication"), "sendEvent:", event);
}
objc_disposeClassPair(windowDelegate);
send(_metalLayer, "release");
send(_window, "release");
return 0;
}
private:
id _window;
id _metalLayer;
auto static windowWillClose(id self, SEL _cmd, id sender) -> void {
send(send("NSApplication", "sharedApplication"),"terminate:", nil);
running = false;
}
auto createMetalLayer() -> void {
_metalLayer = send("CAMetalLayer", "new");
send(_metalLayer, "setDevice:", send("MTLCreateSystemDefaultDevice", "new"));
send(_metalLayer, "setPixelFormat:", 70); // MTLPixelFormatBGRA8Unorm
send(_metalLayer, "setFrame:", CGRectMake(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT));
auto contentView = send(_window, "contentView");
send(contentView, "setWantsLayer:", YES);
send(contentView, "setLayer:", _metalLayer);
}
};
extern "C" auto entry() -> void {
Window{}.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment