207 lines
6.6 KiB
C++
207 lines
6.6 KiB
C++
//
|
|
// 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 <Foundation/Foundation.hpp>
|
|
#include <Metal/Metal.hpp>
|
|
#include <QuartzCore/QuartzCore.hpp>
|
|
|
|
#include "metal.h"
|
|
#include "../graphics.h"
|
|
#include "../texture.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>
|
|
|
|
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<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_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<slang::TargetDesc>({ {
|
|
.format = SLANG_METAL,
|
|
.profile = slangGlobalSession->findProfile("metallib_2_4"),
|
|
} })};
|
|
// auto slangOptions{ std::to_array<slang::CompilerOptionEntry>({ {
|
|
// 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);
|
|
}
|