core: Tolerate stencil clipping setup failures (#4317)

This commit is contained in:
Sargun Vohra
2026-07-13 09:51:24 -07:00
committed by GitHub
parent 58538f0bcd
commit 945ffff229
12 changed files with 168 additions and 73 deletions

View File

@@ -3,7 +3,6 @@
#define GL_GLEXT_PROTOTYPES #define GL_GLEXT_PROTOTYPES
#include <GLES3/gl3.h> #include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
namespace mbgl { namespace mbgl {
namespace platform { namespace platform {

View File

@@ -5,10 +5,11 @@
#include <mbgl/shaders/attributes.hpp> #include <mbgl/shaders/attributes.hpp>
#include <mbgl/util/literal.hpp> #include <mbgl/util/literal.hpp>
#include <vector> #include <algorithm>
#include <string>
#include <optional> #include <optional>
#include <string>
#include <type_traits> #include <type_traits>
#include <vector>
namespace mbgl { namespace mbgl {
namespace gl { namespace gl {
@@ -34,9 +35,12 @@ public:
void queryLocations(const ProgramID& id) { void queryLocations(const ProgramID& id) {
locations = Locations{ locations = Locations{
queryLocation(id, concat_literals<&string_literal<'a', '_'>::value, &As::name>::value())...}; queryLocation(id, concat_literals<&string_literal<'a', '_'>::value, &As::name>::value())...};
}
bool hasExpectedFirstLocation() const {
using TypeOfFirst = typename std::tuple_element_t<0, std::tuple<As...>>; using TypeOfFirst = typename std::tuple_element_t<0, std::tuple<As...>>;
[[maybe_unused]] auto first = locations.template get<TypeOfFirst>(); const auto location = locations.template get<TypeOfFirst>();
assert(first && first.value() == 0); return location && *location == 0;
} }
static constexpr const char* getFirstAttribName() { static constexpr const char* getFirstAttribName() {
@@ -67,12 +71,24 @@ public:
AttributeBindingArray toBindingArray(const gfx::AttributeBindings<TypeList<As...>>& bindings) const { AttributeBindingArray toBindingArray(const gfx::AttributeBindings<TypeList<As...>>& bindings) const {
AttributeBindingArray result; AttributeBindingArray result;
result.resize(sizeof...(As));
std::optional<AttributeLocation> maxLocation;
auto updateMaxLocation = [&](const std::optional<AttributeLocation>& location) {
if (location) {
maxLocation = maxLocation ? std::max(*maxLocation, *location) : *location;
}
};
util::ignore({(updateMaxLocation(locations.template get<As>()), 0)...});
if (maxLocation) {
result.resize(*maxLocation + 1);
}
auto maybeAddBinding = [&](const std::optional<AttributeLocation>& location, auto maybeAddBinding = [&](const std::optional<AttributeLocation>& location,
const std::optional<gfx::AttributeBinding>& binding) { const std::optional<gfx::AttributeBinding>& binding) {
if (location) { if (location) {
result.at(*location) = binding; result[*location] = binding;
} }
}; };

View File

@@ -693,6 +693,12 @@ void Context::setStencilMode(const gfx::StencilMode& stencil) {
} }
} }
bool Context::hasStencilBuffer() const {
GLint bits = 0;
MBGL_CHECK_ERROR(glGetIntegerv(GL_STENCIL_BITS, &bits));
return bits > 0;
}
void Context::setColorMode(const gfx::ColorMode& color) { void Context::setColorMode(const gfx::ColorMode& color) {
if (color.blendFunction.is<gfx::ColorMode::Replace>()) { if (color.blendFunction.is<gfx::ColorMode::Replace>()) {
blend = false; blend = false;

View File

@@ -79,6 +79,7 @@ public:
void setColorMode(const gfx::ColorMode&); void setColorMode(const gfx::ColorMode&);
void setCullFaceMode(const gfx::CullFaceMode&); void setCullFaceMode(const gfx::CullFaceMode&);
void setScissorTest(const gfx::ScissorRect&); void setScissorTest(const gfx::ScissorRect&);
bool hasStencilBuffer() const;
void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength); void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength);

View File

@@ -243,7 +243,7 @@ gfx::ColorMode DrawableGL::makeColorMode(PaintParameters& parameters) const {
} }
gfx::StencilMode DrawableGL::makeStencilMode(PaintParameters& parameters) const { gfx::StencilMode DrawableGL::makeStencilMode(PaintParameters& parameters) const {
if (enableStencil) { if (enableStencil && parameters.stencilClippingAvailable) {
if (!is3D && tileID) { if (!is3D && tileID) {
return parameters.stencilModeForClipping(tileID->toUnwrapped()); return parameters.stencilModeForClipping(tileID->toUnwrapped());
} }

View File

@@ -64,7 +64,9 @@ void TileLayerGroupGL::render(RenderOrchestrator&, PaintParameters& parameters)
bool stencil3d = false; bool stencil3d = false;
gfx::StencilMode stencilMode3d; gfx::StencilMode stencilMode3d;
if (getDrawableCount()) { parameters.stencilClippingAvailable = parameters.renderTargetHasStencilBuffer;
if (getDrawableCount() && parameters.stencilClippingAvailable) {
MLN_TRACE_ZONE(clip masks); MLN_TRACE_ZONE(clip masks);
#if !defined(NDEBUG) #if !defined(NDEBUG)
const auto label_clip = getName() + (getName().empty() ? "" : "-") + "tile-clip-masks"; const auto label_clip = getName() + (getName().empty() ? "" : "-") + "tile-clip-masks";
@@ -90,10 +92,16 @@ void TileLayerGroupGL::render(RenderOrchestrator&, PaintParameters& parameters)
if (features3d) { if (features3d) {
stencilMode3d = stencil3d ? parameters.stencilModeFor3D() : gfx::StencilMode::disabled(); stencilMode3d = stencil3d ? parameters.stencilModeFor3D() : gfx::StencilMode::disabled();
} else if (stencilTiles && !stencilTiles->empty()) { } else if (stencilTiles && !stencilTiles->empty()) {
parameters.renderTileClippingMasks(stencilTiles); if (!parameters.renderTileClippingMasks(stencilTiles)) {
parameters.stencilClippingAvailable = false;
}
} }
} }
if (!parameters.stencilClippingAvailable) {
context.setStencilMode(gfx::StencilMode::disabled());
}
#if !defined(NDEBUG) #if !defined(NDEBUG)
const auto label_render = getName() + (getName().empty() ? "" : "-") + "render"; const auto label_render = getName() + (getName().empty() ? "" : "-") + "render";
const auto debugGroupRender = parameters.encoder->createDebugGroup(label_render.c_str()); const auto debugGroupRender = parameters.encoder->createDebugGroup(label_render.c_str());
@@ -167,6 +175,12 @@ void LayerGroupGL::render(RenderOrchestrator&, PaintParameters& parameters) {
return; return;
} }
auto& context = static_cast<gl::Context&>(parameters.context);
parameters.stencilClippingAvailable = parameters.renderTargetHasStencilBuffer;
if (!parameters.stencilClippingAvailable) {
context.setStencilMode(gfx::StencilMode::disabled());
}
bool bindUBOs = false; bool bindUBOs = false;
visitDrawables([&](gfx::Drawable& drawable) { visitDrawables([&](gfx::Drawable& drawable) {
if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) { if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) {

View File

@@ -12,6 +12,7 @@
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#if MLN_RENDER_BACKEND_OPENGL #if MLN_RENDER_BACKEND_OPENGL
#include <mbgl/gl/context.hpp>
#include <mbgl/shaders/gl/legacy/clipping_mask_program.hpp> #include <mbgl/shaders/gl/legacy/clipping_mask_program.hpp>
#endif #endif
@@ -95,6 +96,18 @@ PaintParameters::PaintParameters(gfx::Context& context_,
PaintParameters::~PaintParameters() = default; PaintParameters::~PaintParameters() = default;
#if MLN_RENDER_BACKEND_OPENGL
void PaintParameters::updateStencilBufferAvailability() {
auto& glContext = static_cast<gl::Context&>(context);
renderTargetHasStencilBuffer = glContext.hasStencilBuffer();
stencilClippingAvailable = renderTargetHasStencilBuffer;
if (!stencilClippingAvailable) {
glContext.setStencilMode(gfx::StencilMode::disabled());
}
}
#endif
mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) const { mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) const {
mat4 matrix; mat4 matrix;
state.matrixFor(matrix, tileID); state.matrixFor(matrix, tileID);
@@ -169,10 +182,14 @@ void PaintParameters::clearStencil() {
#endif #endif
} }
void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { bool PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) {
// We can avoid updating the mask if it already contains the same set of tiles. // We can avoid updating the mask if it already contains the same set of tiles.
if (!renderTiles || !renderPass || tileIDsCovered(renderTiles, tileClippingMaskIDs)) { if (!renderTiles || tileIDsCovered(renderTiles, tileClippingMaskIDs)) {
return; return true;
}
if (!renderPass) {
return false;
} }
tileClippingMaskIDs.clear(); tileClippingMaskIDs.clear();
@@ -292,11 +309,9 @@ void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) {
auto program = staticData.shaders->getLegacyGroup().get<ClippingMaskProgram>(); auto program = staticData.shaders->getLegacyGroup().get<ClippingMaskProgram>();
if (!program) { if (!program) {
return; return false;
} }
static_cast<gl::Context&>(context).renderingStats().stencilUpdates++;
const style::Properties<>::PossiblyEvaluated properties{}; const style::Properties<>::PossiblyEvaluated properties{};
const ClippingMaskProgram::Binders paintAttributeData(properties, 0); const ClippingMaskProgram::Binders paintAttributeData(properties, 0);
@@ -313,32 +328,38 @@ void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) {
continue; continue;
} }
program->draw(context, if (!program->draw(context,
*renderPass, *renderPass,
gfx::Triangles(), gfx::Triangles(),
gfx::DepthMode::disabled(), gfx::DepthMode::disabled(),
gfx::StencilMode{gfx::StencilMode::Always{}, gfx::StencilMode{gfx::StencilMode::Always{},
stencilID, stencilID,
0b11111111, 0b11111111,
gfx::StencilOpType::Keep, gfx::StencilOpType::Keep,
gfx::StencilOpType::Keep, gfx::StencilOpType::Keep,
gfx::StencilOpType::Replace}, gfx::StencilOpType::Replace},
gfx::ColorMode::disabled(), gfx::ColorMode::disabled(),
gfx::CullFaceMode::disabled(), gfx::CullFaceMode::disabled(),
*staticData.quadTriangleIndexBuffer, *staticData.quadTriangleIndexBuffer,
staticData.clippingMaskSegments, staticData.clippingMaskSegments,
ClippingMaskProgram::computeAllUniformValues( ClippingMaskProgram::computeAllUniformValues(
ClippingMaskProgram::LayoutUniformValues{ ClippingMaskProgram::LayoutUniformValues{
uniforms::matrix::Value(matrixForTile(tileID)), uniforms::matrix::Value(matrixForTile(tileID)),
}, },
paintAttributeData, paintAttributeData,
properties, properties,
static_cast<float>(state.getZoom())), static_cast<float>(state.getZoom())),
ClippingMaskProgram::computeAllAttributeBindings( ClippingMaskProgram::computeAllAttributeBindings(
*staticData.tileVertexBuffer, paintAttributeData, properties), *staticData.tileVertexBuffer, paintAttributeData, properties),
"clipping/" + util::toString(stencilID)); "clipping/" + util::toString(stencilID))) {
tileClippingMaskIDs.clear();
return false;
}
} }
static_cast<gl::Context&>(context).renderingStats().stencilUpdates++;
#endif // MLN_RENDER_BACKEND_OPENGL #endif // MLN_RENDER_BACKEND_OPENGL
return true;
} }
gfx::StencilMode PaintParameters::stencilModeForClipping(const UnwrappedTileID& tileID) const { gfx::StencilMode PaintParameters::stencilModeForClipping(const UnwrappedTileID& tileID) const {

View File

@@ -97,7 +97,20 @@ public:
// Stencil handling // Stencil handling
public: public:
void renderTileClippingMasks(const RenderTiles&); #if MLN_RENDER_BACKEND_OPENGL
/// Update cached stencil availability for the currently bound GL framebuffer.
void updateStencilBufferAvailability();
/// Whether the current GL render target has a stencil buffer.
bool renderTargetHasStencilBuffer = true;
/// Transient GL draw state for the current layer group. GL layer groups reset this from
/// `renderTargetHasStencilBuffer`, then disable it if clipping-mask setup fails.
bool stencilClippingAvailable = true;
#endif
/// @return True when clipping mask state is ready or no update was needed.
bool renderTileClippingMasks(const RenderTiles&);
/// Clear the stencil buffer, even if there are no tile masks (for 3D) /// Clear the stencil buffer, even if there are no tile masks (for 3D)
void clearStencil(); void clearStencil();

View File

@@ -70,6 +70,9 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re
.clearColor = Color{0.0f, 0.0f, 0.0f, 1.0f}, .clearColor = Color{0.0f, 0.0f, 0.0f, 1.0f},
.clearDepth = {}, .clearDepth = {},
.clearStencil = {}}); .clearStencil = {}});
#if MLN_RENDER_BACKEND_OPENGL
parameters.updateStencilBufferAvailability();
#endif
const gfx::ScissorRect prevScissorRect = parameters.scissorRect; const gfx::ScissorRect prevScissorRect = parameters.scissorRect;
const auto& size = getTexture()->getSize(); const auto& size = getTexture()->getSize();

View File

@@ -318,6 +318,9 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
const auto debugGroup(parameters.encoder->createDebugGroup("common-3d")); const auto debugGroup(parameters.encoder->createDebugGroup("common-3d"));
parameters.pass = RenderPass::Pass3D; parameters.pass = RenderPass::Pass3D;
#if MLN_RENDER_BACKEND_OPENGL
parameters.updateStencilBufferAvailability();
#endif
// TODO is this needed? // TODO is this needed?
// if (!parameters.staticData.depthRenderbuffer || // if (!parameters.staticData.depthRenderbuffer ||
@@ -368,6 +371,9 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
.clearColor = color, .clearColor = color,
.clearDepth = 1.0f, .clearDepth = 1.0f,
.clearStencil = 0}); .clearStencil = 0});
#if MLN_RENDER_BACKEND_OPENGL
parameters.updateStencilBufferAvailability();
#endif
} }
}; };

View File

@@ -91,7 +91,7 @@ public:
} }
template <class DrawMode> template <class DrawMode>
void draw(gfx::Context& context, bool draw(gfx::Context& context,
gfx::RenderPass& renderPass, gfx::RenderPass& renderPass,
const DrawMode& drawMode, const DrawMode& drawMode,
const gfx::DepthMode& depthMode, const gfx::DepthMode& depthMode,
@@ -106,7 +106,7 @@ public:
static_assert(Primitive == gfx::PrimitiveTypeOf<DrawMode>::value, "incompatible draw mode"); static_assert(Primitive == gfx::PrimitiveTypeOf<DrawMode>::value, "incompatible draw mode");
if (!programBase) { if (!programBase) {
return; return false;
} }
auto drawScopeIt = segment.drawScopes.find(layerID); auto drawScopeIt = segment.drawScopes.find(layerID);
@@ -114,23 +114,23 @@ public:
drawScopeIt = segment.drawScopes.emplace(layerID, context.createDrawScope()).first; drawScopeIt = segment.drawScopes.emplace(layerID, context.createDrawScope()).first;
} }
programBase->draw(context, return programBase->draw(context,
renderPass, renderPass,
drawMode, drawMode,
depthMode, depthMode,
stencilMode, stencilMode,
colorMode, colorMode,
cullFaceMode, cullFaceMode,
uniformValues, uniformValues,
drawScopeIt->second, drawScopeIt->second,
allAttributeBindings.offset(segment.vertexOffset), allAttributeBindings.offset(segment.vertexOffset),
indexBuffer, indexBuffer,
segment.indexOffset, segment.indexOffset,
segment.indexLength); segment.indexLength);
} }
template <class DrawMode> template <class DrawMode>
void draw(gfx::Context& context, bool draw(gfx::Context& context,
gfx::RenderPass& renderPass, gfx::RenderPass& renderPass,
const DrawMode& drawMode, const DrawMode& drawMode,
const gfx::DepthMode& depthMode, const gfx::DepthMode& depthMode,
@@ -145,7 +145,7 @@ public:
static_assert(Primitive == gfx::PrimitiveTypeOf<DrawMode>::value, "incompatible draw mode"); static_assert(Primitive == gfx::PrimitiveTypeOf<DrawMode>::value, "incompatible draw mode");
if (!programBase) { if (!programBase) {
return; return false;
} }
for (auto& segment : segments) { for (auto& segment : segments) {
@@ -155,20 +155,23 @@ public:
drawScopeIt = segment.drawScopes.emplace(layerID, context.createDrawScope()).first; drawScopeIt = segment.drawScopes.emplace(layerID, context.createDrawScope()).first;
} }
programBase->draw(context, if (!programBase->draw(context,
renderPass, renderPass,
drawMode, drawMode,
depthMode, depthMode,
stencilMode, stencilMode,
colorMode, colorMode,
cullFaceMode, cullFaceMode,
uniformValues, uniformValues,
drawScopeIt->second, drawScopeIt->second,
allAttributeBindings.offset(segment.vertexOffset), allAttributeBindings.offset(segment.vertexOffset),
indexBuffer, indexBuffer,
segment.indexOffset, segment.indexOffset,
segment.indexLength); segment.indexLength)) {
return false;
}
} }
return true;
} }
}; };

