59 _window(options.window),
61 _commandQueue(nullptr),
66 _metalLayer =
static_cast<CA::MetalLayer*
>(options.
swapChain);
67 assert(_metalLayer &&
"Missing CAMetalLayer swap chain");
69 _device = _metalLayer->device();
70 assert(_device &&
"CAMetalLayer has no Metal device");
72 _metalLayer->setDevice(_device);
73 _metalLayer->setPixelFormat(MTL::PixelFormatBGRA8Unorm);
78 _metalLayer->setFramebufferOnly(
false);
80 _commandQueue = _device->newCommandQueue();
81 assert(_commandQueue &&
"Failed to create Metal command queue");
83 auto* samplerDesc = MTL::SamplerDescriptor::alloc()->init();
84 samplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear);
85 samplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear);
86 samplerDesc->setSAddressMode(MTL::SamplerAddressModeRepeat);
87 samplerDesc->setTAddressMode(MTL::SamplerAddressModeRepeat);
88 _defaultSampler = _device->newSamplerState(samplerDesc);
89 samplerDesc->release();
91 auto* depthDesc = MTL::DepthStencilDescriptor::alloc()->init();
95 depthDesc->setDepthCompareFunction(MTL::CompareFunctionLessEqual);
96 depthDesc->setDepthWriteEnabled(
true);
97 _defaultDepthStencilState = _device->newDepthStencilState(depthDesc);
102 depthDesc->setDepthWriteEnabled(
false);
103 _noWriteDepthStencilState = _device->newDepthStencilState(depthDesc);
104 depthDesc->release();
106 _renderPipeline = std::make_unique<MetalRenderPipeline>(
this);
107 _computePipeline = std::make_unique<MetalComputePipeline>(
this);
117 static constexpr size_t kMaxDrawsPerFrame = 4096;
118 _transformRing = std::make_unique<MetalUniformRingBuffer>(
119 _device, kMaxDrawsPerFrame,
sizeof(ModelData),
"TransformRing");
120 _uniformRing = std::make_unique<MetalUniformRingBuffer>(
125 _paletteRing = std::make_unique<MetalPaletteRingBuffer>(_device,
"PaletteRing");
129 backBufferOptions.
name =
"MetalBackBuffer";
131 setBackBuffer(std::make_shared<MetalBackBufferRenderTarget>(backBufferOptions));
387 if (computes.empty() || !_commandQueue || !_device) {
392 spdlog::warn(
"Skipping compute dispatch while a render/compute encoder is active");
396 auto* commandBuffer = _commandQueue->commandBuffer();
397 if (!commandBuffer) {
398 spdlog::warn(
"Failed to allocate command buffer for compute dispatch");
402 auto* encoder = commandBuffer->computeCommandEncoder();
404 spdlog::warn(
"Failed to allocate compute encoder");
408 if (!label.empty()) {
409 encoder->pushDebugGroup(NS::String::string(label.c_str(), NS::UTF8StringEncoding));
412 for (
const auto* compute : computes) {
413 if (!compute || !compute->shader()) {
417 auto* pipelineState = _computePipeline->get(compute->shader());
418 if (!pipelineState) {
422 encoder->setComputePipelineState(pipelineState);
426 std::vector<std::pair<std::string, Texture*>> textureBindings;
427 textureBindings.reserve(compute->textureParameters().size());
428 for (
const auto& [name, texture] : compute->textureParameters()) {
429 textureBindings.emplace_back(name, texture);
431 std::sort(textureBindings.begin(), textureBindings.end(),
432 [](
const auto& a,
const auto& b) { return a.first < b.first; });
435 for (
const auto& [_, texture] : textureBindings) {
436 auto* hwTexture = texture ?
dynamic_cast<gpu::MetalTexture*
>(texture->impl()) :
nullptr;
437 encoder->setTexture(hwTexture ? hwTexture->raw() :
nullptr,
static_cast<NS::UInteger
>(slot));
441 const MTL::Size threadgroups(
442 static_cast<NS::UInteger
>(compute->dispatchX()),
443 static_cast<NS::UInteger
>(compute->dispatchY()),
444 static_cast<NS::UInteger
>(compute->dispatchZ())
449 const MTL::Size threadsPerThreadgroup(8, 8, 1);
450 encoder->dispatchThreadgroups(threadgroups, threadsPerThreadgroup);
453 if (!label.empty()) {
454 encoder->popDebugGroup();
457 encoder->endEncoding();
458 commandBuffer->commit();
552 int numInstances,
const int indirectSlot,
const bool first,
const bool last)
555 spdlog::warn(
"Draw skipped: shader or render encoder is not set");
560 assert(passEncoder !=
nullptr);
562 MTL::RenderPipelineState* pipelineState = _pipelineState;
570 const std::shared_ptr<VertexBuffer>* instancingVBPtr =
nullptr;
582 int vbSlot = submitVertexBuffer(vb0, 0);
585 submitVertexBuffer(vb1, vbSlot);
590 if (instancingVBPtr) {
591 submitVertexBuffer(*instancingVBPtr, 5);
598 const int ibFormat = indexBuffer ? indexBuffer->format() : -1;
599 const auto instFmt = instancingVBPtr ? (*instancingVBPtr)->format() :
nullptr;
600 pipelineState = _renderPipeline->get(primitive, vb0 !=
nullptr ? vb0->format() :
nullptr,
601 vb1 !=
nullptr ? vb1->format() :
nullptr,
604 if (!pipelineState) {
605 spdlog::error(
"Draw skipped: failed to create/render pipeline state");
614 if (_pipelineState != pipelineState) {
615 _pipelineState = pipelineState;
616 passEncoder->setRenderPipelineState(pipelineState);
620 MTL::Buffer* ibBuffer =
nullptr;
621 MTL::IndexType indexType = MTL::IndexTypeUInt16;
623 ibBuffer =
static_cast<MTL::Buffer*
>(indexBuffer->nativeBuffer());
625 spdlog::warn(
"Draw skipped: index buffer has no native Metal buffer");
628 switch (indexBuffer->format()) {
630 indexType = MTL::IndexTypeUInt16;
633 indexType = MTL::IndexTypeUInt32;
637 spdlog::warn(
"Draw skipped: uint8 index buffers are not supported on Metal");
640 indexType = MTL::IndexTypeUInt16;
651 passEncoder->setFrontFacingWinding(MTL::WindingCounterClockwise);
655 const auto* boundMaterial =
material();
659 const void* uniformData = &materialUniforms;
663 size_t customSize = 0;
664 const void* customData = boundMaterial->customUniformData(customSize);
665 if (customData && customSize > 0) {
666 uniformData = customData;
667 uniformSize = customSize;
669 boundMaterial->updateUniforms(materialUniforms);
673 if (_uniformBinder.isMaterialChanged(boundMaterial)) {
674 std::vector<TextureSlot> textureSlots;
675 boundMaterial->getTextureSlots(textureSlots);
676 _textureBinder.bindMaterialTextures(passEncoder, textureSlots);
679 _textureBinder.clearAllMaterialSlots(passEncoder);
685 _textureBinder.bindSceneTextures(passEncoder,
686 _uniformBinder.envAtlasTexture(), _uniformBinder.shadowTexture(),
689 _textureBinder.bindLocalShadowTextures(passEncoder,
690 _uniformBinder.localShadowTexture0(), _uniformBinder.localShadowTexture1());
691 _textureBinder.bindOmniShadowTextures(passEncoder,
692 _uniformBinder.omniShadowCube0(), _uniformBinder.omniShadowCube1());
696 _uniformBinder.setScreenResolution(
vw(),
vh());
701 _uniformBinder.setReflectionBlurParams(
702 rbp.intensity, rbp.blurAmount, rbp.fadeStrength, rbp.angleFade,
703 rbp.fadeColor.r, rbp.fadeColor.g, rbp.fadeColor.b);
704 _uniformBinder.setReflectionDepthParams(rbp.planeDistance, rbp.heightRange);
707 _uniformBinder.submitPerDrawUniforms(passEncoder, _uniformRing.get(),
708 boundMaterial, uniformData, uniformSize,
hdrPass());
712 const auto& atmoUniforms = _uniformBinder.atmosphereUniforms();
713 passEncoder->setFragmentBytes(&atmoUniforms,
sizeof(atmoUniforms), 9);
716 _textureBinder.bindSamplerCached(passEncoder, _defaultSampler);
720 _textureBinder.markClean();
725 if (needsNoWrite && _noWriteDepthStencilState) {
726 passEncoder->setDepthStencilState(_noWriteDepthStencilState);
734 if (_pendingPaletteOffset != SIZE_MAX) {
735 passEncoder->setVertexBufferOffset(_pendingPaletteOffset, 6);
736 _pendingPaletteOffset = SIZE_MAX;
743 if (indirectSlot >= 0 && _indirectDrawBuffer) {
747 passEncoder->drawIndexedPrimitives(
748 primitiveType, indexType, ibBuffer, 0,
749 _indirectDrawBuffer, indirectOffset);
751 passEncoder->drawPrimitives(
752 primitiveType, _indirectDrawBuffer, indirectOffset);
754 _indirectDrawBuffer =
nullptr;
758 const auto indexElementSize = (indexType == MTL::IndexTypeUInt32) ? 4 : 2;
759 const auto indexBufferOffset =
static_cast<NS::UInteger
>(primitive.
base * indexElementSize);
760 passEncoder->drawIndexedPrimitives(
762 static_cast<NS::UInteger
>(primitive.
count),
766 static_cast<NS::UInteger
>(numInstances),
767 static_cast<NS::Integer
>(primitive.
baseVertex),
771 passEncoder->drawPrimitives(
773 static_cast<NS::UInteger
>(primitive.
base),
774 static_cast<NS::UInteger
>(primitive.
count),
775 static_cast<NS::UInteger
>(numInstances)
781 if (needsNoWrite && _defaultDepthStencilState) {
782 passEncoder->setDepthStencilState(_defaultDepthStencilState);
790 _pipelineState =
nullptr;
797 spdlog::warn(
"Cannot start render pass: queue/layer invalid or encoder already active");
801 const std::shared_ptr<RenderTarget> activeTarget =
804 const auto offscreenTarget = std::dynamic_pointer_cast<MetalRenderTarget>(activeTarget);
805 const bool isBackBufferPass = activeTarget ==
backBuffer();
806 if (!isBackBufferPass && !offscreenTarget) {
807 spdlog::error(
"Non-backbuffer render target is not a MetalRenderTarget");
810 if (offscreenTarget) {
811 offscreenTarget->ensureAttachments();
814 _currentDrawable =
nullptr;
815 if (isBackBufferPass) {
821 if (_frameDrawable) {
822 _currentDrawable = _frameDrawable;
823 spdlog::trace(
"Reusing cached CAMetalDrawable for back-buffer pass");
825 _currentDrawable = _metalLayer->nextDrawable();
826 if (!_currentDrawable) {
827 spdlog::warn(
"Failed to acquire CAMetalDrawable");
830 _frameDrawable = _currentDrawable;
831 spdlog::trace(
"Acquired new CAMetalDrawable for frame");
835 _commandBuffer = _commandQueue->commandBuffer();
836 if (!_commandBuffer) {
837 spdlog::error(
"Failed to create Metal command buffer");
838 _currentDrawable =
nullptr;
842 auto* passDesc = MTL::RenderPassDescriptor::alloc()->init();
844 const auto& colorOpsArray = renderPass ? renderPass->
colorArrayOps() : std::vector<std::shared_ptr<ColorAttachmentOps>>{};
845 const auto depthOps = renderPass ? renderPass->
depthStencilOps() :
nullptr;
846 const bool canResolve = activeTarget && activeTarget->samples() > 1 && activeTarget->autoResolve();
848 auto resolveColorStoreAction = [](
bool store,
bool resolve) {
850 return store ? MTL::StoreActionStoreAndMultisampleResolve : MTL::StoreActionMultisampleResolve;
852 return store ? MTL::StoreActionStore : MTL::StoreActionDontCare;
855 if (isBackBufferPass) {
856 auto* colorAttachment = passDesc->colorAttachments()->object(0);
857 colorAttachment->setTexture(_currentDrawable->texture());
859 const auto colorOps = renderPass ? renderPass->
colorOps() :
nullptr;
860 if (colorOps && colorOps->clear) {
861 const auto c = colorOps->clearValue;
862 colorAttachment->setLoadAction(MTL::LoadActionClear);
863 colorAttachment->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a));
865 colorAttachment->setLoadAction(MTL::LoadActionLoad);
867 colorAttachment->setStoreAction(colorOps && colorOps->store ? MTL::StoreActionStore : MTL::StoreActionDontCare);
869 const auto drawableWidth =
static_cast<int>(_currentDrawable->texture()->width());
870 const auto drawableHeight =
static_cast<int>(_currentDrawable->texture()->height());
871 if (!_backBufferDepthTexture ||
872 _backBufferDepthWidth != drawableWidth ||
873 _backBufferDepthHeight != drawableHeight) {
874 if (_backBufferDepthTexture) {
875 _backBufferDepthTexture->release();
876 _backBufferDepthTexture =
nullptr;
879 _backBufferDepthWidth = drawableWidth;
880 _backBufferDepthHeight = drawableHeight;
883 if (_backBufferDepthTexture) {
884 auto* depthAttachment = passDesc->depthAttachment();
885 depthAttachment->setTexture(_backBufferDepthTexture);
886 if (depthOps && depthOps->clearDepth) {
887 depthAttachment->setLoadAction(MTL::LoadActionClear);
888 depthAttachment->setClearDepth(depthOps->clearDepthValue);
890 depthAttachment->setLoadAction(MTL::LoadActionLoad);
892 depthAttachment->setStoreAction(depthOps && depthOps->storeDepth
893 ? MTL::StoreActionStore
894 : MTL::StoreActionDontCare);
897 const auto& colorAttachments = offscreenTarget->colorAttachments();
898 for (
size_t i = 0; i < colorAttachments.size(); ++i) {
899 const auto& attachment = colorAttachments[i];
900 if (!attachment || !attachment->texture) {
904 auto* colorAttachment = passDesc->colorAttachments()->object(
static_cast<NS::UInteger
>(i));
905 const bool multisampled = attachment->multisampledBuffer !=
nullptr;
906 colorAttachment->setTexture(multisampled ? attachment->multisampledBuffer : attachment->texture);
908 const auto ops = i < colorOpsArray.size() ? colorOpsArray[i] :
nullptr;
909 if (ops && ops->clear) {
910 const auto c = ops->clearValue;
911 colorAttachment->setLoadAction(MTL::LoadActionClear);
912 colorAttachment->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a));
914 colorAttachment->setLoadAction(MTL::LoadActionLoad);
917 const bool resolve = multisampled && canResolve && ops && ops->resolve;
919 colorAttachment->setResolveTexture(attachment->texture);
921 colorAttachment->setStoreAction(resolveColorStoreAction(ops ? ops->store :
true, resolve));
924 const auto& depthAttachmentData = offscreenTarget->depthAttachment();
925 if (depthAttachmentData && depthAttachmentData->depthTexture) {
926 auto* depthAttachment = passDesc->depthAttachment();
927 const bool depthMsaa = depthAttachmentData->multisampledDepthBuffer !=
nullptr;
928 MTL::Texture* depthTex = depthMsaa ? depthAttachmentData->multisampledDepthBuffer : depthAttachmentData->depthTexture;
929 depthAttachment->setTexture(depthTex);
934 if (depthTex->textureType() == MTL::TextureTypeCube && activeTarget->face() >= 0) {
935 depthAttachment->setSlice(
static_cast<NS::UInteger
>(activeTarget->face()));
938 if (depthOps && depthOps->clearDepth) {
939 depthAttachment->setLoadAction(MTL::LoadActionClear);
940 depthAttachment->setClearDepth(depthOps->clearDepthValue);
942 depthAttachment->setLoadAction(MTL::LoadActionLoad);
945 const bool resolveDepth = depthMsaa && canResolve && depthOps && depthOps->resolveDepth;
947 depthAttachment->setResolveTexture(depthAttachmentData->depthTexture);
949 depthAttachment->setStoreAction(resolveColorStoreAction(depthOps ? depthOps->storeDepth :
true, resolveDepth));
951 if (depthAttachmentData->hasStencil) {
952 auto* stencilAttachment = passDesc->stencilAttachment();
953 stencilAttachment->setTexture(depthMsaa ? depthAttachmentData->multisampledDepthBuffer : depthAttachmentData->depthTexture);
954 if (depthOps && depthOps->clearStencil) {
955 stencilAttachment->setLoadAction(MTL::LoadActionClear);
956 stencilAttachment->setClearStencil(depthOps->clearStencilValue);
958 stencilAttachment->setLoadAction(MTL::LoadActionLoad);
960 stencilAttachment->setStoreAction(depthOps && depthOps->storeStencil ? MTL::StoreActionStore : MTL::StoreActionDontCare);
967 spdlog::error(
"Failed to create Metal render command encoder");
968 _commandBuffer =
nullptr;
969 _currentDrawable =
nullptr;
972 if (_defaultDepthStencilState) {
975 const int targetWidth = activeTarget ? activeTarget->width() :
size().first;
976 const int targetHeight = activeTarget ? activeTarget->height() :
size().second;
977 if (targetWidth > 0 && targetHeight > 0) {
978 setViewport(0.0f, 0.0f,
static_cast<float>(targetWidth),
static_cast<float>(targetHeight));
992 if (_clusterBuffersSet && _clusterLightBuffer && _clusterCellBuffer) {
998 _uniformBinder.resetPassState();
999 _textureBinder.resetPassState();
1003 passDesc->release();