// // Created by Vicente Ferrari Smith on 26.02.26. // #include "init.h" #include "../graphics.h" #include #include #include #include #define GLFW_EXPOSE_NATIVE_COCOA #include #include #include #include "vertex_data.h" Device metal_device{}; MTL::Buffer* triangle_vertex_buffer{}; MTL::CommandQueue *queue{}; CA::MetalLayer *metal_layer{}; MTL::RenderPipelineState *pipeline_state{}; CA::MetalDrawable *metal_drawable{}; MTL::CommandBuffer* metal_command_buffer{}; MTL::Function *vertex_shader{}; MTL::Function *fragment_shader{}; void create_window(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 encode_render_command(MTL::RenderCommandEncoder *renderCommandEncoder) { renderCommandEncoder->setRenderPipelineState(pipeline_state); renderCommandEncoder->setVertexBuffer(triangle_vertex_buffer, 0, 0); MTL::PrimitiveType typeTriangle = MTL::PrimitiveTypeTriangle; NS::UInteger vertexStart = 0; NS::UInteger vertexCount = 6; renderCommandEncoder->drawPrimitives(typeTriangle, vertexStart, vertexCount); } void send_render_command() { metal_command_buffer = queue->commandBuffer(); MTL::RenderPassDescriptor* renderPassDescriptor = MTL::RenderPassDescriptor::alloc()->init(); MTL::RenderPassColorAttachmentDescriptor *cd = renderPassDescriptor->colorAttachments()->object(0); cd->setTexture(metal_drawable->texture()); cd->setLoadAction(MTL::LoadActionClear); cd->setClearColor(MTL::ClearColor( 100.0f / 255.0f, 149.0f / 255.0f, 237.0f / 255.0f, 1.0 )); cd->setStoreAction(MTL::StoreActionStore); MTL::RenderCommandEncoder* renderCommandEncoder = metal_command_buffer->renderCommandEncoder(renderPassDescriptor); encode_render_command(renderCommandEncoder); renderCommandEncoder->endEncoding(); metal_command_buffer->presentDrawable(metal_drawable); metal_command_buffer->commit(); metal_command_buffer->waitUntilCompleted(); renderPassDescriptor->release(); } void LoadMetalShader(const std::string &shader_path, const std::string &vertex_fn_name, const std::string &fragment_fn_name) { 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_render_pipeline() { LoadMetalShader("shaders/shader.metal", "vertex_main", "fragment_main"); MTL::RenderPipelineDescriptor* renderPipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init(); renderPipelineDescriptor->setLabel(NS::String::string("Triangle Rendering Pipeline", NS::ASCIIStringEncoding)); renderPipelineDescriptor->setVertexFunction(vertex_shader); renderPipelineDescriptor->setFragmentFunction(fragment_shader); assert(renderPipelineDescriptor); const MTL::PixelFormat pixel_format = metal_layer->pixelFormat(); renderPipelineDescriptor->colorAttachments()->object(0)->setPixelFormat(pixel_format); NS::Error* error; pipeline_state = metal_device.device->newRenderPipelineState(renderPipelineDescriptor, &error); renderPipelineDescriptor->release(); } void create_command_queue() { queue = metal_device.device->newCommandQueue(); } void create_triangle() { VertexData square_vertices[] = { {{-0.5, -0.5}, {1.0, 0.0, 0.0, 1.0}}, {{0.5, -0.5}, {0.0, 1.0, 0.0, 1.0}}, {{0.5, 0.5}, {0.0, 0.0, 1.0, 1.0}}, {{0.5, 0.5}, {0.0, 0.0, 1.0, 1.0}}, {{-0.5, 0.5}, {0.0, 1.0, 0.0, 1.0}}, {{-0.5, -0.5}, {1.0, 0.0, 0.0, 1.0}}, }; triangle_vertex_buffer = metal_device.device->newBuffer(&square_vertices, sizeof(square_vertices), MTL::ResourceStorageModeShared); } void graphics_init(GLFWwindow *window) { std::println("wow, we are on macos!! crazy!!"); create_device(); create_window(window); create_triangle(); create_command_queue(); create_render_pipeline(); } void graphics_deinit() { } void begin_frame() { } void end_frame() { auto pPool = NS::AutoreleasePool::alloc()->init(); metal_drawable = metal_layer->nextDrawable(); send_render_command(); pPool->release(); } void create_device() { metal_device.device = MTL::CreateSystemDefaultDevice(); }