// // Created by Vicente Ferrari Smith on 26.02.26. // #define NS_PRIVATE_IMPLEMENTATION #define MTL_PRIVATE_IMPLEMENTATION #define MTK_PRIVATE_IMPLEMENTATION #define CA_PRIVATE_IMPLEMENTATION #include #include #include #include "metal.h" #include "../graphics.h" #include "../texture.h" #include #include #include #include #define GLFW_EXPOSE_NATIVE_COCOA #include #include #include extern int32_t window_width; extern int32_t window_height; Device metal_device{}; MTL::CommandQueue *queue{}; CA::MetalLayer *metal_layer{}; CA::MetalDrawable *metal_drawable{}; Renderer renderer; void upload_texture( const int w, const int h, const void *pixels, Texture *texture) { MTL::TextureDescriptor *td = MTL::TextureDescriptor::alloc()->init(); td->setPixelFormat(MTL::PixelFormatRGBA8Unorm); td->setWidth(w); td->setHeight(h); td->setStorageMode(MTL::StorageModeShared); td->setUsage(MTL::TextureUsageShaderRead); texture->p_texture->texture = metal_device.device->newTexture(td); MTL::Region region = MTL::Region(0, 0, 0, w, h, 1); NS::UInteger bytesPerRow = 4 * w; texture->p_texture->texture->replaceRegion(region, 0, pixels, bytesPerRow); td->release(); } void create_metal_layer(GLFWwindow *window) { void *ns_window = glfwGetCocoaWindow(window); if (!ns_window) { throw std::runtime_error("Failed to get Cocoa window from GLFWwindow"); } SEL contentViewSel = sel_registerName("contentView"); id content_view = ((id (*)(id, SEL))objc_msgSend)((id)ns_window, contentViewSel); SEL setWantsLayerSel = sel_registerName("setWantsLayer:"); ((void (*)(id, SEL, bool))objc_msgSend)(content_view, setWantsLayerSel, true); metal_layer = CA::MetalLayer::layer()->retain(); SEL setLayerSel = sel_registerName("setLayer:"); ((void (*)(id, SEL, id))objc_msgSend)(content_view, setLayerSel, (id)metal_layer); metal_layer->retain(); metal_layer->setDevice(metal_device.device); metal_layer->setPixelFormat(MTL::PixelFormatRGBA16Float); metal_layer->setFramebufferOnly(true); metal_layer->setDrawableSize(CGSizeMake(800, 600)); CGColorSpaceRef p3Space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); metal_layer->setColorspace(p3Space); } void load_metal_shader(const std::string &shader_path, const std::string &vertex_fn_name, const std::string &fragment_fn_name, MTL::Function **vertex_shader, MTL::Function **fragment_shader) { NS::Error *error = nullptr; MTL::Library *library = nullptr; auto ends_with = [](const std::string& s, const char* suf) -> bool { const size_t n = std::strlen(suf); return s.size() >= n && s.compare(s.size() - n, n, suf) == 0; }; if (ends_with(shader_path, ".metal")) { // Compile from source at runtime std::ifstream file(shader_path, std::ios::in | std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to open .metal source file"); } std::string src; file.seekg(0, std::ios::end); src.resize(static_cast(file.tellg())); file.seekg(0, std::ios::beg); file.read(src.data(), static_cast(src.size())); file.close(); NS::String* source = NS::String::string(src.c_str(), NS::UTF8StringEncoding); MTL::CompileOptions* opts = MTL::CompileOptions::alloc()->init(); library = metal_device.device->newLibrary(source, opts, &error); opts->release(); } else { // Load a precompiled metallib from file path NS::String *nsPath = NS::String::string(shader_path.c_str(), NS::UTF8StringEncoding); library = metal_device.device->newLibrary(nsPath, &error); } if (error || library == nullptr) { if (error) { // Extract the actual compiler error message const char* errorMessage = error->localizedDescription()->utf8String(); std::string detailedError = "Metal Library Error: "; detailedError += errorMessage; // It is good practice to release the error object if it exists error->release(); throw std::runtime_error(detailedError); } throw std::runtime_error("Failed to create Metal library (Unknown error)"); } NS::String *vname = NS::String::string(vertex_fn_name.c_str(), NS::UTF8StringEncoding); NS::String *fname = NS::String::string(fragment_fn_name.c_str(), NS::UTF8StringEncoding); *vertex_shader = library->newFunction(vname); *fragment_shader = library->newFunction(fname); if (*vertex_shader == nullptr || *fragment_shader == nullptr) { throw std::runtime_error("Failed to create Metal shader functions"); } library->release(); } void create_command_queue() { queue = metal_device.device->newCommandQueue(); } void create_device() { metal_device.device = MTL::CreateSystemDefaultDevice(); } void platform_graphics_init(GLFWwindow *window) { std::println("wow, we are on macos!! crazy!!"); auto slangTargets{ std::to_array({ { .format = SLANG_METAL, .profile = slangGlobalSession->findProfile("metallib_2_4"), } })}; // auto slangOptions{ std::to_array({ { // slang::CompilerOptionName::EmitSpirvDirectly, // {slang::CompilerOptionValueKind::Int, 1} // } })}; const char *search_paths[] = {"shaders"}; slang::SessionDesc slangSessionDesc{ .targets = slangTargets.data(), .targetCount = SlangInt(slangTargets.size()), .searchPaths = search_paths, .searchPathCount = 1, // .defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR, }; slangGlobalSession->createSession(slangSessionDesc, slangSession.writeRef()); create_device(); create_metal_layer(window); create_command_queue(); renderer = Renderer(window); } void graphics_deinit() { queue->release(); metal_layer->release(); metal_device.device->release(); } void begin_frame() { renderer.begin_frame(); } void end_frame(GLFWwindow *window) { auto pPool = NS::AutoreleasePool::alloc()->init(); metal_drawable = metal_layer->nextDrawable(); renderer.end_frame(window); pPool->release(); } void submit_sprite(glm::vec2 pos, const sprite_t &sprite) { renderer.submit_sprite({pos.x, pos.y}, sprite); }