From 3f224685fa720d99bda2613d3e999e352b81a2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 23 Jan 2025 12:38:10 +0100 Subject: [PATCH 1/9] Only emit cameraChanged() on frame start Previously the entire camera change was handled on every input event, which led to slowdown and stutters, since we can get ~1000/s such events with common mice. We don't care about intermediate camera states and can only process the last state before rendering. --- src/3d/qgscameracontroller.cpp | 8 +++++++- src/3d/qgscameracontroller.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index b889a069a4e3..d13debfc5ead 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -161,6 +161,12 @@ void QgsCameraController::zoomCameraAroundPivot( const QVector3D &oldCameraPosit void QgsCameraController::frameTriggered( float dt ) { Q_UNUSED( dt ) + + if ( mCameraChanged ) + { + emit cameraChanged(); + mCameraChanged = false; + } } void QgsCameraController::resetView( float distance ) @@ -282,7 +288,7 @@ void QgsCameraController::updateCameraFromPose() if ( mCamera ) mCameraPose.updateCamera( mCamera ); - emit cameraChanged(); + mCameraChanged = true; } void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff ) diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 6bb158e7831f..08830805c12e 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -385,6 +385,9 @@ class _3D_EXPORT QgsCameraController : public QObject // 3D world's origin in map coordinates QgsVector3D mOrigin; + //! Did camera change since last frame? Need to know if we should emit cameraChanged(). + bool mCameraChanged = false; + // To test the cameracontroller friend class TestQgs3DRendering; friend class TestQgs3DCameraController; From cf5afa91aea6ad331d5d23548a79411f547f151b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 23 Jan 2025 14:53:02 +0100 Subject: [PATCH 2/9] Add tracing events to Qgs3DMapScene and QgsCameraController --- src/3d/qgs3dmapscene.cpp | 5 +++-- src/3d/qgscameracontroller.cpp | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 1845df0759c2..baa60913ef0c 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -334,8 +334,7 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) return; } - if ( forceUpdate ) - QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) ); + QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), forceUpdate ? QStringLiteral( "Force update scene" ) : QStringLiteral( "Update scene" ) ); Qgs3DMapSceneEntity::SceneContext sceneContext; Qt3DRender::QCamera *camera = mEngine->camera(); @@ -421,6 +420,8 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() void Qgs3DMapScene::onFrameTriggered( float dt ) { + QgsEventTracing::addEvent( QgsEventTracing::EventType::Instant, QStringLiteral( "3D" ), QStringLiteral( "Frame begins" ) ); + mCameraController->frameTriggered( dt ); updateScene(); diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index d13debfc5ead..fe97e31d206e 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -15,6 +15,7 @@ #include "qgscameracontroller.h" #include "moc_qgscameracontroller.cpp" +#include "qgseventtracing.h" #include "qgsvector3d.h" #include "qgswindow3dengine.h" #include "qgs3dmapscene.h" @@ -302,6 +303,8 @@ void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse ) if ( !mInputHandlersEnabled ) return; + QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) ); + switch ( mCameraNavigationMode ) { case Qgis::NavigationMode::TerrainBased: From 4852f781789be82ee4a21acba373e5d5d56f4855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 23 Jan 2025 14:48:44 +0100 Subject: [PATCH 3/9] Cache depth buffer average to improve performance Previously the average could be computed on every mouse move event. This is a problem, since the loop can be slow, especially on debug builds where nothing gets inlined. --- src/3d/qgscameracontroller.cpp | 21 ++++++++++++++------- src/3d/qgscameracontroller.h | 5 ++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index fe97e31d206e..01f636c6a0da 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -240,7 +240,7 @@ void QgsCameraController::readXml( const QDomElement &elem ) setLookingAtPoint( QgsVector3D( x, elev, y ), dist, pitch, yaw ); } -double QgsCameraController::sampleDepthBuffer( const QImage &buffer, int px, int py ) +double QgsCameraController::sampleDepthBuffer( int px, int py ) { double depth = 1; @@ -249,9 +249,9 @@ double QgsCameraController::sampleDepthBuffer( const QImage &buffer, int px, int { for ( int y = py - 3; y <= py + 3; ++y ) { - if ( buffer.valid( x, y ) ) + if ( mDepthBufferImage.valid( x, y ) ) { - depth = std::min( depth, Qgs3DUtils::decodeDepth( buffer.pixel( x, y ) ) ); + depth = std::min( depth, Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ) ); } } } @@ -259,14 +259,18 @@ double QgsCameraController::sampleDepthBuffer( const QImage &buffer, int px, int if ( depth < 1 ) return depth; + // Cache the computed depth, since averaging over all pixels can be expensive + if ( mDepthBufferNonVoidAverage != -1 ) + return mDepthBufferNonVoidAverage; + // Returns the average of depth values that are not 1 (void area) depth = 0; int samplesCount = 0; - for ( int x = 0; x < buffer.width(); ++x ) + for ( int x = 0; x < mDepthBufferImage.width(); ++x ) { - for ( int y = 0; y < buffer.height(); ++y ) + for ( int y = 0; y < mDepthBufferImage.height(); ++y ) { - double d = Qgs3DUtils::decodeDepth( buffer.pixel( x, y ) ); + double d = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ); if ( d < 1 ) { depth += d; @@ -281,6 +285,8 @@ double QgsCameraController::sampleDepthBuffer( const QImage &buffer, int px, int else depth /= samplesCount; + mDepthBufferNonVoidAverage = depth; + return depth; } @@ -319,7 +325,7 @@ void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse ) bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition ) { - depth = sampleDepthBuffer( mDepthBufferImage, position.x(), position.y() ); + depth = sampleDepthBuffer( position.x(), position.y() ); if ( !std::isfinite( depth ) ) { QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: depth is NaN or Inf. This should not happen." ), 2 ); @@ -1026,6 +1032,7 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage ) { mDepthBufferImage = depthImage; mDepthBufferIsReady = true; + mDepthBufferNonVoidAverage = -1; if ( mCurrentOperation == MouseOperation::ZoomWheel ) { diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index 08830805c12e..bd5c4466dfc6 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -326,7 +326,7 @@ class _3D_EXPORT QgsCameraController : public QObject * Returns the minimum depth value in the square [px - 3, px + 3] * [py - 3, py + 3] * If the value is 1, the average depth of all non void pixels is returned instead. */ - double sampleDepthBuffer( const QImage &buffer, int px, int py ); + double sampleDepthBuffer( int px, int py ); #ifndef SIP_RUN //! Converts screen point to world position @@ -350,6 +350,9 @@ class _3D_EXPORT QgsCameraController : public QObject bool mDepthBufferIsReady = false; QImage mDepthBufferImage; + // -1 when unset + // TODO: Change to std::optional + double mDepthBufferNonVoidAverage; std::unique_ptr mCameraBefore; From 25685b6dbd06177e83424ef6e201313e4f1c694a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Mon, 27 Jan 2025 16:48:51 +0100 Subject: [PATCH 4/9] Fix tests after 3D camera changes Part of the camera change handling now happens only once per frame, so we need to wait for a new frame to be rendered so the changes are applied. --- src/3d/qgs3dutils.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index db6010839948..8f86c1bc9a74 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) @@ -58,6 +59,17 @@ typedef Qt3DCore::QBuffer Qt3DQBuffer; // declared here as Qgs3DTypes has no cpp file const char *Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG = "PROP_NAME_3D_RENDERER_FLAG"; +static void waitForFrame( Qgs3DMapScene *scene ) +{ + // Wait for at least one frame to render + Qt3DLogic::QFrameAction *frameAction = new Qt3DLogic::QFrameAction(); + scene->addComponent( frameAction ); + QEventLoop evLoop; + QObject::connect( frameAction, &Qt3DLogic::QFrameAction::triggered, &evLoop, &QEventLoop::quit ); + evLoop.exec(); + scene->removeComponent( frameAction ); +} + QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ) { QImage resImage; @@ -66,13 +78,7 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene // We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always ); - auto requestImageFcn = [&engine, scene] { - if ( scene->sceneState() == Qgs3DMapScene::Ready ) - { - engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::OnDemand ); - engine.requestCaptureImage(); - } - }; + waitForFrame( scene ); auto saveImageFcn = [&evLoop, &resImage]( const QImage &img ) { resImage = img; @@ -82,6 +88,14 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene const QMetaObject::Connection conn1 = QObject::connect( &engine, &QgsAbstract3DEngine::imageCaptured, saveImageFcn ); QMetaObject::Connection conn2; + auto requestImageFcn = [&engine, scene] { + if ( scene->sceneState() == Qgs3DMapScene::Ready ) + { + engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::OnDemand ); + engine.requestCaptureImage(); + } + }; + if ( scene->sceneState() == Qgs3DMapScene::Ready ) { requestImageFcn(); @@ -110,6 +124,8 @@ QImage Qgs3DUtils::captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMa // We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always ); + waitForFrame( scene ); + auto requestImageFcn = [&engine, scene] { if ( scene->sceneState() == Qgs3DMapScene::Ready ) { From dc2600d486ee606f90a9691f4c6ed768b329bfa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Wed, 12 Feb 2025 14:52:09 +0100 Subject: [PATCH 5/9] Fix more tests --- src/3d/qgs3dutils.cpp | 14 +++++-- src/3d/qgs3dutils.h | 6 +++ src/3d/qgscameracontroller.cpp | 1 + tests/src/3d/testqgs3dcameracontroller.cpp | 46 ++++++++++++++-------- tests/src/3d/testqgs3drendering.cpp | 3 ++ 5 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 8f86c1bc9a74..18fdcf702b03 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -59,8 +59,12 @@ typedef Qt3DCore::QBuffer Qt3DQBuffer; // declared here as Qgs3DTypes has no cpp file const char *Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG = "PROP_NAME_3D_RENDERER_FLAG"; -static void waitForFrame( Qgs3DMapScene *scene ) +void Qgs3DUtils::waitForFrame( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ) { + // Set policy to always render frame, so we don't wait forever. + Qt3DRender::QRenderSettings::RenderPolicy oldPolicy = engine.renderSettings()->renderPolicy(); + engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always ); + // Wait for at least one frame to render Qt3DLogic::QFrameAction *frameAction = new Qt3DLogic::QFrameAction(); scene->addComponent( frameAction ); @@ -68,6 +72,8 @@ static void waitForFrame( Qgs3DMapScene *scene ) QObject::connect( frameAction, &Qt3DLogic::QFrameAction::triggered, &evLoop, &QEventLoop::quit ); evLoop.exec(); scene->removeComponent( frameAction ); + + engine.renderSettings()->setRenderPolicy( oldPolicy ); } QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ) @@ -78,7 +84,7 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene // We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always ); - waitForFrame( scene ); + waitForFrame( engine, scene ); auto saveImageFcn = [&evLoop, &resImage]( const QImage &img ) { resImage = img; @@ -124,8 +130,6 @@ QImage Qgs3DUtils::captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMa // We need to change render policy to RenderPolicy::Always, since otherwise render capture node won't work engine.renderSettings()->setRenderPolicy( Qt3DRender::QRenderSettings::RenderPolicy::Always ); - waitForFrame( scene ); - auto requestImageFcn = [&engine, scene] { if ( scene->sceneState() == Qgs3DMapScene::Ready ) { @@ -142,6 +146,8 @@ QImage Qgs3DUtils::captureSceneDepthBuffer( QgsAbstract3DEngine &engine, Qgs3DMa QMetaObject::Connection conn1 = QObject::connect( &engine, &QgsAbstract3DEngine::depthBufferCaptured, saveImageFcn ); QMetaObject::Connection conn2; + // Make sure once-per-frame functions run + waitForFrame( engine, scene ); if ( scene->sceneState() == Qgs3DMapScene::Ready ) { requestImageFcn(); diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index e2689ecb01ab..bda8d4d9ab64 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -65,6 +65,12 @@ class _3D_EXPORT Qgs3DUtils */ static QImage captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ); + /** + * Waits for a frame to be rendered. Useful to trigger once-per-frame updates + * \since QGIS 3.42 + */ + static void waitForFrame( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ); + /** * Captures the depth buffer of the current 3D scene of a 3D engine. The function waits * until the scene is not fully loaded/updated before capturing the image. diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 01f636c6a0da..1871bfb46a50 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -572,6 +572,7 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel ) if ( mCurrentOperation != MouseOperation::ZoomWheel ) { setMouseParameters( MouseOperation::ZoomWheel ); + // The actual zooming will happen after we get a new depth buffer } else { diff --git a/tests/src/3d/testqgs3dcameracontroller.cpp b/tests/src/3d/testqgs3dcameracontroller.cpp index 0b61369c09e7..ff871f7a88c2 100644 --- a/tests/src/3d/testqgs3dcameracontroller.cpp +++ b/tests/src/3d/testqgs3dcameracontroller.cpp @@ -52,6 +52,8 @@ class TestQgs3DCameraController : public QgsTest void testRotationCenterRotationCameraRotationCenter(); private: + void waitForNearPlane( QgsOffscreen3DEngine &engine, Qgs3DMapScene *scene, float atLeast ); //#spellok + QgsRasterLayer *mLayerRgb = nullptr; QgsVectorLayer *mLayerBuildings = nullptr; }; @@ -455,14 +457,13 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter() // look from the top scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + waitForNearPlane( engine, scene, 1000 ); + QVector3D initialCamViewCenter = scene->cameraController()->camera()->viewCenter(); QVector3D initialCamPosition = scene->cameraController()->camera()->position(); float initialPitch = scene->cameraController()->pitch(); float initialYaw = scene->cameraController()->yaw(); - // this call is not used but ensures to synchronize the scene - Qgs3DUtils::captureSceneImage( engine, scene ); - QMouseEvent mousePressEvent( QEvent::MouseButtonPress, midPos, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier ); scene->cameraController()->onMousePressed( new Qt3DInput::QMouseEvent( mousePressEvent ) ); @@ -522,9 +523,9 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter() depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); scene->cameraController()->depthBufferCaptured( depthImage ); - QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 283.2, -923.1, -27.0 ), 1.5 ); - QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( 99.4, -319.9, -8.8 ), 2.0 ); - QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1631.9, 2.0 ); + QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 312.936, -950.772, -125.381 ), 3.0 ); + QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( 109.8, -329.4, -43.3 ), 3.0 ); + QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1631.9, 3.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 ); @@ -540,6 +541,7 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter() initialPitch = scene->cameraController()->pitch(); initialYaw = scene->cameraController()->yaw(); + Qgs3DUtils::waitForFrame( engine, scene ); // the first mouse event only updates the mouse position // the second one will update the camera QMouseEvent mouseMoveEvent3( QEvent::MouseMove, midPos + movement1 + movement2, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier ); @@ -558,9 +560,9 @@ void TestQgs3DCameraController::testRotationCenterZoomWheelRotationCenter() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::RotationCenter ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 25.9, 7.1, 5.2 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 26.9, 7.3, 5.4 ), 2.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -44.3, -9.1, -11.7 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -43.2, -9.1, -11.1 ), 1.0 ); diffPitch = scene->cameraController()->pitch() - initialPitch; diffYaw = scene->cameraController()->yaw() - initialYaw; QGSCOMPARENEAR( diffPitch, 2.5, 0.1 ); @@ -598,14 +600,13 @@ void TestQgs3DCameraController::testTranslateRotationCenterTranslate() // look from the top scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + waitForNearPlane( engine, scene, 1000 ); + QVector3D initialCamViewCenter = scene->cameraController()->camera()->viewCenter(); QVector3D initialCamPosition = scene->cameraController()->camera()->position(); float initialPitch = scene->cameraController()->pitch(); float initialYaw = scene->cameraController()->yaw(); - // this call is not used but ensures to synchronize the scene - Qgs3DUtils::captureSceneImage( engine, scene ); - // // 1. Translate // @@ -751,14 +752,12 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate() // look from the top scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + waitForNearPlane( engine, scene, 1000 ); QVector3D initialCamViewCenter = scene->cameraController()->camera()->viewCenter(); QVector3D initialCamPosition = scene->cameraController()->camera()->position(); float initialPitch = scene->cameraController()->pitch(); float initialYaw = scene->cameraController()->yaw(); - // this call is not used but ensures to synchronize the scene - Qgs3DUtils::captureSceneImage( engine, scene ); - // // 1. Translate // @@ -857,9 +856,9 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -11.3, 11.3, 0.0 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -17.2, 17.2, 0.0 ), 1.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -11.3, 11.3, 0.0 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -17.2, 17.2, 0.0 ), 1.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -1173,5 +1172,20 @@ void TestQgs3DCameraController::testRotationCenterRotationCameraRotationCenter() mapSettings->setLayers( {} ); } +void TestQgs3DCameraController::waitForNearPlane( QgsOffscreen3DEngine &engine, Qgs3DMapScene *scene, float atLeast ) //#spellok +{ + // XXX: Sometimes the near/far planes aren't calculated correctly, so they're + // left at the too-deep default. This causes the rest of the test to fail in + // weird ways every once in a while, so loop until we get good values. + do + { + // Force recalcualtion of near/far planes. + scene->cameraController()->mCameraChanged = true; + + // this call is not used but ensures to synchronize the scene + Qgs3DUtils::captureSceneImage( engine, scene ); + } while ( scene->cameraController()->camera()->nearPlane() < atLeast ); //#spellok +} + QGSTEST_MAIN( TestQgs3DCameraController ) #include "testqgs3dcameracontroller.moc" diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index f129886a477f..583f4e637939 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -2114,6 +2114,7 @@ void TestQgs3DRendering::testDepthBuffer() QGSVERIFYIMAGECHECK( "depth_wheel_action_1", "depth_wheel_action_1", grayImage, QString(), 5, QSize( 0, 0 ), 2 ); scene->cameraController()->depthBufferCaptured( depthImage ); + Qgs3DUtils::waitForFrame( engine, scene ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.7, -185.5, 224.6 ), 1.0 ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -6.8, -38.6, 46.7 ), 1.0 ); @@ -2131,6 +2132,7 @@ void TestQgs3DRendering::testDepthBuffer() QGSVERIFYIMAGECHECK( "depth_wheel_action_2", "depth_wheel_action_2", grayImage, QString(), 5, QSize( 0, 0 ), 2 ); scene->cameraController()->depthBufferCaptured( depthImage ); + Qgs3DUtils::waitForFrame( engine, scene ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.5, -184.7, 223.5 ), 1.0 ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -12.1, -69.0, 83.5 ), 1.0 ); @@ -2148,6 +2150,7 @@ void TestQgs3DRendering::testDepthBuffer() QGSVERIFYIMAGECHECK( "depth_wheel_action_3", "depth_wheel_action_3", grayImage, QString(), 5, QSize( 0, 0 ), 2 ); scene->cameraController()->depthBufferCaptured( depthImage ); + Qgs3DUtils::waitForFrame( engine, scene ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( -32.4, -184.1, 222.8 ), 1.0 ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -29.0, -164.9, 199.6 ), 1.0 ); From cc3a09b4a3808de25d045b71931c467602412dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 13 Feb 2025 11:33:25 +0100 Subject: [PATCH 6/9] Apply suggestions per review --- src/3d/qgs3dutils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 18fdcf702b03..c5141af3f6ea 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -45,7 +45,7 @@ #include #include #include -#include +#include #if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) @@ -72,6 +72,7 @@ void Qgs3DUtils::waitForFrame( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene QObject::connect( frameAction, &Qt3DLogic::QFrameAction::triggered, &evLoop, &QEventLoop::quit ); evLoop.exec(); scene->removeComponent( frameAction ); + frameAction->deleteLater(); engine.renderSettings()->setRenderPolicy( oldPolicy ); } From 3ae7bf025ac93cbb702ae7ec3e832b61b4fa2c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 13 Feb 2025 11:34:23 +0100 Subject: [PATCH 7/9] Speed up QgsCameraController::sampleDepthBuffer() averaging --- src/3d/qgscameracontroller.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/3d/qgscameracontroller.cpp b/src/3d/qgscameracontroller.cpp index 1871bfb46a50..13e650f515be 100644 --- a/src/3d/qgscameracontroller.cpp +++ b/src/3d/qgscameracontroller.cpp @@ -266,11 +266,14 @@ double QgsCameraController::sampleDepthBuffer( int px, int py ) // Returns the average of depth values that are not 1 (void area) depth = 0; int samplesCount = 0; - for ( int x = 0; x < mDepthBufferImage.width(); ++x ) + // Make sure we can do the cast + Q_ASSERT( mDepthBufferImage.format() == QImage::Format_RGB32 ); + for ( int y = 0; y < mDepthBufferImage.height(); ++y ) { - for ( int y = 0; y < mDepthBufferImage.height(); ++y ) + const QRgb *line = reinterpret_cast( mDepthBufferImage.constScanLine( y ) ); + for ( int x = 0; x < mDepthBufferImage.width(); ++x ) { - double d = Qgs3DUtils::decodeDepth( mDepthBufferImage.pixel( x, y ) ); + double d = Qgs3DUtils::decodeDepth( line[x] ); if ( d < 1 ) { depth += d; From 1f8450a69f3cca3e41a709ef2f4ecf680c4dd9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ko=C5=88a=C5=99=C3=ADk?= Date: Thu, 13 Feb 2025 11:54:43 +0100 Subject: [PATCH 8/9] Don't loop forever in test waiting for sensible near plane --- tests/src/3d/testqgs3dcameracontroller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/3d/testqgs3dcameracontroller.cpp b/tests/src/3d/testqgs3dcameracontroller.cpp index ff871f7a88c2..7e8f76d161df 100644 --- a/tests/src/3d/testqgs3dcameracontroller.cpp +++ b/tests/src/3d/testqgs3dcameracontroller.cpp @@ -1177,8 +1177,11 @@ void TestQgs3DCameraController::waitForNearPlane( QgsOffscreen3DEngine &engine, // XXX: Sometimes the near/far planes aren't calculated correctly, so they're // left at the too-deep default. This causes the rest of the test to fail in // weird ways every once in a while, so loop until we get good values. + size_t i = 0; do { + QVERIFY2( i++ < 10, "Near plane not set properly even after multiple tries" ); + // Force recalcualtion of near/far planes. scene->cameraController()->mCameraChanged = true; From 91fef27891d507e24be44c3ccc769006a23594b8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 14 Feb 2025 08:00:36 +1000 Subject: [PATCH 9/9] Update src/3d/qgscameracontroller.h --- src/3d/qgscameracontroller.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/3d/qgscameracontroller.h b/src/3d/qgscameracontroller.h index bd5c4466dfc6..e1b2e7304b6b 100644 --- a/src/3d/qgscameracontroller.h +++ b/src/3d/qgscameracontroller.h @@ -352,7 +352,7 @@ class _3D_EXPORT QgsCameraController : public QObject QImage mDepthBufferImage; // -1 when unset // TODO: Change to std::optional - double mDepthBufferNonVoidAverage; + double mDepthBufferNonVoidAverage = -1; std::unique_ptr mCameraBefore;