Allow setting frustum offset to not render edges of the screen (#3676)

Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
yousifd
2025-11-19 20:30:50 +03:00
committed by GitHub
parent e37a39edcc
commit 2809961b09
42 changed files with 408 additions and 50 deletions

View File

@@ -639,6 +639,7 @@ MLN_CORE_HEADERS = [
"include/mbgl/gfx/stencil_mode.hpp",
"include/mbgl/gfx/types.hpp",
"include/mbgl/gfx/vertex_buffer.hpp",
"include/mbgl/gfx/scissor_rect.hpp",
"include/mbgl/i18n/collator.hpp",
"include/mbgl/i18n/number_format.hpp",
"include/mbgl/layermanager/background_layer_factory.hpp",

View File

@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
namespace mbgl {
namespace gfx {
class ScissorRect {
public:
int32_t x;
int32_t y;
uint32_t width;
uint32_t height;
bool operator==(const ScissorRect& other) const = default;
bool operator!=(const ScissorRect& other) const = default;
};
} // namespace gfx
} // namespace mbgl

View File

@@ -45,7 +45,7 @@ protected:
/// It sets the internal assumed state to the supplied values.
void assumeFramebufferBinding(FramebufferID fbo);
void assumeViewport(int32_t x, int32_t y, const Size&);
void assumeScissorTest(bool);
void assumeScissorTest(int32_t, int32_t, uint32_t, uint32_t);
/// Returns true when assumed framebuffer binding hasn't changed from the implicit binding.
bool implicitFramebufferBound();
@@ -55,7 +55,7 @@ public:
/// match the supplied values.
void setFramebufferBinding(FramebufferID fbo);
void setViewport(int32_t x, int32_t y, const Size&);
void setScissorTest(bool);
void setScissorTest(int32_t, int32_t, uint32_t, uint32_t);
};
} // namespace gl

View File

@@ -107,6 +107,8 @@ public:
void setConstrainMode(ConstrainMode);
void setViewportMode(ViewportMode);
void setSize(Size);
void setFrustumOffset(const EdgeInsets&);
EdgeInsets getFrustumOffset();
MapOptions getMapOptions() const;
// Projection Mode

View File

@@ -62,6 +62,7 @@ public:
void setCullMode(const MTL::CullMode);
void setFrontFacingWinding(const MTL::Winding);
void setScissorRect(const MTL::ScissorRect);
private:
void pushDebugGroup(const char* name) override;
@@ -92,6 +93,10 @@ private:
MTL::CullMode currentCullMode = MTL::CullModeNone;
MTL::Winding currentWinding = MTL::WindingClockwise;
MTL::ScissorRect currentScissorRect;
size_t width;
size_t height;
};
} // namespace mtl

View File

