Files
MapLibreParticleLab/src/mbgl/mtl/context.cpp
2025-04-02 19:51:34 +00:00

637 lines
27 KiB
C++

#include <mbgl/mtl/context.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/mtl/command_encoder.hpp>
#include <mbgl/mtl/drawable_builder.hpp>
#include <mbgl/mtl/layer_group.hpp>
#include <mbgl/mtl/offscreen_texture.hpp>
#include <mbgl/mtl/renderer_backend.hpp>
#include <mbgl/mtl/renderable_resource.hpp>
#include <mbgl/mtl/texture2d.hpp>
#include <mbgl/mtl/tile_layer_group.hpp>
#include <mbgl/mtl/render_pass.hpp>
#include <mbgl/mtl/uniform_buffer.hpp>
#include <mbgl/mtl/upload_pass.hpp>
#include <mbgl/mtl/vertex_buffer_resource.hpp>
#include <mbgl/programs/program_parameters.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_target.hpp>
#include <mbgl/shaders/mtl/clipping_mask.hpp>
#include <mbgl/shaders/mtl/shader_program.hpp>
#include <mbgl/util/traits.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <mbgl/util/hash.hpp>
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
#include <algorithm>
#include <cstring>
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_) {}
Context::~Context() noexcept {
if (cleanupOnDestruction) {
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
performCleanup();
emptyVertexBuffer.reset();
tileVertexBuffer.reset();
tileIndexBuffer.reset();
clipMaskShader.reset();
clipMaskDepthStencilState.reset();
clipMaskPipelineState.reset();
clipMaskUniformsBuffer.reset();
stencilStateRenderable = nullptr;
for (size_t i = 0; i < globalUniformBuffers.allocatedSize(); i++) {
globalUniformBuffers.set(i, nullptr);
}
#if !defined(NDEBUG)
Log::Debug(Event::General, "Rendering Stats:\n" + stats.toString("\n"));
#endif
assert(stats.isZero());
}
}
void Context::beginFrame() {
backend.getThreadPool().runRenderJobs();
}
void Context::endFrame() {}
std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {
return std::make_unique<CommandEncoder>(*this);
}
BufferResource Context::createBuffer(
const void* data, std::size_t size, gfx::BufferUsageType, bool isIndexBuffer, bool persistent) const {
return {const_cast<Context&>(*this), data, size, MTL::ResourceStorageModeShared, isIndexBuffer, persistent};
}
// If enabled, ubsan flags the implicit cast from assigning `NS::String*` to `NS::Object*` here
// as an alignment error. The ObjC NSString allocations are cast to a C++ object pointer by
// `NS::Object::sendMessage`, and the alignment rules are different. We assume that, because
// `NS::Object` does not use virtual methods and only serves as a way for C++ code to store
// and pass ObjC `id`s to `objc_msgSend`, this won't cause any real problems.
UniqueShaderProgram Context::createProgram(shaders::BuiltIn shaderID,
std::string name,
const std::string_view source,
const std::string_view vertexName,
const std::string_view fragmentName,
const ProgramParameters& programParameters,
const mbgl::unordered_map<std::string, std::string>& 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::string defineStr;
std::vector<const NS::Object*> rawDefines;
rawDefines.reserve(2 * numDefines);
const auto addDefine = [&rawDefines, &defineStr](const auto& pair) {
const auto* nsKey = NS::String::string(pair.first.data(), NS::UTF8StringEncoding);
const 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);
defineStr += "#define " + pair.first + " " + pair.second + "\n";
};
std::for_each(programDefines.begin(), programDefines.end(), addDefine);
std::for_each(additionalDefines.begin(), additionalDefines.end(), addDefine);
observer->onPreCompileShader(shaderID, gfx::Backend::Type::Metal, defineStr);
const auto nsDefines = NS::Dictionary::dictionary(
&rawDefines[numDefines], rawDefines.data(), static_cast<NS::UInteger>(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.
// requires a check for iOS 14+
// options->setLibraryType(MTL::LibraryTypeExecutable);
// Allows use of the [[invariant]] attribute on position outputs to
// guarantee that the GPU performs the calculations the same way.
// requires a check for iOS 14+
// 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();
auto library = NS::TransferPtr(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);
observer->onShaderCompileFailed(shaderID, gfx::Backend::Type::Metal, defineStr);
assert(false);
return nullptr;
}
const auto nsVertName = NS::String::string(vertexName.data(), NS::UTF8StringEncoding);
MTLFunctionPtr vertexFunction = NS::TransferPtr(library->newFunction(nsVertName));
if (!vertexFunction) {
Log::Error(Event::Shader, name + " missing vertex function " + vertexName.data());
observer->onShaderCompileFailed(shaderID, gfx::Backend::Type::Metal, defineStr);
assert(false);
return nullptr;
}
// fragment function is optional
MTLFunctionPtr fragmentFunction;
if (!fragmentName.empty()) {
const auto nsFragName = NS::String::string(fragmentName.data(), NS::UTF8StringEncoding);
fragmentFunction = NS::TransferPtr(library->newFunction(nsFragName));
if (!fragmentFunction) {
Log::Error(Event::Shader, name + " missing fragment function " + fragmentName.data());
observer->onShaderCompileFailed(shaderID, gfx::Backend::Type::Metal, defineStr);
assert(false);
return nullptr;
}
}
auto shader = std::make_unique<ShaderProgram>(
std::move(name), backend, std::move(vertexFunction), std::move(fragmentFunction));
observer->onPostCompileShader(shaderID, gfx::Backend::Type::Metal, defineStr);
return shader;
}
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()));
}
void Context::performCleanup() {
stats.numDrawCalls = 0;
stats.numFrames++;
clipMaskUniformsBufferUsed = false;
}
gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) {
return std::make_unique<DrawableBuilder>(std::move(name));
}
gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size, bool persistent, bool /*ssbo*/) {
return std::make_shared<UniformBuffer>(
createBuffer(data, size, gfx::BufferUsageType::StaticDraw, /*isIndexBuffer=*/false, persistent));
}
UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
return std::make_unique<UniformBufferArray>();
}
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<gfx::ShaderProgramBase>(std::move(shader));
}
TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
return std::make_shared<TileLayerGroup>(layerIndex, initialCapacity, std::move(name));
}
LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
return std::make_shared<LayerGroup>(layerIndex, initialCapacity, name);
}
gfx::Texture2DPtr Context::createTexture2D() {
return std::make_shared<Texture2D>(*this);
}
RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) {
return std::make_shared<RenderTarget>(*this, size, type);
}
void Context::resetState(gfx::DepthMode, gfx::ColorMode) {}
bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer,
const void* data,
std::size_t size,
bool persistent) {
if (buffer) {
buffer->update(data, size);
return false;
} else {
buffer = createUniformBuffer(data, size, persistent);
return true;
}
}
const BufferResource& Context::getTileVertexBuffer() {
if (!tileVertexBuffer) {
const auto vertices = RenderStaticData::tileVertices();
tileVertexBuffer.emplace(createBuffer(vertices.data(),
vertices.bytes(),
gfx::BufferUsageType::StaticDraw,
/*isIndexBuffer=*/true,
/*persistent=*/true));
}
return *tileVertexBuffer;
}
const BufferResource& Context::getTileIndexBuffer() {
if (!tileIndexBuffer) {
const auto indexes = RenderStaticData::quadTriangleIndices();
tileIndexBuffer.emplace(createBuffer(indexes.data(),
indexes.bytes(),
gfx::BufferUsageType::StaticDraw,
/*isIndexBuffer=*/true,
/*persistent=*/true));
}
return *tileIndexBuffer;
}
const UniqueVertexBufferResource& Context::getEmptyVertexBuffer() {
if (!emptyVertexBuffer) {
// This buffer is bound to vertex attribtue indexes when the uniforms are used instead and
// shaders are expected not to access the attribute values, but Metal requires a binding.
// This buffer could also hold a single default value applied for all vertices, in which case
// it could not be shared (See `MTL::VertexStepFunctionConstant` in the vertex attribute
// descriptors, but that isn't currently being used.
constexpr std::size_t size = 32;
constexpr auto usage = gfx::BufferUsageType::StaticDraw;
emptyVertexBuffer = std::make_unique<VertexBufferResource>(
createBuffer(nullptr, size, usage, /*isIndexBuffer=*/false, /*persistent=*/false));
}
return emptyVertexBuffer;
}
namespace {
const auto clipMaskStencilMode = gfx::StencilMode{
/*.test=*/gfx::StencilMode::Always(),
/*.ref=*/0,
/*.mask=*/0b11111111,
/*.fail=*/gfx::StencilOpType::Keep,
/*.depthFail=*/gfx::StencilOpType::Keep,
/*.pass=*/gfx::StencilOpType::Replace,
};
const auto clipMaskDepthMode = gfx::DepthMode{/*.func=*/gfx::DepthFunctionType::Always,
/*.mask=*/gfx::DepthMaskType::ReadOnly};
} // namespace
bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass,
RenderStaticData& staticData,
const std::vector<shaders::ClipUBO>& tileUBOs) {
using ShaderClass = shaders::ShaderSource<shaders::BuiltIn::ClippingMaskProgram, gfx::Backend::Type::Metal>;
if (!clipMaskShader) {
const auto group = staticData.shaders->getShaderGroup("ClippingMaskProgram");
if (group) {
clipMaskShader = std::static_pointer_cast<gfx::ShaderProgramBase>(group->getOrCreateShader(*this, {}));
}
}
if (!clipMaskShader) {
assert(!"Failed to create shader for clip masking");
return false;
}
const auto& mtlShader = static_cast<const mtl::ShaderProgram&>(*clipMaskShader);
auto& mtlRenderPass = static_cast<mtl::RenderPass&>(renderPass);
const auto& encoder = mtlRenderPass.getMetalEncoder();
const auto colorMode = gfx::ColorMode::disabled();
// Create a vertex buffer from the fixed tile coordinates
constexpr auto vertexSize = sizeof(gfx::Vertex<PositionOnlyLayoutAttributes>::a1);
const auto& vertexRes = getTileVertexBuffer();
if (!vertexRes) {
assert(vertexSize == vertexRes.getSizeInBytes());
return false;
}
// Create a buffer from the fixed tile indexes
constexpr NS::UInteger indexCount = 6;
const auto indexRes = &getTileIndexBuffer();
if (!indexRes) {
return false;
}
const auto& renderPassDescriptor = mtlRenderPass.getDescriptor();
const auto& renderable = renderPassDescriptor.renderable;
if (stencilStateRenderable != &renderable) {
// We're on a new renderable, invalidate objects constructed for the previous one.
clipMaskPipelineState.reset();
clipMaskDepthStencilState.reset();
stencilStateRenderable = &renderable;
}
// Create the depth-stencil state, if necessary.
if (!clipMaskDepthStencilState) {
if (auto depthStencilState = makeDepthStencilState(clipMaskDepthMode, clipMaskStencilMode, renderable)) {
clipMaskDepthStencilState = std::move(depthStencilState);
}
}
assert(clipMaskDepthStencilState || !"Failed to create depth-stencil state for clip masking");
mtlRenderPass.setDepthStencilState(clipMaskDepthStencilState);
if (!clipMaskPipelineState) {
// A vertex descriptor tells Metal what's in the vertex buffer
auto vertDesc = NS::RetainPtr(MTL::VertexDescriptor::vertexDescriptor());
auto attribDesc = NS::TransferPtr(MTL::VertexAttributeDescriptor::alloc()->init());
auto layoutDesc = NS::TransferPtr(MTL::VertexBufferLayoutDescriptor::alloc()->init());
if (!vertDesc || !attribDesc || !layoutDesc) {
return false;
}
attribDesc->setBufferIndex(ShaderClass::attributes[0].index);
attribDesc->setOffset(0);
attribDesc->setFormat(MTL::VertexFormatShort2);
layoutDesc->setStride(static_cast<NS::UInteger>(vertexSize));
layoutDesc->setStepFunction(MTL::VertexStepFunctionPerVertex);
layoutDesc->setStepRate(1);
vertDesc->attributes()->setObject(attribDesc.get(), ShaderClass::attributes[0].index);
vertDesc->layouts()->setObject(layoutDesc.get(), ShaderClass::attributes[0].index);
// Create a render pipeline state, telling Metal how to render the primitives
const std::size_t hash = mbgl::util::hash(ShaderClass::attributes[0].index,
0,
MTL::VertexFormatShort2,
vertexSize,
MTL::VertexStepFunctionPerVertex,
1);
if (auto state = mtlShader.getRenderPipelineState(
renderable, vertDesc, colorMode, mbgl::util::hash(colorMode.hash(), hash))) {
clipMaskPipelineState = std::move(state);
}
}
if (clipMaskPipelineState) {
encoder->setRenderPipelineState(clipMaskPipelineState.get());
} else {
assert(!"Failed to create render pipeline state for clip masking");
return false;
}
// Create a buffer for the UBO data.
constexpr auto uboSize = sizeof(shaders::ClipUBO);
const auto bufferSize = tileUBOs.size() * uboSize;
std::optional<BufferResource> tempBuffer;
auto& uboBuffer = clipMaskUniformsBufferUsed ? tempBuffer : clipMaskUniformsBuffer;
clipMaskUniformsBufferUsed = true;
if (!uboBuffer || !*uboBuffer || uboBuffer->getSizeInBytes() < bufferSize) {
uboBuffer = createBuffer(tileUBOs.data(),
bufferSize,
gfx::BufferUsageType::StaticDraw,
/*isIndexBuffer=*/false,
/*persistent=*/!clipMaskUniformsBufferUsed);
if (!uboBuffer) {
return false;
}
} else {
uboBuffer->update(tileUBOs.data(), bufferSize, /*offset=*/0);
}
encoder->setCullMode(MTL::CullModeNone);
mtlRenderPass.bindVertex(vertexRes, /*offset=*/0, ShaderClass::attributes[0].index);
// Instancing is disabled for now because the `[[stencil]]` attribute in the fragment shader output
// that we need to apply a different stencil value for each tile causes a problem on some older (A8-A11)
// devices, specifically `XPC_ERROR_CONNECTION_INTERRUPTED` when creating a render pipeline state.
// Adding a `[[depth(...)]]` output to the shader prevents this error, but the stencil value is
// still not written to the stencil attachment on those same devices.
#if STENCIL_INSTANCING
encoder->setVertexBuffer(uboBuffer.getMetalBuffer().get(), /*offset=*/0, shaders::idClippingMaskUBO);
encoder->drawIndexedPrimitives(MTL::PrimitiveType::PrimitiveTypeTriangle,
indexCount,
MTL::IndexType::IndexTypeUInt16,
indexRes->getMetalBuffer().get(),
/*indexOffset=*/0,
/*instanceCount=*/static_cast<NS::UInteger>(tileUBOs.size()));
#else
for (std::size_t ii = 0; ii < tileUBOs.size(); ++ii) {
encoder->setStencilReferenceValue(tileUBOs[ii].stencil_ref);
mtlRenderPass.bindVertex(*uboBuffer, /*offset=*/ii * uboSize, shaders::idClippingMaskUBO, /*size=*/uboSize);
encoder->drawIndexedPrimitives(MTL::PrimitiveType::PrimitiveTypeTriangle,
indexCount,
MTL::IndexType::IndexTypeUInt16,
indexRes->getMetalBuffer().get(),
/*indexOffset=*/0,
/*instanceCount=*/1);
}
#endif
stats.numDrawCalls++;
stats.totalDrawCalls++;
return true;
}
void Context::setDirtyState() {}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size,
gfx::TextureChannelDataType type,
bool depth,
bool stencil) {
return std::make_unique<OffscreenTexture>(*this, size, type, depth, stencil);
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) {
return createOffscreenTexture(size, type, false, false);
}
std::unique_ptr<gfx::TextureResource> Context::createTextureResource(Size,
gfx::TexturePixelType,
gfx::TextureChannelDataType) {
assert(false);
return nullptr;
}
std::unique_ptr<gfx::RenderbufferResource> Context::createRenderbufferResource(gfx::RenderbufferPixelType, Size) {
return std::make_unique<RenderbufferResource>();
}
std::unique_ptr<gfx::DrawScopeResource> Context::createDrawScopeResource() {
assert(false);
return nullptr;
}
gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const {
return std::make_shared<VertexAttributeArray>();
}
#if !defined(NDEBUG)
void Context::visualizeStencilBuffer() {}
void Context::visualizeDepthBuffer(float) {}
#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 <gfx::StencilFunctionType F>
void operator()(const gfx::StencilMode::SimpleTest<F>& mode) {
apply(mode.func, std::numeric_limits<uint32_t>::max());
}
template <gfx::StencilFunctionType F>
void operator()(const gfx::StencilMode::MaskedTest<F>& 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 gfx::Renderable& renderable) const {
auto depthStencilDescriptor = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init());
if (!depthStencilDescriptor) {
return {};
}
auto& device = backend.getDevice();
const auto& renderableResource = renderable.getResource<RenderableResource>();
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 (depthTarget->texture()) {
applyDepthMode(depthMode, depthStencilDescriptor.get());
}
}
if (auto* stencilTarget = rpd->stencilAttachment()) {
if (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()));
}
void Context::bindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept {
auto& mtlRenderPass = static_cast<mtl::RenderPass&>(renderPass);
globalUniformBuffers.bindMtl(mtlRenderPass);
}
} // namespace mtl
} // namespace mbgl