#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace mtl { // Maximum number of vertex attributes, per vertex descriptor // 31 for Apple2-8, Mac2, per https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf constexpr uint32_t maximumVertexBindingCount = 31; Context::Context(RendererBackend& backend_) : gfx::Context(mtl::maximumVertexBindingCount), backend(backend_), stats() {} Context::~Context() noexcept { if (cleanupOnDestruction) { performCleanup(); assert(stats.isZero()); } } std::unique_ptr Context::createCommandEncoder() { return std::make_unique(*this); } BufferResource Context::createBuffer(const void* data, std::size_t size, gfx::BufferUsageType) const { return {backend.getDevice(), data, size, MTL::ResourceStorageModeShared}; } UniqueShaderProgram Context::createProgram(std::string name, const std::string_view source, const std::string_view vertexName, const std::string_view fragmentName, const ProgramParameters& programParameters, const std::unordered_map& additionalDefines) { const auto pool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init()); // No NSMutableDictionary? const auto& programDefines = programParameters.getDefines(); const auto numDefines = programDefines.size() + additionalDefines.size(); std::vector rawDefines; rawDefines.reserve(2 * numDefines); const auto addDefine = [&rawDefines](const auto& pair) { auto* nsKey = NS::String::string(pair.first.data(), NS::UTF8StringEncoding); auto* nsVal = NS::String::string(pair.second.data(), NS::UTF8StringEncoding); rawDefines.insert(std::next(rawDefines.begin(), rawDefines.size() / 2), nsKey); rawDefines.insert(rawDefines.end(), nsVal); }; std::for_each(programDefines.begin(), programDefines.end(), addDefine); std::for_each(additionalDefines.begin(), additionalDefines.end(), addDefine); const auto nsDefines = NS::Dictionary::dictionary( &rawDefines[numDefines], rawDefines.data(), static_cast(numDefines)); rawDefines.clear(); auto options = NS::TransferPtr(MTL::CompileOptions::alloc()->init()); options->setPreprocessorMacros(nsDefines); options->setFastMathEnabled(true); options->setLanguageVersion(MTL::LanguageVersion2_1); // TODO: Compile common code into a `LibraryTypeDynamic` to be used by other shaders // instead of duplicating that code in each and every shader compilation. options->setLibraryType(MTL::LibraryTypeExecutable); // Allows use of the [[invariant]] attribute on position outputs to // guarantee that the GPU performs the calculations the same way. options->setPreserveInvariance(true); // TODO: Allow use of `LibraryOptimizationLevelSize` which "may also reduce compile time" // requires a check for iOS 16+ // options->setOptimizationLevel(MTL::LibraryOptimizationLevelDefault); NS::Error* error = nullptr; NS::String* nsSource = NS::String::string(source.data(), NS::UTF8StringEncoding); const auto& device = backend.getDevice(); MTL::Library* library = device->newLibrary(nsSource, options.get(), &error); if (!library || error) { const auto errPtr = error ? error->localizedDescription()->utf8String() : nullptr; const auto errStr = (errPtr && errPtr[0]) ? ": " + std::string(errPtr) : std::string(); Log::Error(Event::Shader, name + " compile failed" + errStr); assert(false); return nullptr; } const auto nsVertName = NS::String::string(vertexName.data(), NS::UTF8StringEncoding); NS::SharedPtr vertexFunction = NS::RetainPtr(library->newFunction(nsVertName)); if (!vertexFunction) { Log::Error(Event::Shader, name + " missing vertex function " + vertexName.data()); assert(false); return nullptr; } // fragment function is optional NS::SharedPtr fragmentFunction; if (!fragmentName.empty()) { const auto nsFragName = NS::String::string(fragmentName.data(), NS::UTF8StringEncoding); fragmentFunction = NS::RetainPtr(library->newFunction(nsFragName)); if (!fragmentFunction) { Log::Error(Event::Shader, name + " missing fragment function " + fragmentName.data()); assert(false); return nullptr; } } return std::make_unique( std::move(name), backend, std::move(vertexFunction), std::move(fragmentFunction)); } MTLTexturePtr Context::createMetalTexture(MTLTextureDescriptorPtr textureDescriptor) const { return NS::TransferPtr(backend.getDevice()->newTexture(textureDescriptor.get())); } MTLSamplerStatePtr Context::createMetalSamplerState(MTLSamplerDescriptorPtr samplerDescriptor) const { return NS::TransferPtr(backend.getDevice()->newSamplerState(samplerDescriptor.get())); } gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) { return std::make_unique(std::move(name)); } gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size) { return std::make_shared(createBuffer(data, size, gfx::BufferUsageType::StaticDraw)); } gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& shaders, const std::string& name) { const auto shaderGroup = shaders.getShaderGroup(name); auto shader = shaderGroup ? shaderGroup->getOrCreateShader(*this, {}) : gfx::ShaderProgramBasePtr{}; return std::static_pointer_cast(std::move(shader)); } TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { return std::make_shared(layerIndex, initialCapacity, std::move(name)); } LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { return std::make_shared(layerIndex, initialCapacity, name); } gfx::Texture2DPtr Context::createTexture2D() { return std::make_shared(*this); } RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) { return std::make_shared(*this, size, type); } void Context::resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) {} bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer, const void* data, std::size_t size) { if (buffer) { buffer->update(data, size); return false; } else { buffer = createUniformBuffer(data, size); return true; } } void Context::setDirtyState() {} std::unique_ptr Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) { return std::make_unique(*this, size, type); } std::unique_ptr Context::createTextureResource(Size, gfx::TexturePixelType, gfx::TextureChannelDataType) { assert(false); return nullptr; } std::unique_ptr Context::createRenderbufferResource(gfx::RenderbufferPixelType, Size size) { return std::make_unique(); } std::unique_ptr Context::createDrawScopeResource() { assert(false); return nullptr; } #if !defined(NDEBUG) void Context::visualizeStencilBuffer() {} void Context::visualizeDepthBuffer(float depthRangeSize) {} #endif // !defined(NDEBUG) void Context::clearStencilBuffer(int32_t) { // See `PaintParameters::clearStencil` assert(false); } namespace { MTL::CompareFunction mapFunc(const gfx::DepthFunctionType func) { switch (func) { default: case gfx::DepthFunctionType::Never: return MTL::CompareFunction::CompareFunctionNever; case gfx::DepthFunctionType::Less: return MTL::CompareFunction::CompareFunctionLess; case gfx::DepthFunctionType::Equal: return MTL::CompareFunction::CompareFunctionEqual; case gfx::DepthFunctionType::LessEqual: return MTL::CompareFunction::CompareFunctionLessEqual; case gfx::DepthFunctionType::Greater: return MTL::CompareFunction::CompareFunctionGreater; case gfx::DepthFunctionType::NotEqual: return MTL::CompareFunction::CompareFunctionNotEqual; case gfx::DepthFunctionType::GreaterEqual: return MTL::CompareFunction::CompareFunctionGreaterEqual; case gfx::DepthFunctionType::Always: return MTL::CompareFunction::CompareFunctionAlways; }; } MTL::CompareFunction mapFunc(const gfx::StencilFunctionType func) { switch (func) { default: case gfx::StencilFunctionType::Never: return MTL::CompareFunction::CompareFunctionNever; case gfx::StencilFunctionType::Less: return MTL::CompareFunction::CompareFunctionLess; case gfx::StencilFunctionType::Equal: return MTL::CompareFunction::CompareFunctionEqual; case gfx::StencilFunctionType::LessEqual: return MTL::CompareFunction::CompareFunctionLessEqual; case gfx::StencilFunctionType::Greater: return MTL::CompareFunction::CompareFunctionGreater; case gfx::StencilFunctionType::NotEqual: return MTL::CompareFunction::CompareFunctionNotEqual; case gfx::StencilFunctionType::GreaterEqual: return MTL::CompareFunction::CompareFunctionGreaterEqual; case gfx::StencilFunctionType::Always: return MTL::CompareFunction::CompareFunctionAlways; }; } MTL::StencilOperation mapOperation(const gfx::StencilOpType op) { switch (op) { case gfx::StencilOpType::Zero: return MTL::StencilOperation::StencilOperationZero; default: case gfx::StencilOpType::Keep: return MTL::StencilOperation::StencilOperationKeep; case gfx::StencilOpType::Replace: return MTL::StencilOperation::StencilOperationReplace; case gfx::StencilOpType::Increment: return MTL::StencilOperation::StencilOperationIncrementClamp; case gfx::StencilOpType::Decrement: return MTL::StencilOperation::StencilOperationDecrementClamp; case gfx::StencilOpType::Invert: return MTL::StencilOperation::StencilOperationInvert; case gfx::StencilOpType::IncrementWrap: return MTL::StencilOperation::StencilOperationIncrementWrap; case gfx::StencilOpType::DecrementWrap: return MTL::StencilOperation::StencilOperationDecrementWrap; } } void applyDepthMode(const gfx::DepthMode& depthMode, MTL::DepthStencilDescriptor* desc) { desc->setDepthCompareFunction(mapFunc(depthMode.func)); desc->setDepthWriteEnabled(depthMode.mask == gfx::DepthMaskType::ReadWrite); // depthMode.range ? } struct StencilModeVisitor { MTL::StencilDescriptor* desc; template void operator()(const gfx::StencilMode::SimpleTest& mode) { apply(mode.func, std::numeric_limits::max()); } template void operator()(const gfx::StencilMode::MaskedTest& mode) { apply(mode.func, mode.mask); } void apply(gfx::StencilFunctionType func, uint32_t mask) { desc->setStencilCompareFunction(mapFunc(func)); desc->setReadMask(mask); desc->setWriteMask(mask); } }; // helper type for the visitor #4 void applyStencilMode(const gfx::StencilMode& stencilMode, MTL::StencilDescriptor* desc) { mapbox::util::apply_visitor(StencilModeVisitor{desc}, stencilMode.test); desc->setStencilFailureOperation(mapOperation(stencilMode.fail)); desc->setDepthFailureOperation(mapOperation(stencilMode.depthFail)); desc->setDepthStencilPassOperation(mapOperation(stencilMode.pass)); } } // namespace MTLDepthStencilStatePtr Context::makeDepthStencilState(const gfx::DepthMode& depthMode, const gfx::StencilMode& stencilMode, const mtl::RenderPass& renderPass) const { auto depthStencilDescriptor = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init()); if (!depthStencilDescriptor) { return {}; } auto& device = backend.getDevice(); const auto& renderPassDescriptor = renderPass.getDescriptor(); const auto& renderable = renderPassDescriptor.renderable; const auto& renderableResource = renderable.getResource(); if (const auto& rpd = renderableResource.getRenderPassDescriptor()) { // Setting depth/stencil properties when the corresponding target textures aren't set causes, e.g.: // `Draw Errors Validation MTLDepthStencilDescriptor sets depth test but MTLRenderPassDescriptor has a nil // depthAttachment texture` if (auto* depthTarget = rpd->depthAttachment()) { if (auto* tex = depthTarget->texture()) { applyDepthMode(depthMode, depthStencilDescriptor.get()); } } if (auto* stencilTarget = rpd->stencilAttachment()) { if (auto* tex = stencilTarget->texture()) { auto stencilDescriptor = NS::TransferPtr(MTL::StencilDescriptor::alloc()->init()); if (!stencilDescriptor) { return {}; } applyStencilMode(stencilMode, stencilDescriptor.get()); depthStencilDescriptor->setFrontFaceStencil(stencilDescriptor.get()); depthStencilDescriptor->setBackFaceStencil(stencilDescriptor.get()); } } } return NS::TransferPtr(device->newDepthStencilState(depthStencilDescriptor.get())); } } // namespace mtl } // namespace mbgl