v/renderer/metal/metal.cpp
2026-04-28 19:46:32 +02:00

217 lines
7.4 KiB
C++

//
// Created by Vicente Ferrari Smith on 26.02.26.
//
#include "init.h"
#include "../graphics.h"
#include <print>
#include <iostream>
#include <fstream>
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3native.h>
#include <objc/message.h>
#include <objc/objc.h>
#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<size_t>(file.tellg()));
file.seekg(0, std::ios::beg);
file.read(src.data(), static_cast<std::streamsize>(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();
}