Renderer.hh
1 #pragma once 2 3 #include "base.hh" 4 5 #include <Metal/Metal.hpp> 6 #include <MetalKit/MetalKit.hpp> 7 #include <simd/simd.h> 8 9 /// Triple buffering 10 static const int MAX_FRAMES_IN_FLIGHT = 3; 11 12 /// Shader source code embedded in the program (REQ. C23 or C++26) 13 static constexpr char SHADER_SRC[] = { 14 #embed "Shader.metal" 15 , '\0' 16 }; 17 18 struct RendererError { 19 enum class Kind { 20 ShaderCompilationFailed, 21 EntrypointNotFound, 22 PipelineCreationFailed, 23 DeviceError 24 }; 25 26 Kind kind; 27 std::optional<NS::String*> details; 28 29 RendererError(Kind kind); 30 RendererError(Kind kind, NS::Error* error = nullptr); 31 32 ~RendererError(); 33 34 static constexpr std::string_view baseMessage(Kind kind); 35 std::string message() const; 36 }; 37 38 /// The Renderer class is responsible for rendering the Mandelbulb fractal. 39 class Renderer { 40 MTL::Device* device; 41 MTL::CommandQueue* command_queue; 42 MTL::RenderPipelineState* pipeline_state; 43 44 MTL::Library* shader_lib; 45 46 MTL::Buffer* vertex_data_buffer; 47 MTL::Buffer* uniform_buffer[MAX_FRAMES_IN_FLIGHT]; 48 MTL::DepthStencilState* depth_stencil_state; 49 50 float current_time; 51 float angle; 52 int frame; 53 54 /// Semaphore to synchronize CPU and GPU, ensure frames in flight 55 dispatch_semaphore_t frame_semaphore; 56 57 /** Creates necessary GPU buffers: 58 * - Vertex buffer containing a full-screen quad (two triangles) 59 * - Multiple uniform buffers (one for each frame in flight) that store 60 * rendering parameters like time, resolution, and camera settings **/ 61 void buildBuffers(); 62 63 /** Sets up depth testing configuration: 64 * - Enables depth testing with "less than" comparison 65 * - Enables depth write 66 * - Creates depth stencil state object used during rendering **/ 67 void buildDepthStencilStates(); 68 69 /** Compiles and sets up the shader pipeline: 70 * - Loads and compiles Metal shader code 71 * - Configures render pipeline (pvertex/fragment functions, pixel format) 72 * - Creates pipeline state object 73 * @return error if shader compilation or pipeline creation fails **/ 74 std::expected<void, RendererError> buildShaders(); 75 76 struct VertexData { 77 simd::float3 position; 78 simd::float2 texcoord; // texture coordinates 79 }; 80 81 struct Uniforms { 82 float time; 83 simd::float2 resolution; // width, height 84 simd::float3 camera_position; 85 simd::float3 camera_target; 86 simd::float3 camera_up; 87 }; 88 89 /// Full-screen quad vertices 90 static constexpr VertexData vertices[] = { 91 // First triangle 92 { { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f } }, // bottom left 93 { { 1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f } }, // bottom right 94 { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f } }, // top left 95 96 // Second triangle 97 { { 1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f } }, // bottom right 98 { { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f } }, // top right 99 { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f } } // top left 100 }; 101 102 public: 103 // The only way to return an error from a constructor is 104 // to throw an exception, which is not ideal. 105 static std::expected<std::unique_ptr<Renderer>, RendererError> 106 init(MTL::Device* device); 107 108 ~Renderer(); 109 110 void draw(MTK::View* view); 111 };