View File

@@ -15,6 +15,8 @@
#include <mbgl/util/io.hpp> #include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#include <map>
#include <set>
#include <string> #include <string>
namespace mbgl { namespace mbgl {
@@ -45,6 +47,10 @@ public:
context.createShader(ShaderType::Fragment, fragmentSource), context.createShader(ShaderType::Fragment, fragmentSource),
attributeLocations.getFirstAttribName())) { attributeLocations.getFirstAttribName())) {
attributeLocations.queryLocations(program); attributeLocations.queryLocations(program);
if (!attributeLocations.hasExpectedFirstLocation()) {
throw std::runtime_error("Shader is missing active attribute at location 0: " +
std::string(attributeLocations.getFirstAttribName()));
}
uniformStates.queryLocations(program); uniformStates.queryLocations(program);
} }
@@ -80,7 +86,7 @@ public:
gl::UniformStates<UniformList> uniformStates; gl::UniformStates<UniformList> uniformStates;
}; };
void draw(gfx::Context& genericContext, bool draw(gfx::Context& genericContext,
gfx::RenderPass&, gfx::RenderPass&,
const gfx::DrawMode& drawMode, const gfx::DrawMode& drawMode,
const gfx::DepthMode& depthMode, const gfx::DepthMode& depthMode,
@@ -101,6 +107,10 @@ public:
context.setCullFaceMode(cullFaceMode); context.setCullFaceMode(cullFaceMode);
const uint32_t key = gl::AttributeKey<AttributeList>::compute(attributeBindings); const uint32_t key = gl::AttributeKey<AttributeList>::compute(attributeBindings);
if (failedInstances.contains(key)) {
return false;
}
auto it = instances.find(key); auto it = instances.find(key);
if (it == instances.end()) { if (it == instances.end()) {
try { try {
@@ -112,7 +122,8 @@ public:
.first; .first;
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
Log::Error(Event::OpenGL, e.what()); Log::Error(Event::OpenGL, e.what());
return; failedInstances.insert(key);
return false;
} }
} }
@@ -125,10 +136,12 @@ public:
vertexArray.bind(context, indexBuffer, instance.attributeLocations.toBindingArray(attributeBindings)); vertexArray.bind(context, indexBuffer, instance.attributeLocations.toBindingArray(attributeBindings));
context.draw(drawMode, indexOffset, indexLength); context.draw(drawMode, indexOffset, indexLength);
return true;
} }
private: private:
std::map<uint32_t, std::unique_ptr<Instance>> instances; std::map<uint32_t, std::unique_ptr<Instance>> instances;
std::set<uint32_t> failedInstances;
}; };
} // namespace gl } // namespace gl