diff --git a/.DS_Store b/.DS_Store index 707a91f..4a56286 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97b7afe --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Specify filepatterns you want git to ignore. +.DS_Store +build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d581385..3566ec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" "$/shaders" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/assets" "$/assets" + COMMENT "Copying shaders and assets to build directory" + ) + #[[ #shaders set(SHADER_DIR ${CMAKE_SOURCE_DIR}/shaders) diff --git a/main.cpp b/main.cpp index fd3d6eb..90c9195 100644 --- a/main.cpp +++ b/main.cpp @@ -22,12 +22,10 @@ GLFWwindow *window; int32_t window_width = 640; 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 t = 0; +uint64_t accumulator = 0; +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); diff --git a/renderer/graphics.h b/renderer/graphics.h index ce337a2..0bca3c3 100644 --- a/renderer/graphics.h +++ b/renderer/graphics.h @@ -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( diff --git a/renderer/metal/metal.cpp b/renderer/metal/metal.cpp index 34decbc..bb3acdd 100644 --- a/renderer/metal/metal.cpp +++ b/renderer/metal/metal.cpp @@ -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); } diff --git a/renderer/metal/metal.h b/renderer/metal/metal.h index f1d1634..8eb1cd6 100644 --- a/renderer/metal/metal.h +++ b/renderer/metal/metal.h @@ -7,7 +7,7 @@ #include #define GLFW_EXPOSE_NATIVE_COCOA -#import +#include #include #include diff --git a/renderer/metal/renderer.cpp b/renderer/metal/renderer.cpp index 5424707..bdca99f 100644 --- a/renderer/metal/renderer.cpp +++ b/renderer/metal/renderer.cpp @@ -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 vertices; + std::vector 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; diff --git a/renderer/metal/renderer.h b/renderer/metal/renderer.h index cfe011c..fd692fb 100644 --- a/renderer/metal/renderer.h +++ b/renderer/metal/renderer.h @@ -114,6 +114,7 @@ struct Renderer { uint32_t nextTextureSlot = 0; + MTL::SamplerState *sampler_state{}; MTL::CommandBuffer *command_buffer{}; struct Frame { diff --git a/renderer/texture.cpp b/renderer/texture.cpp index 10e30d0..e3e51d0 100644 --- a/renderer/texture.cpp +++ b/renderer/texture.cpp @@ -2,6 +2,7 @@ // Created by Vicente Ferrari Smith on 01.03.26. // +#include #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;