This commit is contained in:
Vicente Ferrari Smith 2026-05-16 17:20:07 +02:00
parent 6883d103ff
commit e43edc67bd
10 changed files with 74 additions and 62 deletions

BIN
.DS_Store vendored

Binary file not shown.

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Specify filepatterns you want git to ignore.
.DS_Store
build

View File

@ -122,6 +122,14 @@ elseif (APPLE)
renderer/metal/renderer.h
)
add_custom_command(TARGET v POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/shaders" "$<TARGET_FILE_DIR:v>/shaders"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/assets" "$<TARGET_FILE_DIR:v>/assets"
COMMENT "Copying shaders and assets to build directory"
)
#[[ #shaders
set(SHADER_DIR ${CMAKE_SOURCE_DIR}/shaders)

View File

@ -24,10 +24,8 @@ int32_t window_height = 480;
uint64_t t = 0;
uint64_t accumulator = 0;
uint64_t dt = glfwGetTimerFrequency() / 60; // 1/60 s
uint64_t current_time = glfwGetTimerValue();
uint64_t dt = 0;
uint64_t current_time = 0;
sprite_t sprite {
.origin = {0.5, 0.5},
@ -44,6 +42,9 @@ int init() {
if (!glfwInit())
return -1;
dt = glfwGetTimerFrequency() / 60;
current_time = glfwGetTimerValue();
std::println("Hello, Sailor!");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
@ -87,7 +88,6 @@ void main_loop(void *data) {
begin_frame();
double f = (t / (double) glfwGetTimerFrequency());
std::println("{}", f);
for (int x = sprite.scale.x / 2; x < window_width; x += sprite.scale.x) {
submit_sprite({x + 30 * cos(f), 200 + 30 * sin(f)}, sprite);

View File

@ -36,7 +36,7 @@ void graphics_deinit();
void begin_frame();
void end_frame(GLFWwindow *window);
void submit_quad();
void submit_quad(glm::vec2 pos, glm::vec2 scale);
void submit_sprite(glm::vec2 pos, const sprite_t &sprite);
void upload_texture(

View File

@ -39,6 +39,8 @@ void upload_texture(
const void *pixels,
Texture *texture)
{
texture->p_texture = new PlatformTexture{};
MTL::TextureDescriptor *td = MTL::TextureDescriptor::alloc()->init();
td->setPixelFormat(MTL::PixelFormatRGBA8Unorm);
td->setWidth(w);
@ -73,13 +75,15 @@ void create_metal_layer(GLFWwindow *window) {
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));
int fb_w, fb_h;
glfwGetFramebufferSize(window, &fb_w, &fb_h);
metal_layer->setDrawableSize(CGSizeMake(fb_w, fb_h));
CGColorSpaceRef p3Space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
metal_layer->setColorspace(p3Space);
CGColorSpaceRelease(p3Space);
}
void load_metal_shader(const std::string &shader_path,
@ -201,6 +205,10 @@ void end_frame(GLFWwindow *window) {
pPool->release();
}
void submit_quad(glm::vec2 pos, glm::vec2 scale) {
renderer.submit_quad({pos.x, pos.y}, {scale.x, scale.y});
}
void submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
renderer.submit_sprite({pos.x, pos.y}, sprite);
}

View File

@ -7,7 +7,7 @@
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_COCOA
#import <GLFW/glfw3native.h>
#include <GLFW/glfw3native.h>
#include <Metal/Metal.hpp>
#include <QuartzCore/CAMetalLayer.hpp>

View File

@ -65,7 +65,7 @@ void Renderer::submit_quad(simd::float2 pos, simd::float2 scale) {
RenderCommand cmd {};
cmd.pipeline = PipelineType::ColoredQuad;
cmd.key = {
(uint16_t) pos.y,
(uint16_t) pos[1],
0,
(uint8_t) PipelineType::ColoredQuad
};
@ -83,7 +83,7 @@ void Renderer::submit_sprite(simd::float2 pos, const sprite_t &sprite) {
RenderCommand cmd {};
cmd.pipeline = PipelineType::TexturedQuad;
cmd.key = {
(uint16_t) pos.y,
(uint16_t) pos[1],
0,
(uint8_t) PipelineType::TexturedQuad
};
@ -247,6 +247,14 @@ void Renderer::create_render_pipeline() {
}
renderPipelineDescriptor->release();
vd->release();
vertex_shader->release();
fragment_shader->release();
MTL::SamplerDescriptor *sampler_desc = MTL::SamplerDescriptor::alloc()->init();
sampler_desc->setMinFilter(MTL::SamplerMinMagFilterLinear);
sampler_desc->setMagFilter(MTL::SamplerMinMagFilterLinear);
sampler_state = metal_device.device->newSamplerState(sampler_desc);
sampler_desc->release();
}
float4x4 make_ortho(float left, float right, float bottom, float top, float near, float far) {
@ -267,46 +275,34 @@ float4x4 make_ortho(float left, float right, float bottom, float top, float near
}
void Renderer::encode_render_command(GLFWwindow *window, MTL::RenderCommandEncoder *render_command_encoder) {
struct DrawGroup {
MTL::Texture *texture;
NS::UInteger vertex_start;
NS::UInteger vertex_count;
};
std::vector<vertex_p2_s2_uv2_c4_a1> vertices;
std::vector<DrawGroup> groups;
for (auto& cmd : commands) {
switch (cmd.pipeline) {
case PipelineType::ColoredQuad: {
const auto &q = cmd.colored_quad;
vertex_p2_s2_uv2_c4_a1 vTL = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vTR = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vBL = { q.pos, q.scale, {}, q.colour };
vertex_p2_s2_uv2_c4_a1 vBR = { q.pos, q.scale, {}, q.colour };
vertices.push_back(vTL);
vertices.push_back(vBL);
vertices.push_back(vTR);
vertices.push_back(vTR);
vertices.push_back(vBL);
vertices.push_back(vBR);
break;
}
case PipelineType::TexturedQuad: {
const auto &q = cmd.textured_quad;
if (!q.texture) break;
vertex_p2_s2_uv2_c4_a1 vTL = { q.pos, q.scale, q.uv0, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vTR = { q.pos, q.scale, {q.uv1.x, q.uv0.y}, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vBL = { q.pos, q.scale, {q.uv0.x, q.uv1.y}, q.colour, 1.0 };
vertex_p2_s2_uv2_c4_a1 vBR = { q.pos, q.scale, q.uv1, q.colour, 1.0};
if (groups.empty() || groups.back().texture != q.texture)
groups.push_back({q.texture, (NS::UInteger)vertices.size(), 0});
vertices.push_back(vTL);
vertices.push_back(vBL);
vertices.push_back(vTR);
vertices.push_back(vTR);
vertices.push_back(vBL);
vertices.push_back(vBR);
simd::float2 uv_tr = {q.uv1[0], q.uv0[1]};
simd::float2 uv_bl = {q.uv0[0], q.uv1[1]};
vertices.push_back({ q.pos, q.scale, q.uv0, q.colour, 1.0f });
vertices.push_back({ q.pos, q.scale, uv_bl, q.colour, 1.0f });
vertices.push_back({ q.pos, q.scale, uv_tr, q.colour, 1.0f });
vertices.push_back({ q.pos, q.scale, uv_tr, q.colour, 1.0f });
vertices.push_back({ q.pos, q.scale, uv_bl, q.colour, 1.0f });
vertices.push_back({ q.pos, q.scale, q.uv1, q.colour, 1.0f });
groups.back().vertex_count += 6;
break;
}
default:
@ -314,33 +310,26 @@ void Renderer::encode_render_command(GLFWwindow *window, MTL::RenderCommandEncod
}
}
if (vertices.empty()) return;
const Frame &frame = frames[current_frame];
memcpy(frame.vertex_buffer->contents(), vertices.data(), vertices.size() * sizeof(vertex_p2_s2_uv2_c4_a1));
MTL::SamplerDescriptor* sampler_desc = MTL::SamplerDescriptor::alloc()->init();
sampler_desc->setMinFilter(MTL::SamplerMinMagFilterLinear);
sampler_desc->setMagFilter(MTL::SamplerMinMagFilterLinear);
MTL::SamplerState* sampler = metal_device.device->newSamplerState(sampler_desc);
int width, height;
glfwGetFramebufferSize(window, &width, &height);
simd::float4x4 ortho = make_ortho(0.0, window_width, window_height, 0.0, 1.0, 0.0);
simd::float4x4 ortho = make_ortho(0.0f, (float)width, (float)height, 0.0f, 1.0f, 0.0f);
auto tm = simd::transpose(ortho);
memcpy(frame.uniform_buffer->contents(), &tm, sizeof(ortho));
render_command_encoder->setRenderPipelineState(textured_quad_pipeline);
render_command_encoder->setVertexBuffer(frame.vertex_buffer, 0, 0);
render_command_encoder->setVertexBuffer(frame.uniform_buffer, 0, 1);
render_command_encoder->setFragmentSamplerState(sampler, 0);
MTL::PrimitiveType type_triangle = MTL::PrimitiveTypeTriangle;
NS::UInteger vertexStart = 0;
NS::UInteger vertexCount = vertices.size();
const Texture &texture = texture_manager.textures["assets/boy.png"];
render_command_encoder->setFragmentTexture(texture.p_texture->texture, 0);
render_command_encoder->drawPrimitives(type_triangle, vertexStart, vertexCount);
render_command_encoder->setFragmentSamplerState(sampler_state, 0);
sampler_desc->release();
for (const auto &group : groups) {
render_command_encoder->setFragmentTexture(group.texture, 0);
render_command_encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, group.vertex_start, group.vertex_count);
}
}
void Renderer::send_render_command(GLFWwindow *window) {
@ -371,7 +360,6 @@ void Renderer::send_render_command(GLFWwindow *window) {
command_buffer->presentDrawable(metal_drawable);
command_buffer->commit();
command_buffer->waitUntilCompleted();
current_frame = (current_frame + 1) % kMaxFramesInFlight;

View File

@ -114,6 +114,7 @@ struct Renderer {
uint32_t nextTextureSlot = 0;
MTL::SamplerState *sampler_state{};
MTL::CommandBuffer *command_buffer{};
struct Frame {

View File

@ -2,6 +2,7 @@
// Created by Vicente Ferrari Smith on 01.03.26.
//
#include <print>
#include "texture.h"
#include "graphics.h"
@ -10,11 +11,14 @@ TextureManager::TextureManager() {
}
Texture TextureManager::load(const std::string& path) {
// Dedup: Don't load the same file twice!
// if (path_to_id.contains(path)) return path_to_id[path];
if (textures.contains(path)) return textures[path];
int w, h, ch;
unsigned char* data = stbi_load(path.c_str(), &w, &h, &ch, STBI_rgb_alpha);
if (!data) {
std::println("Failed to load texture: {}", path);
return {};
}
// Tell the renderer to make the GPU version
Texture res;