From 2809961b099b3331b0fdddcde8c9d5c55f572622 Mon Sep 17 00:00:00 2001 From: yousifd Date: Wed, 19 Nov 2025 20:30:50 +0300 Subject: [PATCH] Allow setting frustum offset to not render edges of the screen (#3676) Co-authored-by: Bart Louwers --- bazel/core.bzl | 1 + include/mbgl/gfx/scissor_rect.hpp | 21 ++++ include/mbgl/gl/renderer_backend.hpp | 4 +- include/mbgl/map/map.hpp | 2 + include/mbgl/mtl/render_pass.hpp | 5 + include/mbgl/vulkan/pipeline.hpp | 4 + .../src/cpp/native_map_view.cpp | 11 +- .../src/cpp/native_map_view.hpp | 2 + .../maplibre/android/maps/MapLibreMap.java | 12 +++ .../org/maplibre/android/maps/NativeMap.java | 2 + .../maplibre/android/maps/NativeMapView.java | 11 ++ .../maplibre/android/maps/MapLibreMapTest.kt | 38 +++++++ .../default/src/mbgl/gl/headless_backend.cpp | 2 +- platform/ios/src/MLNMapView.h | 10 ++ platform/ios/src/MLNMapView.mm | 10 ++ .../ios/test/MLNMapViewFrustumOffsetTests.m | 97 ++++++++++++++++++ src/mbgl/gl/context.cpp | 8 +- src/mbgl/gl/context.hpp | 2 + src/mbgl/gl/drawable_gl.cpp | 2 + src/mbgl/gl/offscreen_texture.cpp | 2 +- src/mbgl/gl/renderer_backend.cpp | 8 +- src/mbgl/gl/value.cpp | 10 +- src/mbgl/gl/value.hpp | 5 +- src/mbgl/map/map.cpp | 9 ++ src/mbgl/map/transform.cpp | 8 ++ src/mbgl/map/transform.hpp | 4 + src/mbgl/map/transform_state.cpp | 17 ++- src/mbgl/map/transform_state.hpp | 9 ++ src/mbgl/mtl/drawable.cpp | 6 ++ src/mbgl/mtl/render_pass.cpp | 19 ++++ src/mbgl/renderer/paint_parameters.cpp | 6 +- src/mbgl/renderer/paint_parameters.hpp | 6 +- src/mbgl/renderer/render_target.cpp | 6 ++ src/mbgl/renderer/renderer_impl.cpp | 47 ++++++--- src/mbgl/vulkan/context.cpp | 2 + src/mbgl/vulkan/drawable.cpp | 2 + src/mbgl/vulkan/pipeline.cpp | 19 ++-- src/mbgl/webgpu/drawable.cpp | 6 ++ .../map/setFrustumOffset/after/expected.png | Bin 0 -> 1260 bytes .../map/setFrustumOffset/before/expected.png | Bin 0 -> 853 bytes test/map/map.test.cpp | 17 +++ test/util/offscreen_texture.test.cpp | 6 +- 42 files changed, 408 insertions(+), 50 deletions(-) create mode 100644 include/mbgl/gfx/scissor_rect.hpp create mode 100644 platform/ios/test/MLNMapViewFrustumOffsetTests.m create mode 100644 test/fixtures/map/setFrustumOffset/after/expected.png create mode 100644 test/fixtures/map/setFrustumOffset/before/expected.png diff --git a/bazel/core.bzl b/bazel/core.bzl index fe4ba981b..e431a9598 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -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", diff --git a/include/mbgl/gfx/scissor_rect.hpp b/include/mbgl/gfx/scissor_rect.hpp new file mode 100644 index 000000000..a26741ad9 --- /dev/null +++ b/include/mbgl/gfx/scissor_rect.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +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 diff --git a/include/mbgl/gl/renderer_backend.hpp b/include/mbgl/gl/renderer_backend.hpp index b95988589..494bdae9e 100644 --- a/include/mbgl/gl/renderer_backend.hpp +++ b/include/mbgl/gl/renderer_backend.hpp @@ -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 diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 06b4f29fd..a614b7bd7 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -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 diff --git a/include/mbgl/mtl/render_pass.hpp b/include/mbgl/mtl/render_pass.hpp index c7a94821b..49ffbf10b 100644 --- a/include/mbgl/mtl/render_pass.hpp +++ b/include/mbgl/mtl/render_pass.hpp @@ -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 diff --git a/include/mbgl/vulkan/pipeline.hpp b/include/mbgl/vulkan/pipeline.hpp index 69c909e09..cc53b4f1d 100644 --- a/include/mbgl/vulkan/pipeline.hpp +++ b/include/mbgl/vulkan/pipeline.hpp @@ -6,6 +6,7 @@ #include #include #include +#include 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(); diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index bda6b12eb..4e71d68f1 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -1320,6 +1320,14 @@ void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) { map->enableRenderingStatsView(value); } +void NativeMapView::setFrustumOffset(JNIEnv& env, const jni::Object& 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&) {}; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index 2497925cb..2914c96d1 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -325,6 +325,8 @@ public: jni::jboolean isRenderingStatsViewEnabled(JNIEnv&); void enableRenderingStatsView(JNIEnv&, jni::jboolean); + void setFrustumOffset(JNIEnv&, const jni::Object&); + // Shader compilation void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override; void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java index cdbecdfd6..a732fce3c 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -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); } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java index 585f7ed33..044556460 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -270,6 +270,8 @@ interface NativeMap { void enableRenderingStatsView(boolean value); + void setFrustumOffset(RectF offset); + void setSwapBehaviorFlush(boolean flush); // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java index 571193bd4..67228a468 100755 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -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 // diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt index a75a4fd2c..d616f8ef9 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/MapLibreMapTest.kt @@ -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 { diff --git a/platform/default/src/mbgl/gl/headless_backend.cpp b/platform/default/src/mbgl/gl/headless_backend.cpp index 63674eee5..dfb1db842 100644 --- a/platform/default/src/mbgl/gl/headless_backend.cpp +++ b/platform/default/src/mbgl/gl/headless_backend.cpp @@ -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}; } diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index d4e7fc328..bf7acae4a 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -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 /** diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 7f6ef9266..f7631ce6e 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -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 diff --git a/platform/ios/test/MLNMapViewFrustumOffsetTests.m b/platform/ios/test/MLNMapViewFrustumOffsetTests.m new file mode 100644 index 000000000..432221124 --- /dev/null +++ b/platform/ios/test/MLNMapViewFrustumOffsetTests.m @@ -0,0 +1,97 @@ +#import +#import + +@interface MLNMapViewFrustumOffsetTests : XCTestCase + +@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 diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index b5a3856bd..b61474664 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -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; diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index d7757e15c..8fd9bee72 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index f2f9e2cfe..7fca47b45 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -60,6 +60,8 @@ void DrawableGL::draw(PaintParameters& parameters) const { context.setColorMode(getColorMode()); context.setCullFaceMode(getCullFaceMode()); + context.setScissorTest(parameters.scissorRect); + impl->uniformBuffers.bind(); bindTextures(); diff --git a/src/mbgl/gl/offscreen_texture.cpp b/src/mbgl/gl/offscreen_texture.cpp index e0da20dfc..90f71b414 100644 --- a/src/mbgl/gl/offscreen_texture.cpp +++ b/src/mbgl/gl/offscreen_texture.cpp @@ -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}; } diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index 3a8a31b9d..cb93e9d33 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -53,10 +53,10 @@ void RendererBackend::assumeViewport(int32_t x, int32_t y, const Size& size) { assert(gl::value::Viewport::Get() == getContext().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().scissorTest.setCurrentValue(enabled); + getContext().scissorTest.setCurrentValue({x, y, width, height}); assert(gl::value::ScissorTest::Get() == getContext().scissorTest.getCurrentValue()); } @@ -82,10 +82,10 @@ void RendererBackend::setViewport(int32_t x, int32_t y, const Size& size) { assert(gl::value::Viewport::Get() == getContext().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().scissorTest = enabled; + getContext().scissorTest = {x, y, width, height}; assert(gl::value::ScissorTest::Get() == getContext().scissorTest.getCurrentValue()); } diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index fe0840c72..0aa200e00 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -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(scissor[2]), static_cast(scissor[3])}; } const constexpr BindFramebuffer::Type BindFramebuffer::Default; diff --git a/src/mbgl/gl/value.hpp b/src/mbgl/gl/value.hpp index cea3136a0..e5f951fce 100644 --- a/src/mbgl/gl/value.hpp +++ b/src/mbgl/gl/value.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -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(); }; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 3f2b3c298..52aad7977 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -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(); diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index a16870706..8b641267c 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -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, diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 4828f372f..da72d69f7 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -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; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 50c075daf..add4ea3ae 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -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 { diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index ae4ce86ef..c7d75e5e7 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -85,6 +85,10 @@ struct TransformStateProperties { viewPortMode = val; return *this; } + TransformStateProperties withFrustumOffset(const std::optional& val) { + frustumOffset = val; + return *this; + } std::optional x; std::optional y; @@ -102,6 +106,7 @@ struct TransformStateProperties { std::optional constrain; std::optional northOrientation; std::optional viewPortMode; + std::optional 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; diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index f41d84d5c..5cd0da718 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -121,6 +121,10 @@ MTL::Winding mapWindingMode(const gfx::CullFaceWindingType mode) noexcept { } } +MTL::ScissorRect getMetalScissorRect(gfx::ScissorRect rect) noexcept { + return {static_cast(rect.x), static_cast(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, diff --git a/src/mbgl/mtl/render_pass.cpp b/src/mbgl/mtl/render_pass.cpp index d053016a9..760fde633 100644 --- a/src/mbgl/mtl/render_pass.cpp +++ b/src/mbgl/mtl/render_pass.cpp @@ -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 diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 432bb052f..fd4b9e214 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -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) { diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 9a0c94c67..a985ef557 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -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 diff --git a/src/mbgl/renderer/render_target.cpp b/src/mbgl/renderer/render_target.cpp index ef75e44be..dd2a5a499 100644 --- a/src/mbgl/renderer/render_target.cpp +++ b/src/mbgl/renderer/render_target.cpp @@ -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 diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index e20789009..e5b9dbc0d 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -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(frustumOffset.left() * pixelRatio), +#if MLN_RENDER_BACKEND_OPENGL + .y = static_cast(frustumOffset.bottom() * pixelRatio), +#else + .y = static_cast(frustumOffset.top() * pixelRatio), +#endif + .width = static_cast((size.width - (frustumOffset.left() + frustumOffset.right())) * pixelRatio), + .height = static_cast((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; diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp index 485be150b..26b6c5303 100644 --- a/src/mbgl/vulkan/context.cpp +++ b/src/mbgl/vulkan/context.cpp @@ -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 vertexBuffers = {clipping.vertexBuffer->getVulkanBuffer()}; diff --git a/src/mbgl/vulkan/drawable.cpp b/src/mbgl/vulkan/drawable.cpp index a063b1a34..e2eb2d498 100644 --- a/src/mbgl/vulkan/drawable.cpp +++ b/src/mbgl/vulkan/drawable.cpp @@ -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); diff --git a/src/mbgl/vulkan/pipeline.cpp b/src/mbgl/vulkan/pipeline.cpp index 400a871fa..b0a65e2aa 100644 --- a/src/mbgl/vulkan/pipeline.cpp +++ b/src/mbgl/vulkan/pipeline.cpp @@ -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 PipelineInfo::getDynamicStates(const RendererBackend& backend) const { @@ -450,10 +449,8 @@ std::vector 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; } diff --git a/src/mbgl/webgpu/drawable.cpp b/src/mbgl/webgpu/drawable.cpp index 73564db58..42756cf8c 100644 --- a/src/mbgl/webgpu/drawable.cpp +++ b/src/mbgl/webgpu/drawable.cpp @@ -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()) { diff --git a/test/fixtures/map/setFrustumOffset/after/expected.png b/test/fixtures/map/setFrustumOffset/after/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3143f759076d029c232b6a0198587fa85a6ddfb8 GIT binary patch literal 1260 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJEi&7LlfAr*7pUfL*lm_gvM z!TbFskAATma5JjsEqD%6P8zWLQmtMa`nSCAm%y!6|E%3>IdVh)&s}WSvTfD>bAj?s z(O2uAui#&}_SOF4EA1D;zTUTd<(#ql*L#_-id#mtjRwhRDj8uJhmW-K1*kgupR;n# Smv+4(uue}`KbLh*2~7YfJ%+si literal 0 HcmV?d00001 diff --git a/test/fixtures/map/setFrustumOffset/before/expected.png b/test/fixtures/map/setFrustumOffset/before/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf182174f01aa3cca50d9f932add770f449ae7e GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJD{8BZ6-kcv5PuPkI_Fc4_j zaJ&9xS&z8Ok;OM|%U#&=n&Ckoqr;3*G#UaUF9dF|7ax;-%+Ab`56mtMp00i_>zopr E0G5LC=l}o! literal 0 HcmV?d00001 diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index a44273b13..cddcab8a3 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -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("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); +} diff --git a/test/util/offscreen_texture.test.cpp b/test/util/offscreen_texture.test.cpp index e4e0fe69d..6cf2bfbf8 100644 --- a/test/util/offscreen_texture.test.cpp +++ b/test/util/offscreen_texture.test.cpp @@ -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(backend.getContext()).scissorTest.setCurrentValue(true); + static_cast(backend.getContext()).scissorTest.setCurrentValue({64, 64, 128, 128}); backend.getDefaultRenderable().getResource().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().bind();