Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/mln/gfx/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ class Context {
/// Create a new vertex attribute array
virtual gfx::VertexAttributeArrayPtr createVertexAttributeArray() const = 0;

/// A shared 1x1 zero texture to bind wherever a shader declares a sampler the current
/// frame has no real texture for (e.g. the DEM / terrain-depth slots of the symbol, circle
/// and fill-extrusion shaders when 3D terrain is off). Metal's API validation aborts on an
/// unbound sampler even when the shader never samples it; Vulkan already binds a dummy.
const Texture2DPtr& getPlaceholderTexture2D();

/// Create a new drawable builder
virtual UniqueDrawableBuilder createDrawableBuilder(std::string name) = 0;

Expand Down Expand Up @@ -231,6 +237,7 @@ class Context {
std::shared_mutex renderingStatsMutex;
gfx::RenderingStats stats;
ContextObserver* observer;
Texture2DPtr placeholderTexture2D;

// Progressive new-tile build budget (see resetNewTileBuildBudget). Defaults to
// effectively unlimited so any path that never resets it is unaffected.
Expand Down
22 changes: 22 additions & 0 deletions src/mln/gfx/drawable.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include <cstring>
#include <mln/util/image.hpp>
#include <mln/gfx/texture2d.hpp>
#include <mln/gfx/context.hpp>
#include <mln/gfx/drawable.hpp>

#include <mln/gfx/color_mode.hpp>
Expand Down Expand Up @@ -92,3 +96,21 @@ void Drawable::setRenderTile(Immutable<std::vector<RenderTile>> renderTiles_, co

} // namespace gfx
} // namespace mln

namespace mln {
namespace gfx {

const Texture2DPtr& Context::getPlaceholderTexture2D() {
if (!placeholderTexture2D) {
auto image = std::make_shared<PremultipliedImage>(Size{1, 1});
std::memset(image->data.get(), 0, image->bytes());
placeholderTexture2D = createTexture2D();
placeholderTexture2D->setImage(image);
placeholderTexture2D->setSamplerConfiguration(
{.filter = TextureFilterType::Nearest, .wrapU = TextureWrapType::Clamp, .wrapV = TextureWrapType::Clamp});
}
return placeholderTexture2D;
}

} // namespace gfx
} // namespace mln
3 changes: 3 additions & 0 deletions src/mln/renderer/layers/circle_layer_tweaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
drawable.setTexture(
terrainData ? terrainData->demTexture : parameters.terrain->getPlaceholderDEMTexture(context),
idCircleDEMTexture);
} else {
// Keep the declared DEM sampler bound for Metal API validation (never sampled).
drawable.setTexture(context.getPlaceholderTexture2D(), idCircleDEMTexture);
}

// The terrain surface writes depth (so its skirts get occluded); circles
Expand Down
3 changes: 3 additions & 0 deletions src/mln/renderer/layers/fill_extrusion_layer_tweaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP
drawable.setTexture(
terrainData ? terrainData->demTexture : parameters.terrain->getPlaceholderDEMTexture(context),
idFillExtrusionDEMTexture);
} else {
// Keep the declared DEM sampler bound for Metal API validation (never sampled).
drawable.setTexture(context.getPlaceholderTexture2D(), idFillExtrusionDEMTexture);
}

#if MLN_UBO_CONSOLIDATION
Expand Down
6 changes: 6 additions & 0 deletions src/mln/renderer/layers/symbol_layer_tweaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
idSymbolDEMTexture);
// Packed terrain depth for occlusion (calculate_visibility)
drawable.setTexture(parameters.terrain->getDepthTexture(context), idSymbolDepthTexture);
} else {
// The shader declares both samplers regardless; leaving them unbound trips Metal's
// API validation (missing sampler binding) even though dem_enabled / depth_enabled
// are 0 and nothing is sampled.
drawable.setTexture(context.getPlaceholderTexture2D(), idSymbolDEMTexture);
drawable.setTexture(context.getPlaceholderTexture2D(), idSymbolDepthTexture);
}

// The terrain surface writes depth so its skirts get occluded; symbols
Expand Down
26 changes: 19 additions & 7 deletions src/mln/shaders/mtl/shader_program.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <mln/shaders/mtl/shader_program.hpp>
#include <mln/util/hash.hpp>

#include <mln/gfx/render_pass.hpp>
#include <mln/mtl/context.hpp>
Expand Down Expand Up @@ -84,12 +85,6 @@ MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Rende
const MTLVertexDescriptorPtr& vertexDescriptor,
const gfx::ColorMode& colorMode,
const std::optional<std::size_t> reuseHash) const {
if (reuseHash.has_value()) {
// we'd like to reuse a previous value
if (auto it = renderPipelineStateCache.find(reuseHash.value()); it != renderPipelineStateCache.end())
return it->second;
}

auto pool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init());

const auto& renderableResource = renderable.getResource<RenderableResource>();
Expand All @@ -115,6 +110,23 @@ MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Rende
}
}

// A pipeline state is only valid for the attachment formats it was built with. The caller's
// reuse hash covers the colour mode and vertex layout; fold the renderable's colour, depth
// and stencil formats in too, or a state built for the BGRA8 + stencil screen gets reused for
// an RGBA8 offscreen target (terrain drape targets, hillshade prepare targets) and Metal's
// API validation aborts on the mismatch.
std::optional<std::size_t> cacheKey;
if (reuseHash.has_value()) {
cacheKey = mln::util::hash(
reuseHash.value(),
static_cast<std::size_t>(colorFormat),
static_cast<std::size_t>(depthFormat.value_or(MTL::PixelFormat::PixelFormatInvalid)),
static_cast<std::size_t>(stencilFormat.value_or(MTL::PixelFormat::PixelFormatInvalid)));
if (auto it = renderPipelineStateCache.find(*cacheKey); it != renderPipelineStateCache.end()) {
return it->second;
}
}

auto desc = NS::TransferPtr(MTL::RenderPipelineDescriptor::alloc()->init());
desc->setLabel(NS::String::string(shaderName.data(), NS::UTF8StringEncoding));
desc->setVertexFunction(vertexFunction.get());
Expand Down Expand Up @@ -173,7 +185,7 @@ MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Rende

if (reuseHash.has_value()) {
// store the value for future reuse
renderPipelineStateCache[reuseHash.value()] = rps;
renderPipelineStateCache[*cacheKey] = rps;
}

return rps;
Expand Down