11#include "spdlog/spdlog.h"
12#include "Foundation/NSBundle.hpp"
16 static int _pipelineId = 0;
19 const MTL::PrimitiveType MetalRenderPipeline::primitiveTopology[5] = {
20 MTL::PrimitiveTypePoint,
21 MTL::PrimitiveTypeLine,
22 MTL::PrimitiveTypeLineStrip,
23 MTL::PrimitiveTypeTriangle,
24 MTL::PrimitiveTypeTriangleStrip,
28 const MTL::BlendOperation MetalRenderPipeline::blendOperation[5] = {
29 MTL::BlendOperationAdd,
30 MTL::BlendOperationSubtract,
31 MTL::BlendOperationReverseSubtract,
32 MTL::BlendOperationMin,
33 MTL::BlendOperationMax
37 const MTL::BlendFactor MetalRenderPipeline::blendFactor[13] = {
40 MTL::BlendFactorSourceColor,
41 MTL::BlendFactorOneMinusSourceColor,
42 MTL::BlendFactorDestinationColor,
43 MTL::BlendFactorOneMinusDestinationColor,
44 MTL::BlendFactorSourceAlpha,
45 MTL::BlendFactorSourceAlphaSaturated,
46 MTL::BlendFactorOneMinusSourceAlpha,
47 MTL::BlendFactorDestinationAlpha,
48 MTL::BlendFactorOneMinusDestinationAlpha,
49 MTL::BlendFactorBlendColor,
50 MTL::BlendFactorOneMinusBlendColor
55 _lookupHashes.resize(15, 0);
56 _vertexBufferLayout = std::make_unique<MetalVertexBufferLayout>();
67 for (
const auto& [hash, entries] : _cache) {
68 for (
const auto& entry : entries) {
69 if (entry && entry->pipeline) {
70 entry->pipeline->release();
151 const std::shared_ptr<VertexFormat>& vertexFormat1,
int ibFormat,
const std::shared_ptr<Shader>& shader,
152 const std::shared_ptr<RenderTarget>& renderTarget,
153 const std::vector<std::shared_ptr<MetalBindGroupFormat>>& bindGroupFormats,
154 const std::shared_ptr<BlendState>& blendState,
const std::shared_ptr<DepthState>& depthState,
155 CullMode cullMode,
bool stencilEnabled,
156 const std::shared_ptr<StencilParameters>& stencilFront,
const std::shared_ptr<StencilParameters>& stencilBack,
157 const std::shared_ptr<VertexFormat>& instancingFormat) {
158 assert(bindGroupFormats.size() <= 3);
159 assert(shader !=
nullptr);
160 assert(blendState !=
nullptr);
161 assert(depthState !=
nullptr);
164 int primitiveType = primitive.
type;
165 if (primitiveType < 0 || primitiveType >= 5) {
173 _lookupHashes[0] = primitiveType;
174 _lookupHashes[1] = shader->id();
175 _lookupHashes[2] =
static_cast<int>(cullMode);
176 _lookupHashes[3] = depthState->key();
177 _lookupHashes[4] = blendState->key();
178 _lookupHashes[5] = vertexFormat0 ? vertexFormat0->renderingHash() : 0;
179 _lookupHashes[6] = vertexFormat1 ? vertexFormat1->renderingHash() : 0;
180 _lookupHashes[7] = renderTarget ? renderTarget->key() : 0;
181 _lookupHashes[8] = bindGroupFormats.size() > 0 && bindGroupFormats[0] ? bindGroupFormats[0]->key() : 0;
182 _lookupHashes[9] = bindGroupFormats.size() > 1 && bindGroupFormats[1] ? bindGroupFormats[1]->key() : 0;
183 _lookupHashes[10] = bindGroupFormats.size() > 2 && bindGroupFormats[2] ? bindGroupFormats[2]->key() : 0;
184 _lookupHashes[11] = stencilEnabled ? stencilFront->key() : 0;
185 _lookupHashes[12] = stencilEnabled ? stencilBack->key() : 0;
186 _lookupHashes[13] = ibFormat != -1 ? ibFormat : 0;
187 _lookupHashes[14] = (instancingFormat && instancingFormat->isInstancing())
188 ? instancingFormat->renderingHash() : 0;
190 uint32_t hash =
hash32Fnv1a(_lookupHashes.data(), _lookupHashes.size());
193 auto it = _cache.find(hash);
194 if (it != _cache.end()) {
195 auto& cacheEntries = it->second;
197 for (
auto& entry : cacheEntries) {
198 if (std::equal(entry->hashes.begin(), entry->hashes.end(), _lookupHashes.begin())) {
199 return entry->pipeline;
205 const MTL::PrimitiveType primTopology = primitiveTopology[primitiveType];
211 auto vbLayout = _vertexBufferLayout->get(vertexFormat0, vertexFormat1);
214 const int vbStride = vertexFormat0 ? vertexFormat0->size() : 14 *
static_cast<int>(
sizeof(float));
217 const int instStride = (instancingFormat && instancingFormat->isInstancing())
218 ? instancingFormat->size() : 0;
221 auto cacheEntry = std::make_shared<CacheEntry>();
222 cacheEntry->hashes = _lookupHashes;
223 cacheEntry->pipeline = create(
224 primTopology, ibFormat, shader, renderTarget, pipelineLayout,
225 blendState, depthState, vbLayout, cullMode,
226 stencilEnabled, stencilFront, stencilBack,
229 if (!cacheEntry->pipeline) {
230 spdlog::error(
"Render pipeline creation returned null");
235 if (it != _cache.end()) {
236 it->second.push_back(cacheEntry);
238 _cache[hash] = { cacheEntry };
241 return cacheEntry->pipeline;
244 MTL::RenderPipelineState* MetalRenderPipeline::create(
const MTL::PrimitiveType primitiveTopology,
int ibFormat,
245 const std::shared_ptr<Shader>& shader,
const std::shared_ptr<RenderTarget>& renderTarget,
247 std::shared_ptr<DepthState> depthState,
const std::vector<void*>& vertexBufferLayout,
248 CullMode cullMode,
bool stencilEnabled, std::shared_ptr<StencilParameters> stencilFront,
249 std::shared_ptr<StencilParameters> stencilBack,
255 spdlog::trace(
"Creating Metal render pipeline");
257 MTL::RenderPipelineDescriptor* pipelineDescriptor = MTL::RenderPipelineDescriptor::alloc()->init();
259 NS::Error* error =
nullptr;
260 MTL::Library* library =
nullptr;
261 const NS::Bundle* bundle = NS::Bundle::mainBundle();
262 if (
auto* metalShader =
dynamic_cast<MetalShader*
>(shader.get())) {
263 library = metalShader->getLibrary(mtlDevice, bundle, &error);
268 spdlog::error(
"Unsupported shader implementation for MetalRenderPipeline. Expected MetalShader.");
269 pipelineDescriptor->release();
274 spdlog::error(
"Failed to load Metal library: {}", error ? error->localizedDescription()->utf8String() :
"unknown");
276 pipelineDescriptor->release();
280 const auto& vertexEntry = shader->vertexEntry().empty() ? std::string(
"vertexShader") : shader->vertexEntry();
281 const auto& fragmentEntry = shader->fragmentEntry().empty() ? std::string(
"fragmentShader") : shader->fragmentEntry();
282 auto* vertexFunction = library->newFunction(NS::String::string(vertexEntry.c_str(), NS::UTF8StringEncoding));
283 auto* fragmentFunction = library->newFunction(NS::String::string(fragmentEntry.c_str(), NS::UTF8StringEncoding));
284 if (!vertexFunction || !fragmentFunction) {
285 spdlog::error(
"Failed to find required shader entry points ({} / {})", vertexEntry, fragmentEntry);
286 if (vertexFunction) vertexFunction->release();
287 if (fragmentFunction) fragmentFunction->release();
289 pipelineDescriptor->release();
294 pipelineDescriptor->setVertexFunction(vertexFunction);
295 pipelineDescriptor->setFragmentFunction(fragmentFunction);
297 if (
const auto metalTarget = std::dynamic_pointer_cast<MetalRenderTarget>(renderTarget))
299 const auto colorAttachments = metalTarget->colorAttachments();
301 MTL::ColorWriteMask writeMask = MTL::ColorWriteMaskNone;
302 if (blendState->redWrite()) writeMask |= MTL::ColorWriteMaskRed;
303 if (blendState->greenWrite()) writeMask |= MTL::ColorWriteMaskGreen;
304 if (blendState->blueWrite()) writeMask |= MTL::ColorWriteMaskBlue;
305 if (blendState->alphaWrite()) writeMask |= MTL::ColorWriteMaskAlpha;
307 for (
auto i = 0; i < colorAttachments.size(); ++i)
309 auto* colorAttachment = pipelineDescriptor->colorAttachments()->object(i);
310 colorAttachment->setPixelFormat(colorAttachments[i]->pixelFormat);
311 colorAttachment->setWriteMask(writeMask);
313 setBlend(colorAttachment, blendState);
317 if (
const auto& depthAtt = metalTarget->depthAttachment()) {
318 pipelineDescriptor->setDepthAttachmentPixelFormat(depthAtt->pixelFormat);
319 if (depthAtt->hasStencil) {
320 pipelineDescriptor->setStencilAttachmentPixelFormat(depthAtt->pixelFormat);
325 auto* colorAttachment = pipelineDescriptor->colorAttachments()->object(0);
326 colorAttachment->setPixelFormat(MTL::PixelFormatBGRA8Unorm);
327 setBlend(colorAttachment, blendState);
328 pipelineDescriptor->setDepthAttachmentPixelFormat(MTL::PixelFormatDepth32Float);
332 const std::string label =
"RenderPipeline-" + std::to_string(_pipelineId);
333 pipelineDescriptor->setLabel(NS::String::string(label.c_str(), NS::UTF8StringEncoding));
335 MTL::VertexDescriptor* vertexDescriptor = MTL::VertexDescriptor::vertexDescriptor();
336 auto* attributes = vertexDescriptor->attributes();
337 auto* layouts = vertexDescriptor->layouts();
347 constexpr int STRIDE_POINT_VERTEX = 7 *
static_cast<int>(
sizeof(float));
348 constexpr int STRIDE_DYNAMIC_BATCH = 15 *
static_cast<int>(
sizeof(float));
349 constexpr int STRIDE_WITH_COLOR = 18 *
static_cast<int>(
sizeof(float));
351 if (vertexStride <= STRIDE_POINT_VERTEX) {
355 attributes->object(0)->setFormat(MTL::VertexFormatFloat3);
356 attributes->object(0)->setOffset(0);
357 attributes->object(0)->setBufferIndex(0);
359 attributes->object(1)->setFormat(MTL::VertexFormatFloat3);
360 attributes->object(1)->setOffset(0);
361 attributes->object(1)->setBufferIndex(0);
363 attributes->object(2)->setFormat(MTL::VertexFormatFloat2);
364 attributes->object(2)->setOffset(0);
365 attributes->object(2)->setBufferIndex(0);
367 attributes->object(3)->setFormat(MTL::VertexFormatFloat4);
368 attributes->object(3)->setOffset(0);
369 attributes->object(3)->setBufferIndex(0);
371 attributes->object(4)->setFormat(MTL::VertexFormatFloat2);
372 attributes->object(4)->setOffset(0);
373 attributes->object(4)->setBufferIndex(0);
375 attributes->object(5)->setFormat(MTL::VertexFormatFloat4);
376 attributes->object(5)->setOffset(3 *
static_cast<NS::UInteger
>(
sizeof(
float)));
377 attributes->object(5)->setBufferIndex(0);
380 attributes->object(0)->setFormat(MTL::VertexFormatFloat3);
381 attributes->object(0)->setOffset(0);
382 attributes->object(0)->setBufferIndex(0);
384 attributes->object(1)->setFormat(MTL::VertexFormatFloat3);
385 attributes->object(1)->setOffset(3 *
static_cast<NS::UInteger
>(
sizeof(
float)));
386 attributes->object(1)->setBufferIndex(0);
388 attributes->object(2)->setFormat(MTL::VertexFormatFloat2);
389 attributes->object(2)->setOffset(6 *
static_cast<NS::UInteger
>(
sizeof(
float)));
390 attributes->object(2)->setBufferIndex(0);
392 attributes->object(3)->setFormat(MTL::VertexFormatFloat4);
393 attributes->object(3)->setOffset(8 *
static_cast<NS::UInteger
>(
sizeof(
float)));
394 attributes->object(3)->setBufferIndex(0);
396 attributes->object(4)->setFormat(MTL::VertexFormatFloat2);
397 attributes->object(4)->setOffset(12 *
static_cast<NS::UInteger
>(
sizeof(
float)));
398 attributes->object(4)->setBufferIndex(0);
403 if (vertexStride >= STRIDE_WITH_COLOR) {
404 attributes->object(5)->setFormat(MTL::VertexFormatFloat4);
405 attributes->object(5)->setOffset(14 *
static_cast<NS::UInteger
>(
sizeof(
float)));
406 attributes->object(5)->setBufferIndex(0);
407 }
else if (vertexStride >= STRIDE_DYNAMIC_BATCH) {
408 attributes->object(5)->setFormat(MTL::VertexFormatFloat);
409 attributes->object(5)->setOffset(14 *
static_cast<NS::UInteger
>(
sizeof(
float)));
410 attributes->object(5)->setBufferIndex(0);
414 layouts->object(0)->setStride(
static_cast<NS::UInteger
>(vertexStride));
415 layouts->object(0)->setStepFunction(MTL::VertexStepFunctionPerVertex);
416 layouts->object(0)->setStepRate(1);
421 if (instancingStride > 0) {
422 constexpr NS::UInteger INST_BUFFER_INDEX = 5;
423 constexpr NS::UInteger INST_ATTR_BASE = 6;
425 for (NS::UInteger i = 0; i < 5; ++i) {
426 attributes->object(INST_ATTR_BASE + i)->setFormat(MTL::VertexFormatFloat4);
427 attributes->object(INST_ATTR_BASE + i)->setOffset(i * 4 *
sizeof(
float));
428 attributes->object(INST_ATTR_BASE + i)->setBufferIndex(INST_BUFFER_INDEX);
431 layouts->object(INST_BUFFER_INDEX)->setStride(
static_cast<NS::UInteger
>(instancingStride));
432 layouts->object(INST_BUFFER_INDEX)->setStepFunction(MTL::VertexStepFunctionPerInstance);
433 layouts->object(INST_BUFFER_INDEX)->setStepRate(1);
436 pipelineDescriptor->setVertexDescriptor(vertexDescriptor);
438 auto* pipeline = mtlDevice->newRenderPipelineState(pipelineDescriptor, &error);
441 spdlog::error(
"Failed to create render pipeline state: {}", error->localizedDescription()->utf8String());
445 spdlog::trace(
"RenderPipelineAlloc | Alloc: Id " + std::to_string(_pipelineId));
447 vertexFunction->release();
448 fragmentFunction->release();
450 pipelineDescriptor->release();
455 void MetalRenderPipeline::setBlend(MTL::RenderPipelineColorAttachmentDescriptor* colorAttachment,
456 const std::shared_ptr<BlendState>& blendState)
458 if (!blendState->enabled()) {
462 colorAttachment->setBlendingEnabled(
true);
464 colorAttachment->setRgbBlendOperation(blendOperation[blendState->colorOp()]);
465 colorAttachment->setSourceRGBBlendFactor(blendFactor[blendState->colorSrcFactor()]);
466 colorAttachment->setDestinationRGBBlendFactor(blendFactor[blendState->colorDstFactor()]);
468 colorAttachment->setAlphaBlendOperation(blendOperation[blendState->alphaOp()]);
469 colorAttachment->setSourceAlphaBlendFactor(blendFactor[blendState->alphaSrcFactor()]);
470 colorAttachment->setDestinationAlphaBlendFactor(blendFactor[blendState->alphaDstFactor()]);
uint32_t hash32Fnv1a(const uint32_t *array, size_t length)
Describes how vertex and index data should be interpreted for a draw call.