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 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 #[[ #shaders
set(SHADER_DIR ${CMAKE_SOURCE_DIR}/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 t = 0;
uint64_t accumulator = 0; uint64_t accumulator = 0;
uint64_t dt = 0;
uint64_t dt = glfwGetTimerFrequency() / 60; // 1/60 s uint64_t current_time = 0;
uint64_t current_time = glfwGetTimerValue();
sprite_t sprite { sprite_t sprite {
.origin = {0.5, 0.5}, .origin = {0.5, 0.5},
@ -44,6 +42,9 @@ int init() {
if (!glfwInit()) if (!glfwInit())
return -1; return -1;
dt = glfwGetTimerFrequency() / 60;
current_time = glfwGetTimerValue();
std::println("Hello, Sailor!"); std::println("Hello, Sailor!");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
@ -87,7 +88,6 @@ void main_loop(void *data) {
begin_frame(); begin_frame();
double f = (t / (double) glfwGetTimerFrequency()); double f = (t / (double) glfwGetTimerFrequency());
std::println("{}", f);
for (int x = sprite.scale.x / 2; x < window_width; x += sprite.scale.x) { 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); submit_sprite({x + 30 * cos(f), 200 + 30 * sin(f)}, sprite);

View File

@ -36,7 +36,7 @@ void graphics_deinit();
void begin_frame(); void begin_frame();
void end_frame(GLFWwindow *window); 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 submit_sprite(glm::vec2 pos, const sprite_t &sprite);
void upload_texture( void upload_texture(

View File

@ -39,6 +39,8 @@ void upload_texture(
const void *pixels, const void *pixels,
Texture *texture) Texture *texture)
{ {
texture->p_texture = new PlatformTexture{};
MTL::TextureDescriptor *td = MTL::TextureDescriptor::alloc()->init(); MTL::TextureDescriptor *td = MTL::TextureDescriptor::alloc()->init();
td->setPixelFormat(MTL::PixelFormatRGBA8Unorm); td->setPixelFormat(MTL::PixelFormatRGBA8Unorm);
td->setWidth(w); td->setWidth(w);
@ -73,13 +75,15 @@ void create_metal_layer(GLFWwindow *window) {
SEL setLayerSel = sel_registerName("setLayer:"); SEL setLayerSel = sel_registerName("setLayer:");
((void (*)(id, SEL, id))objc_msgSend)(content_view, setLayerSel, (id)metal_layer); ((void (*)(id, SEL, id))objc_msgSend)(content_view, setLayerSel, (id)metal_layer);
metal_layer->retain();
metal_layer->setDevice(metal_device.device); metal_layer->setDevice(metal_device.device);
metal_layer->setPixelFormat(MTL::PixelFormatRGBA16Float); metal_layer->setPixelFormat(MTL::PixelFormatRGBA16Float);
metal_layer->setFramebufferOnly(true); 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); CGColorSpaceRef p3Space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
metal_layer->setColorspace(p3Space); metal_layer->setColorspace(p3Space);
CGColorSpaceRelease(p3Space);
} }
void load_metal_shader(const std::string &shader_path, void load_metal_shader(const std::string &shader_path,
@ -201,6 +205,10 @@ void end_frame(GLFWwindow *window) {
pPool->release(); 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) { void submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
renderer.submit_sprite({pos.x, pos.y}, sprite); renderer.submit_sprite({pos.x, pos.y}, sprite);
} }

View File

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

View File

@ -65,7 +65,7 @@ void Renderer::submit_quad(simd::float2 pos, simd::float2 scale) {
RenderCommand cmd {}; RenderCommand cmd {};
cmd.pipeline = PipelineType::ColoredQuad; cmd.pipeline = PipelineType::ColoredQuad;
cmd.key = { cmd.key = {
(uint16_t) pos.y, (uint16_t) pos[1],
0, 0,
(uint8_t) PipelineType::ColoredQuad (uint8_t) PipelineType::ColoredQuad
}; };
@ -83,7 +83,7 @@ void Renderer::submit_sprite(simd::float2 pos, const sprite_t &sprite) {
RenderCommand cmd {}; RenderCommand cmd {};
cmd.pipeline = PipelineType::TexturedQuad; cmd.pipeline = PipelineType::TexturedQuad;
cmd.key = { cmd.key = {
(uint16_t) pos.y, (uint16_t) pos[1],
0, 0,
(uint8_t) PipelineType::TexturedQuad (uint8_t) PipelineType::TexturedQuad
}; };
@ -247,6 +247,14 @@ void Renderer::create_render_pipeline() {
} }
renderPipelineDescriptor->release(); renderPipelineDescriptor->release();
vd->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) { 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) { 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<vertex_p2_s2_uv2_c4_a1> vertices;
std::vector<DrawGroup> groups;
for (auto& cmd : commands) { for (auto& cmd : commands) {
switch (cmd.pipeline) { 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: { case PipelineType::TexturedQuad: {
const auto &q = cmd.textured_quad; 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 }; if (groups.empty() || groups.back().texture != q.texture)
vertex_p2_s2_uv2_c4_a1 vTR = { q.pos, q.scale, {q.uv1.x, q.uv0.y}, q.colour, 1.0 }; groups.push_back({q.texture, (NS::UInteger)vertices.size(), 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};
vertices.push_back(vTL); simd::float2 uv_tr = {q.uv1[0], q.uv0[1]};
vertices.push_back(vBL); simd::float2 uv_bl = {q.uv0[0], q.uv1[1]};
vertices.push_back(vTR);
vertices.push_back(vTR);
vertices.push_back(vBL);
vertices.push_back(vBR);
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; break;
} }
default: default:
@ -314,33 +310,26 @@ void Renderer::encode_render_command(GLFWwindow *window, MTL::RenderCommandEncod
} }
} }
if (vertices.empty()) return;
const Frame &frame = frames[current_frame]; const Frame &frame = frames[current_frame];
memcpy(frame.vertex_buffer->contents(), vertices.data(), vertices.size() * sizeof(vertex_p2_s2_uv2_c4_a1)); 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; int width, height;
glfwGetFramebufferSize(window, &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); auto tm = simd::transpose(ortho);
memcpy(frame.uniform_buffer->contents(), &tm, sizeof(ortho)); memcpy(frame.uniform_buffer->contents(), &tm, sizeof(ortho));
render_command_encoder->setRenderPipelineState(textured_quad_pipeline); render_command_encoder->setRenderPipelineState(textured_quad_pipeline);
render_command_encoder->setVertexBuffer(frame.vertex_buffer, 0, 0); render_command_encoder->setVertexBuffer(frame.vertex_buffer, 0, 0);
render_command_encoder->setVertexBuffer(frame.uniform_buffer, 0, 1); render_command_encoder->setVertexBuffer(frame.uniform_buffer, 0, 1);
render_command_encoder->setFragmentSamplerState(sampler, 0); render_command_encoder->setFragmentSamplerState(sampler_state, 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);
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) { 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->presentDrawable(metal_drawable);
command_buffer->commit(); command_buffer->commit();
command_buffer->waitUntilCompleted();
current_frame = (current_frame + 1) % kMaxFramesInFlight; current_frame = (current_frame + 1) % kMaxFramesInFlight;

View File

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

View File

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