/ src / Renderer.cc
Renderer.cc
  1  #include "Renderer.hh"
  2  
  3  import std;
  4  
  5  std::expected<std::unique_ptr<Renderer>, RendererError>
  6  Renderer::init(MTL::Device* device)
  7  {
  8      auto renderer = std::unique_ptr<Renderer>(new Renderer());
  9  
 10      renderer->device = device->retain();
 11      renderer->command_queue = renderer->device->newCommandQueue();
 12      renderer->angle = 0.0f;
 13      renderer->frame = 0;
 14      renderer->current_time = 0.0f;
 15  
 16      if (!renderer->command_queue) {
 17          return std::unexpected(RendererError(
 18              RendererError::Kind::DeviceError,
 19              nullptr));
 20      }
 21  
 22      return renderer->buildShaders()
 23          .transform([r = std::move(renderer)]() mutable {
 24              r->buildBuffers();
 25              r->buildDepthStencilStates();
 26              r->frame_semaphore = dispatch_semaphore_create(MAX_FRAMES_IN_FLIGHT);
 27              return std::move(r);
 28          });
 29  }
 30  
 31  Renderer::~Renderer()
 32  {
 33      this->vertex_data_buffer->release();
 34  
 35      for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
 36          this->uniform_buffer[i]->release();
 37      }
 38  
 39      this->shader_lib->release();
 40      this->depth_stencil_state->release();
 41      this->pipeline_state->release();
 42      this->command_queue->release();
 43      this->device->release();
 44  }
 45  
 46  std::expected<void, RendererError>
 47  Renderer::buildShaders()
 48  {
 49      NS::Error* error = nullptr;
 50  
 51      MTL::Library* mtl_lib = this->device->newLibrary(nsStringUtf8(SHADER_SRC), nullptr, &error);
 52      if (!mtl_lib) {
 53          return std::unexpected(RendererError(
 54              RendererError::Kind::ShaderCompilationFailed,
 55              error));
 56      }
 57  
 58      MTL::Function* vertex_fn = mtl_lib->newFunction(nsStringUtf8("vertexMain"));
 59      MTL::Function* frag_fn = mtl_lib->newFunction(nsStringUtf8("fragmentMain"));
 60  
 61      if (!vertex_fn || !frag_fn) {
 62          return std::unexpected(RendererError(
 63              RendererError::Kind::EntrypointNotFound,
 64              error));
 65      }
 66  
 67      MTL::RenderPipelineDescriptor* desc = MTL::RenderPipelineDescriptor::alloc()->init();
 68      desc->setVertexFunction(vertex_fn);
 69      desc->setFragmentFunction(frag_fn);
 70      desc->colorAttachments()->object(0)->setPixelFormat(
 71          MTL::PixelFormat::PixelFormatBGRA8Unorm_sRGB);
 72      desc->setDepthAttachmentPixelFormat(MTL::PixelFormat::PixelFormatDepth32Float);
 73  
 74      this->pipeline_state = this->device->newRenderPipelineState(desc, &error);
 75      if (!this->pipeline_state) {
 76          return std::unexpected(RendererError(
 77              RendererError::Kind::PipelineCreationFailed,
 78              error));
 79      }
 80  
 81      this->shader_lib = mtl_lib;
 82  
 83      vertex_fn->release();
 84      frag_fn->release();
 85      desc->release();
 86  
 87      return {};
 88  }
 89  
 90  void Renderer::buildBuffers()
 91  {
 92      const usize vertex_data_size = sizeof(this->vertices);
 93      this->vertex_data_buffer = this->device->newBuffer(
 94          vertex_data_size,
 95          MTL::ResourceStorageModeManaged);
 96  
 97      memcpy(this->vertex_data_buffer->contents(), this->vertices, vertex_data_size);
 98      this->vertex_data_buffer->didModifyRange(NS::Range::Make(0, vertex_data_size));
 99  
100      // Create uniform buffers for each frame
101      const usize uniform_buffer_size = sizeof(Uniforms);
102      for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
103          this->uniform_buffer[i] = this->device->newBuffer(
104              uniform_buffer_size,
105              MTL::ResourceStorageModeManaged);
106      }
107  }
108  
109  void Renderer::buildDepthStencilStates()
110  {
111      MTL::DepthStencilDescriptor* desc = MTL::DepthStencilDescriptor::alloc()->init();
112  
113      desc->setDepthCompareFunction(MTL::CompareFunction::CompareFunctionLess);
114      desc->setDepthWriteEnabled(true);
115  
116      this->depth_stencil_state = this->device->newDepthStencilState(desc);
117      desc->release();
118  }
119  
120  void Renderer::draw(MTK::View* view)
121  {
122      NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init();
123  
124      frame = (frame + 1) % MAX_FRAMES_IN_FLIGHT;
125  
126      // Update uniforms
127      Uniforms* uniforms = static_cast<Uniforms*>(
128          this->uniform_buffer[frame]->contents());
129  
130      current_time += 0.016f;
131  
132      uniforms->time = current_time;
133      uniforms->resolution = {
134          static_cast<float>(view->drawableSize().width),
135          static_cast<float>(view->drawableSize().height)
136      };
137  
138      uniforms->camera_position = { 0.0f, 0.0f, -3.0f };
139      uniforms->camera_target = { 0.0f, 0.0f, 0.0f };
140      uniforms->camera_up = { 0.0f, 1.0f, 0.0f };
141  
142      this->uniform_buffer[frame]->didModifyRange(NS::Range::Make(0, sizeof(Uniforms)));
143  
144      MTL::CommandBuffer* cmd_buffer = this->command_queue->commandBuffer();
145  
146      dispatch_semaphore_wait(this->frame_semaphore, DISPATCH_TIME_FOREVER);
147  
148      Renderer* renderer = this;
149      cmd_buffer->addCompletedHandler(^void(MTL::CommandBuffer* buffer) {
150          dispatch_semaphore_signal(renderer->frame_semaphore);
151      });
152  
153      MTL::RenderPassDescriptor* rpd = view->currentRenderPassDescriptor();
154      MTL::RenderCommandEncoder* encoder = cmd_buffer->renderCommandEncoder(rpd);
155  
156      encoder->setRenderPipelineState(this->pipeline_state);
157      encoder->setDepthStencilState(this->depth_stencil_state);
158  
159      encoder->setVertexBuffer(this->vertex_data_buffer, 0, 0);
160      encoder->setVertexBuffer(this->uniform_buffer[frame], 0, 1);
161      encoder->setFragmentBuffer(this->uniform_buffer[frame], 0, 0);
162  
163      encoder->drawPrimitives(
164          MTL::PrimitiveType::PrimitiveTypeTriangle,
165          /* vertexStart */ NS::UInteger(0), // vertex_id starting point
166          /* vertexCount */ NS::UInteger(6)); // 6 Vertices 2 Triangles
167  
168      encoder->endEncoding();
169      cmd_buffer->presentDrawable(view->currentDrawable());
170      cmd_buffer->commit();
171  
172      angle += 0.01f;
173  
174      pool->release();
175  }
176  
177  RendererError::RendererError(Kind kind)
178      : kind(kind)
179      , details(std::nullopt) { };
180  
181  RendererError::RendererError(Kind kind, NS::Error* error)
182      : kind(kind)
183      , details(error->localizedDescription()->retain())
184  {
185  }
186  
187  RendererError::~RendererError()
188  {
189      if (details)
190          details.value()->release();
191  }
192  
193  constexpr std::string_view RendererError::baseMessage(Kind kind)
194  {
195      switch (kind) {
196      case Kind::ShaderCompilationFailed:
197          return "Shader compilation failed";
198      case Kind::EntrypointNotFound:
199          return "Shader entrypoint not found";
200      case Kind::PipelineCreationFailed:
201          return "Pipeline creation failed";
202      case Kind::DeviceError:
203          return "Metal device error";
204      }
205  }
206  
207  std::string RendererError::message() const
208  {
209      std::string_view base_msg = baseMessage(this->kind);
210  
211      return this->details
212          .transform([base_msg](auto* details) {
213              return std::format("{}: {}",
214                  base_msg,
215                  details->utf8String());
216          })
217          .value_or(std::string(base_msg));
218  }