@@ -6,6 +6,7 @@
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/gfx/stencil_mode.hpp>
#include <mbgl/gfx/scissor_rect.hpp>
namespace mbgl {
namespace vulkan {
@@ -44,6 +45,7 @@ public:
// external values (used in hash)
vk::RenderPass renderPass{};
vk::Extent2D viewExtent{};
vk::Rect2D scissorRect{};
// dynamic values (not part of the pipeline/ignored in hash)
struct {
@@ -67,6 +69,7 @@ public:
static vk::CompareOp vulkanCompareOp(const gfx::DepthFunctionType& value);
static vk::CompareOp vulkanCompareOp(const gfx::StencilFunctionType& value);
static vk::StencilOp vulkanStencilOp(const gfx::StencilOpType& value);
static vk::Rect2D vulkanScissorRect(const gfx::ScissorRect& value);
void setCullMode(const gfx::CullFaceMode& value);
void setDrawMode(const gfx::DrawModeType& value);
@@ -78,6 +81,7 @@ public:
void setStencilMode(const gfx::StencilMode& value);
void setRenderable(const gfx::Renderable& value);
void setLineWidth(float value);
void setScissorRect(const gfx::ScissorRect& value);
bool usesBlendConstants() const;
void updateVertexInputHash();

View File

@@ -1320,6 +1320,14 @@ void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) {
map->enableRenderingStatsView(value);
}
void NativeMapView::setFrustumOffset(JNIEnv& env, const jni::Object<RectF>& padding) {
mbgl::EdgeInsets offset = {RectF::getTop(env, padding),
RectF::getLeft(env, padding),
RectF::getBottom(env, padding),
RectF::getRight(env, padding)};
map->setFrustumOffset(offset);
}
// Static methods //
void NativeMapView::registerNative(jni::JNIEnv& env) {
@@ -1442,7 +1450,8 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"),
METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"),
METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"),
METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"));
METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"),
METHOD(&NativeMapView::setFrustumOffset, "nativeSetFrustumOffset"));
}
void NativeMapView::onRegisterShaders(gfx::ShaderRegistry&) {};

View File

@@ -325,6 +325,8 @@ public:
jni::jboolean isRenderingStatsViewEnabled(JNIEnv&);
void enableRenderingStatsView(JNIEnv&, jni::jboolean);
void setFrustumOffset(JNIEnv&, const jni::Object<RectF>&);
// Shader compilation
void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override;
void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override;

View File

@@ -117,6 +117,18 @@ public final class MapLibreMap {
nativeMapView.enableRenderingStatsView(value);
}
/**
* Frustum offset used to disable rendering of elements at the edge of the screen
*
* Offset applied to camera frustum and scissor rectangle. The camrea frustum is modified
* to avoid loading geometry that's behind UI elements at the top of the screen. The scissor
* rectangle is used to avoid shading fragments that are behind UI elements at the edges of
* the screen. All values are in logical pixels.
*/
public void setFrustumOffset(@NonNull RectF offset) {
nativeMapView.setFrustumOffset(offset);
}
public void setSwapBehaviorFlush(boolean flush) {
nativeMapView.setSwapBehaviorFlush(flush);
}

View File

@@ -270,6 +270,8 @@ interface NativeMap {
void enableRenderingStatsView(boolean value);
void setFrustumOffset(RectF offset);
void setSwapBehaviorFlush(boolean flush);
//

View File

@@ -1149,6 +1149,14 @@ final class NativeMapView implements NativeMap {
nativeEnableRenderingStatsView(value);
}
@Override
public void setFrustumOffset(RectF offset) {
if (checkState("setFrustumOffset")) {
return;
}
nativeSetFrustumOffset(offset);
}
@Override
public void setSwapBehaviorFlush(boolean flush) {
mapRenderer.setSwapBehaviorFlush(flush);
@@ -1746,6 +1754,9 @@ final class NativeMapView implements NativeMap {
@Keep
private native void nativeEnableRenderingStatsView(boolean enabled);
@Keep
private native void nativeSetFrustumOffset(RectF offsset);
//
// Snapshot
//

View File

@@ -497,6 +497,44 @@ class MapLibreMapTest : EspressoTest() {
}
}
@Test
fun testSetFrustumOffset() {
validateTestSetup()
rule.runOnUiThread {
// RectF(left, top, right, bottom) - frustum offset represents insets in pixels
val offset = android.graphics.RectF(20f, 50f, 30f, 40f)
maplibreMap.setFrustumOffset(offset)
// Verify that setFrustumOffset was called without throwing an exception
// Since there's no getter, we can only verify it doesn't crash
}
}
@Test
fun testSetFrustumOffsetZero() {
validateTestSetup()
rule.runOnUiThread {
val zeroOffset = android.graphics.RectF(0f, 0f, 0f, 0f)
maplibreMap.setFrustumOffset(zeroOffset)
// Verify that setFrustumOffset with zero values works
}
}
@Test
fun testSetFrustumOffsetMultipleChanges() {
validateTestSetup()
rule.runOnUiThread {
val firstOffset = android.graphics.RectF(10f, 20f, 30f, 40f)
maplibreMap.setFrustumOffset(firstOffset)
val secondOffset = android.graphics.RectF(100f, 200f, 300f, 400f)
maplibreMap.setFrustumOffset(secondOffset)
val zeroOffset = android.graphics.RectF(0f, 0f, 0f, 0f)
maplibreMap.setFrustumOffset(zeroOffset)
// Verify that multiple changes to frustum offset work correctly
}
}
@Deprecated("remove this class when removing deprecated annotations")
inner class MapLibreMapAction internal constructor(private val invokeViewAction: InvokeViewAction) : ViewAction {
override fun getConstraints(): Matcher<View> {

View File

@@ -24,7 +24,7 @@ public:
void bind() override {
context.bindFramebuffer = framebuffer.framebuffer;
context.scissorTest = false;
context.scissorTest = {0, 0, 0, 0};
context.viewport = {0, 0, framebuffer.size};
}

View File

@@ -532,6 +532,16 @@ MLN_EXPORT
*/
@property (nonatomic, assign) double tileLodZoomShift;
/**
Frustum offset used to disable rendering of elements at the edge of the screen
Offset applied to camera frustum and scissor rectangle. The camrea frustum is modified
to avoid loading geometry that's behind UI elements at the top of the screen. The scissor
rectangle is used to avoid shading fragments that are behind UI elements at the edges of
the screen. All values are in logical pixels.
*/
@property (nonatomic, assign) UIEdgeInsets frustumOffset;
// MARK: Displaying the User’s Location
/**

View File

@@ -3327,6 +3327,16 @@ static void *windowScreenContext = &windowScreenContext;
return _mbglMap->getTileLodZoomShift();
}
-(void)setFrustumOffset:(UIEdgeInsets)frustomOffset
{
_mbglMap->setFrustumOffset(MLNEdgeInsetsFromNSEdgeInsets(frustomOffset));
}
-(UIEdgeInsets)frustumOffset
{
return NSEdgeInsetsFromMLNEdgeInsets(_mbglMap->getFrustumOffset());
}
// MARK: - Accessibility -
- (NSString *)accessibilityValue

View File

@@ -0,0 +1,97 @@
#import <Mapbox.h>
#import <XCTest/XCTest.h>
@interface MLNMapViewFrustumOffsetTests : XCTestCase <MLNMapViewDelegate>
@property (nonatomic) MLNMapView *mapView;
@property (nonatomic) XCTestExpectation *styleLoadingExpectation;
@end
@implementation MLNMapViewFrustumOffsetTests
- (void)setUp {
[super setUp];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 400, 400) styleURL:styleURL];
self.mapView.delegate = self;
if (!self.mapView.style) {
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
}
- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style {
XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);
[_styleLoadingExpectation fulfill];
}
- (void)tearDown {
self.mapView = nil;
[super tearDown];
}
- (void)testFrustumOffsetGetterSetter {
// Test default value (should be UIEdgeInsetsZero)
UIEdgeInsets defaultOffset = self.mapView.frustumOffset;
XCTAssertEqual(defaultOffset.top, 0.0);
XCTAssertEqual(defaultOffset.left, 0.0);
XCTAssertEqual(defaultOffset.bottom, 0.0);
XCTAssertEqual(defaultOffset.right, 0.0);
// Test setting a non-zero offset
UIEdgeInsets testOffset = UIEdgeInsetsMake(50.0, 40.0, 30.0, 20.0);
self.mapView.frustumOffset = testOffset;
UIEdgeInsets retrievedOffset = self.mapView.frustumOffset;
XCTAssertEqualWithAccuracy(retrievedOffset.top, testOffset.top, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.left, testOffset.left, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.bottom, testOffset.bottom, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.right, testOffset.right, 0.01);
}
- (void)testFrustumOffsetZeroInsets {
// Test setting zero insets
UIEdgeInsets zeroOffset = UIEdgeInsetsZero;
self.mapView.frustumOffset = zeroOffset;
UIEdgeInsets retrievedOffset = self.mapView.frustumOffset;
XCTAssertEqual(retrievedOffset.top, 0.0);
XCTAssertEqual(retrievedOffset.left, 0.0);
XCTAssertEqual(retrievedOffset.bottom, 0.0);
XCTAssertEqual(retrievedOffset.right, 0.0);
}
- (void)testFrustumOffsetMultipleChanges {
// Test changing the offset multiple times
UIEdgeInsets firstOffset = UIEdgeInsetsMake(10.0, 20.0, 30.0, 40.0);
self.mapView.frustumOffset = firstOffset;
UIEdgeInsets retrievedOffset = self.mapView.frustumOffset;
XCTAssertEqualWithAccuracy(retrievedOffset.top, 10.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.left, 20.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.bottom, 30.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.right, 40.0, 0.01);
UIEdgeInsets secondOffset = UIEdgeInsetsMake(100.0, 200.0, 300.0, 400.0);
self.mapView.frustumOffset = secondOffset;
retrievedOffset = self.mapView.frustumOffset;
XCTAssertEqualWithAccuracy(retrievedOffset.top, 100.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.left, 200.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.bottom, 300.0, 0.01);
XCTAssertEqualWithAccuracy(retrievedOffset.right, 400.0, 0.01);
// Test setting back to zero
UIEdgeInsets zeroOffset = UIEdgeInsetsZero;
self.mapView.frustumOffset = zeroOffset;
retrievedOffset = self.mapView.frustumOffset;
XCTAssertEqual(retrievedOffset.top, 0.0);
XCTAssertEqual(retrievedOffset.left, 0.0);
XCTAssertEqual(retrievedOffset.bottom, 0.0);
XCTAssertEqual(retrievedOffset.right, 0.0);
}
@end

View File

@@ -463,6 +463,7 @@ void Context::resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) {
setStencilMode(gfx::StencilMode::disabled());
setColorMode(colorMode);
setCullFaceMode(gfx::CullFaceMode::disabled());
setScissorTest({0, 0, 0, 0});
}
bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer,
@@ -495,7 +496,7 @@ void Context::unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept {
void Context::setDirtyState() {
MLN_TRACE_FUNC();
// Note: does not set viewport/scissorTest/bindFramebuffer to dirty
// Note: does not set viewport/bindFramebuffer to dirty
// since they are handled separately in the view object.
stencilFunc.setDirty();
stencilMask.setDirty();
@@ -518,6 +519,7 @@ void Context::setDirtyState() {
cullFace.setDirty();
cullFaceSide.setDirty();
cullFaceWinding.setDirty();
scissorTest.setDirty();
program.setDirty();
lineWidth.setDirty();
activeTextureUnit.setDirty();
@@ -642,6 +644,10 @@ void Context::setCullFaceMode(const gfx::CullFaceMode& mode) {
cullFaceWinding = mode.winding;
}
void Context::setScissorTest(const gfx::ScissorRect& rect) {
scissorTest = rect;
}
void Context::setDepthMode(const gfx::DepthMode& depth) {
if (depth.func == gfx::DepthFunctionType::Always && depth.mask != gfx::DepthMaskType::ReadWrite) {
depthTest = false;

View File

@@ -5,6 +5,7 @@
#include <mbgl/gfx/stencil_mode.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/scissor_rect.hpp>
#include <mbgl/gl/object.hpp>
#include <mbgl/gl/state.hpp>
#include <mbgl/gl/value.hpp>
@@ -77,6 +78,7 @@ public:
void setStencilMode(const gfx::StencilMode&);
void setColorMode(const gfx::ColorMode&);
void setCullFaceMode(const gfx::CullFaceMode&);
void setScissorTest(const gfx::ScissorRect&);
void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength);

View File

@@ -60,6 +60,8 @@ void DrawableGL::draw(PaintParameters& parameters) const {
context.setColorMode(getColorMode());
context.setCullFaceMode(getCullFaceMode());
context.setScissorTest(parameters.scissorRect);
impl->uniformBuffers.bind();
bindTextures();

View File

@@ -33,7 +33,7 @@ public:
}
context.activeTextureUnit = 0;
context.scissorTest = false;
context.scissorTest = {0, 0, 0, 0};
context.viewport = {.x = 0, .y = 0, .size = size};
}

View File

@@ -53,10 +53,10 @@ void RendererBackend::assumeViewport(int32_t x, int32_t y, const Size& size) {
assert(gl::value::Viewport::Get() == getContext<gl::Context>().viewport.getCurrentValue());
}
void RendererBackend::assumeScissorTest(bool enabled) {
void RendererBackend::assumeScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) {
MLN_TRACE_FUNC();
getContext<gl::Context>().scissorTest.setCurrentValue(enabled);
getContext<gl::Context>().scissorTest.setCurrentValue({x, y, width, height});
assert(gl::value::ScissorTest::Get() == getContext<gl::Context>().scissorTest.getCurrentValue());
}
@@ -82,10 +82,10 @@ void RendererBackend::setViewport(int32_t x, int32_t y, const Size& size) {
assert(gl::value::Viewport::Get() == getContext<gl::Context>().viewport.getCurrentValue());
}
void RendererBackend::setScissorTest(bool enabled) {
void RendererBackend::setScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) {
MLN_TRACE_FUNC();
getContext<gl::Context>().scissorTest = enabled;
getContext<gl::Context>().scissorTest = {x, y, width, height};
assert(gl::value::ScissorTest::Get() == getContext<gl::Context>().scissorTest.getCurrentValue());
}

View File

@@ -358,15 +358,17 @@ const constexpr ScissorTest::Type ScissorTest::Default;
void ScissorTest::Set(const Type& value) {
MLN_TRACE_ZONE(ScissorTest::Set);
MLN_TRACE_FUNC_GL();
MBGL_CHECK_ERROR(value ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST));
bool enabled = value.x != 0 || value.y != 0 || value.width != 0 || value.height != 0;
MBGL_CHECK_ERROR(enabled ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST));
MBGL_CHECK_ERROR(glScissor(value.x, value.y, value.width, value.height));
}
ScissorTest::Type ScissorTest::Get() {
MLN_TRACE_ZONE(ScissorTest::Get);
MLN_TRACE_FUNC_GL();
Type scissorTest;
MBGL_CHECK_ERROR(scissorTest = glIsEnabled(GL_SCISSOR_TEST));
return scissorTest;
GLint scissor[4];
MBGL_CHECK_ERROR(glGetIntegerv(GL_SCISSOR_BOX, scissor));
return {scissor[0], scissor[1], static_cast<uint32_t>(scissor[2]), static_cast<uint32_t>(scissor[3])};
}
const constexpr BindFramebuffer::Type BindFramebuffer::Default;

View File

@@ -5,6 +5,7 @@
#include <mbgl/gfx/stencil_mode.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/scissor_rect.hpp>
#include <mbgl/gl/attribute.hpp>
#include <mbgl/platform/gl_functions.hpp>
#include <mbgl/util/color.hpp>
@@ -189,8 +190,8 @@ struct Viewport {
};
struct ScissorTest {
using Type = bool;
static const constexpr Type Default = false;
using Type = gfx::ScissorRect;
static const constexpr Type Default = {0, 0, 0, 0};
static void Set(const Type&);
static Type Get();
};

View File

@@ -375,6 +375,15 @@ void Map::setSize(const Size size) {
impl->onUpdate();
}
void Map::setFrustumOffset(const EdgeInsets& frustumOffset) {
impl->transform.setFrustumOffset(frustumOffset);
impl->onUpdate();
}
EdgeInsets Map::getFrustumOffset() {
return impl->transform.getFrustumOffset();
}
void Map::setNorthOrientation(NorthOrientation orientation) {
impl->transform.setNorthOrientation(orientation);
impl->onUpdate();

View File

@@ -432,6 +432,14 @@ void Transform::setMaxPitch(const double maxPitch) {
state.setMaxPitch(util::deg2rad(maxPitch));
}
void Transform::setFrustumOffset(const EdgeInsets& frustumOffset) {
state.setFrustumOffset(frustumOffset);
}
EdgeInsets Transform::getFrustumOffset() {
return state.getFrustumOffset();
}
// MARK: - Bearing
void Transform::rotateBy(const ScreenCoordinate& first,

View File

@@ -132,6 +132,10 @@ public:
FreeCameraOptions getFreeCameraOptions() const;
void setFreeCameraOptions(const FreeCameraOptions& options);
// Frustum
void setFrustumOffset(const EdgeInsets&);
EdgeInsets getFrustumOffset();
private:
TransformObserver& observer;
TransformState state;

View File

@@ -88,6 +88,9 @@ void TransformState::setProperties(const TransformStateProperties& properties) {
if (properties.viewPortMode) {
setViewportMode(*properties.viewPortMode);
}
if (properties.frustumOffset) {
setFrustumOffset(*properties.frustumOffset);
}
}
// MARK: - Matrix
@@ -120,7 +123,8 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
// See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details.
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
const double tanFovAboveCenter = (size.height * 0.5 + offset.y) / (size.height * 1.5);
const double tanFovAboveCenter =
((size.height - frustumOffset.top()) * 0.5 + (offset.y - (frustumOffset.top() / 2))) / (size.height * 1.5);
const double tanMultiple = tanFovAboveCenter * std::tan(getPitch());
assert(tanMultiple < 1);
// Calculate z distance of the farthest fragment that should be rendered.
@@ -372,6 +376,17 @@ void TransformState::setSize(const Size& size_) {
}
}
EdgeInsets TransformState::getFrustumOffset() const {
return frustumOffset;
}
void TransformState::setFrustumOffset(const EdgeInsets& frustumOffset_) {
if (frustumOffset != frustumOffset_) {
frustumOffset = frustumOffset_;
requestMatricesUpdate = true;
}
}
// MARK: - North Orientation
NorthOrientation TransformState::getNorthOrientation() const {

View File

@@ -85,6 +85,10 @@ struct TransformStateProperties {
viewPortMode = val;
return *this;
}
TransformStateProperties withFrustumOffset(const std::optional<EdgeInsets>& val) {
frustumOffset = val;
return *this;
}
std::optional<double> x;
std::optional<double> y;
@@ -102,6 +106,7 @@ struct TransformStateProperties {
std::optional<ConstrainMode> constrain;
std::optional<NorthOrientation> northOrientation;
std::optional<ViewportMode> viewPortMode;
std::optional<EdgeInsets> frustumOffset;
};
class TransformState {
@@ -118,6 +123,9 @@ public:
Size getSize() const;
void setSize(const Size& size_);
EdgeInsets getFrustumOffset() const;
void setFrustumOffset(const EdgeInsets& frustumOffset_);
// North Orientation
NorthOrientation getNorthOrientation() const;
double getNorthOrientationAngle() const;
@@ -246,6 +254,7 @@ private:
// logical dimensions
Size size;
EdgeInsets frustumOffset;
mat4 coordinatePointMatrix(const mat4& projMatrix) const;
mat4 getPixelMatrix() const;

View File

@@ -121,6 +121,10 @@ MTL::Winding mapWindingMode(const gfx::CullFaceWindingType mode) noexcept {
}
}
MTL::ScissorRect getMetalScissorRect(gfx::ScissorRect rect) noexcept {
return {static_cast<uint32_t>(rect.x), static_cast<uint32_t>(rect.y), rect.width, rect.height};
}
} // namespace
void Drawable::setColorMode(const gfx::ColorMode& value) {
@@ -215,6 +219,8 @@ void Drawable::draw(PaintParameters& parameters) const {
renderPass.setCullMode(cullMode.enabled ? mapCullMode(cullMode.side) : MTL::CullModeNone);
renderPass.setFrontFacingWinding(mapWindingMode(cullMode.winding));
renderPass.setScissorRect(getMetalScissorRect(parameters.scissorRect));
if (!impl->pipelineState) {
impl->pipelineState = shaderMTL.getRenderPipelineState(
renderable,

View File

@@ -30,6 +30,10 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const
}
}
encoder = NS::RetainPtr(buffer->renderCommandEncoder(rpd.get()));
const auto& texture = rpd->colorAttachments()->object(0)->texture();
width = texture->width();
height = texture->height();
}
}
@@ -78,6 +82,7 @@ void RenderPass::resetState() {
currentCullMode = MTL::CullModeNone;
currentWinding = MTL::WindingClockwise;
currentScissorRect = {0, 0, 0, 0};
}
namespace {
@@ -216,5 +221,19 @@ void RenderPass::setFrontFacingWinding(const MTL::Winding winding) {
}
}
void RenderPass::setScissorRect(MTL::ScissorRect rect) {
if (rect.x != currentScissorRect.x || rect.y != currentScissorRect.y || rect.width != currentScissorRect.width ||
rect.height != currentScissorRect.height) {
if (rect.width + rect.x > width) {
rect.width = width - rect.x;
}
if (rect.height + rect.y > height) {
rect.height = height - rect.y;
}
encoder->setScissorRect(rect);
currentScissorRect = rect;
}
}
} // namespace mtl
} // namespace mbgl

View File

@@ -63,7 +63,8 @@ PaintParameters::PaintParameters(gfx::Context& context_,
uint64_t frameCount_,
double tileLodMinRadius_,
double tileLodScale_,
double tileLodPitchThreshold_)
double tileLodPitchThreshold_,
const gfx::ScissorRect& scissorRect_)
: context(context_),
backend(backend_),
encoder(context.createCommandEncoder()),
@@ -81,7 +82,8 @@ PaintParameters::PaintParameters(gfx::Context& context_,
frameCount(frameCount_),
tileLodMinRadius(tileLodMinRadius_),
tileLodScale(tileLodScale_),
tileLodPitchThreshold(tileLodPitchThreshold_) {
tileLodPitchThreshold(tileLodPitchThreshold_),
scissorRect(scissorRect_) {
pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}};
if (state.getViewportMode() == ViewportMode::FlippedY) {

View File

@@ -8,6 +8,7 @@
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/gfx/stencil_mode.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/scissor_rect.hpp>
#include <mbgl/util/mat4.hpp>
#include <array>
@@ -60,7 +61,8 @@ public:
uint64_t frameCount,
double tileLodMinRadius,
double tileLodScale,
double tileLodPitchThreshold);
double tileLodPitchThreshold,
const gfx::ScissorRect&);
~PaintParameters();
gfx::Context& context;
@@ -137,6 +139,8 @@ public:
double tileLodMinRadius;
double tileLodScale;
double tileLodPitchThreshold;
gfx::ScissorRect scissorRect;
};
} // namespace mbgl

View File

@@ -71,6 +71,10 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re
.clearDepth = {},
.clearStencil = {}});
const gfx::ScissorRect prevSicssorRect = parameters.scissorRect;
const auto& size = getTexture()->getSize();
parameters.scissorRect = {0, 0, size.width, size.height};
// Run layer tweakers to update any dynamic elements
parameters.currentLayer = 0;
visitLayerGroups([&](LayerGroupBase& layerGroup) {
@@ -104,6 +108,8 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re
parameters.renderPass.reset();
parameters.encoder->present(*offscreenTexture);
parameters.scissorRect = prevSicssorRect;
}
} // namespace mbgl

View File

@@ -184,21 +184,38 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
observer->onWillStartRenderingFrame();
PaintParameters parameters{context,
pixelRatio,
backend,
renderTreeParameters.light,
renderTreeParameters.mapMode,
renderTreeParameters.debugOptions,
renderTreeParameters.timePoint,
renderTreeParameters.transformParams,
*staticData,
renderTree.getLineAtlas(),
renderTree.getPatternAtlas(),
frameCount,
updateParameters->tileLodMinRadius,
updateParameters->tileLodScale,
updateParameters->tileLodPitchThreshold};
const TransformState& state = renderTreeParameters.transformParams.state;
const Size& size = state.getSize();
const EdgeInsets& frustumOffset = state.getFrustumOffset();
const gfx::ScissorRect scissorRect = {
.x = static_cast<int32_t>(frustumOffset.left() * pixelRatio),
#if MLN_RENDER_BACKEND_OPENGL
.y = static_cast<int32_t>(frustumOffset.bottom() * pixelRatio),
#else
.y = static_cast<int32_t>(frustumOffset.top() * pixelRatio),
#endif
.width = static_cast<uint32_t>((size.width - (frustumOffset.left() + frustumOffset.right())) * pixelRatio),
.height = static_cast<uint32_t>((size.height - (frustumOffset.top() + frustumOffset.bottom())) * pixelRatio),
};
PaintParameters parameters{
context,
pixelRatio,
backend,
renderTreeParameters.light,
renderTreeParameters.mapMode,
renderTreeParameters.debugOptions,
renderTreeParameters.timePoint,
renderTreeParameters.transformParams,
*staticData,
renderTree.getLineAtlas(),
renderTree.getPatternAtlas(),
frameCount,
updateParameters->tileLodMinRadius,
updateParameters->tileLodScale,
updateParameters->tileLodPitchThreshold,
scissorRect,
};
parameters.symbolFadeChange = renderTreeParameters.symbolFadeChange;
parameters.opaquePassCutoff = renderTreeParameters.opaquePassCutOff;

View File

@@ -560,6 +560,8 @@ bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass,
const auto& pipeline = shaderImpl.getPipeline(clipping.pipelineInfo);
commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get(), dispatcher);
clipping.pipelineInfo.setScissorRect(
{0, 0, clipping.pipelineInfo.viewExtent.width, clipping.pipelineInfo.viewExtent.height});
clipping.pipelineInfo.setDynamicValues(backend, commandBuffer);
const std::array<vk::Buffer, 1> vertexBuffers = {clipping.vertexBuffer->getVulkanBuffer()};

View File

@@ -298,6 +298,8 @@ void Drawable::draw(PaintParameters& parameters) const {
// update pipeline info with per segment modifiers
impl->pipelineInfo.setDrawMode(seg->getMode());
impl->pipelineInfo.setScissorRect(parameters.scissorRect);
impl->pipelineInfo.setDynamicValues(context.getBackend(), commandBuffer);
const auto& pipeline = shaderImpl.getPipeline(impl->pipelineInfo);

View File

@@ -6,8 +6,6 @@
namespace mbgl {
namespace vulkan {
#define USE_DYNAMIC_VIEWPORT 0
vk::Format PipelineInfo::vulkanFormat(const gfx::AttributeDataType& value) {
switch (value) {
case gfx::AttributeDataType::Byte:
@@ -110,6 +108,10 @@ vk::CullModeFlagBits PipelineInfo::vulkanCullMode(const gfx::CullFaceSideType& v
}
}
vk::Rect2D PipelineInfo::vulkanScissorRect(const gfx::ScissorRect& value) {
return {{value.x, value.y}, {value.width, value.height}};
}
vk::FrontFace PipelineInfo::vulkanFrontFace(const gfx::CullFaceWindingType& value) {
switch (value) {
default:
@@ -245,6 +247,10 @@ void PipelineInfo::setCullMode(const gfx::CullFaceMode& value) {
frontFace = vulkanFrontFace(value.winding);
}
void PipelineInfo::setScissorRect(const gfx::ScissorRect& value) {
scissorRect = vulkanScissorRect(value);
}
void PipelineInfo::setDrawMode(const gfx::DrawModeType& value) {
topology = vulkanPrimitiveTopology(value);
}
@@ -400,10 +406,6 @@ std::size_t PipelineInfo::hash() const {
stencilDepthFail,
wideLines,
VkRenderPass(renderPass),
#if !USE_DYNAMIC_VIEWPORT
viewExtent.width,
viewExtent.height,
#endif
vertexInputHash);
}
@@ -424,13 +426,10 @@ void PipelineInfo::setDynamicValues(const RendererBackend& backend, const vk::Un
buffer->setLineWidth(dynamicValues.lineWidth, dispatcher);
}
#if USE_DYNAMIC_VIEWPORT
const vk::Viewport viewport(0.0f, 0.0f, viewExtent.width, viewExtent.height, 0.0f, 1.0f);
const vk::Rect2D scissorRect({}, {viewExtent.width, viewExtent.height});
buffer->setViewport(0, viewport, dispatcher);
buffer->setScissor(0, scissorRect, dispatcher);
#endif
}
std::vector<vk::DynamicState> PipelineInfo::getDynamicStates(const RendererBackend& backend) const {
@@ -450,10 +449,8 @@ std::vector<vk::DynamicState> PipelineInfo::getDynamicStates(const RendererBacke
dynamicStates.push_back(vk::DynamicState::eLineWidth);
}
#if USE_DYNAMIC_VIEWPORT
dynamicStates.push_back(vk::DynamicState::eViewport);
dynamicStates.push_back(vk::DynamicState::eScissor);
#endif
return dynamicStates;
}

View File

@@ -841,6 +841,12 @@ void Drawable::draw(PaintParameters& parameters) const {
return;
}
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder,
parameters.scissorRect.x,
parameters.scissorRect.y,
parameters.scissorRect.width,
parameters.scissorRect.height);
uint32_t bufferSlot = 0;
for (const auto& binding : uniqueBindings) {
if (!binding.buffer || !binding.buffer->getBuffer()) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

View File

@@ -38,6 +38,7 @@
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/run_loop.hpp>
@@ -2020,3 +2021,19 @@ TEST(Map, LineLayerDepthDistribution) {
test::checkImage(
"test/fixtures/map/layer_depth_distribution/line", test.frontend.render(test.map).image, 0.0006, 0.1);
}
TEST(Map, SetFrustumOffset) {
MapTest<> test;
test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
auto layer = std::make_unique<BackgroundLayer>("background");
layer->setBackgroundColor({{1, 0, 0, 1}});
test.map.getStyle().addLayer(std::move(layer));
test::checkImage("test/fixtures/map/setFrustumOffset/before", test.frontend.render(test.map).image, 0.0006, 0.1);
test.map.setFrustumOffset(EdgeInsets{50, 50, 50, 50});
test::checkImage("test/fixtures/map/setFrustumOffset/after", test.frontend.render(test.map).image, 0.0006, 0.1);
}

View File

@@ -24,8 +24,7 @@ TEST(OffscreenTexture, EmptyRed) {
gfx::BackendScope scope{backend};
// Scissor test shouldn't leak after gl::HeadlessBackend::bind().
MBGL_CHECK_ERROR(glScissor(64, 64, 128, 128));
static_cast<gl::Context&>(backend.getContext()).scissorTest.setCurrentValue(true);
static_cast<gl::Context&>(backend.getContext()).scissorTest.setCurrentValue({64, 64, 128, 128});
backend.getDefaultRenderable().getResource<gl::RenderableResource>().bind();
@@ -146,8 +145,7 @@ void main() {
gl::OffscreenTexture offscreenTexture(context, {128, 128});
// Scissor test shouldn't leak after OffscreenTexture::bind().
MBGL_CHECK_ERROR(glScissor(32, 32, 64, 64));
context.scissorTest.setCurrentValue(true);
context.scissorTest.setCurrentValue({32, 32, 64, 64});
offscreenTexture.getResource<gl::RenderableResource>().bind();