/ src / Renderer.hh
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  };