Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve 3D Map View movement performance #60246

Merged
merged 9 commits into from
Feb 14, 2025
5 changes: 3 additions & 2 deletions src/3d/qgs3dmapscene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
37 changes: 30 additions & 7 deletions src/3d/qgs3dutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <Qt3DRender/QRenderSettings>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <Qt3DLogic/QFrameAction>


#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
Expand All @@ -58,6 +59,24 @@ 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";

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 );
QEventLoop evLoop;
QObject::connect( frameAction, &Qt3DLogic::QFrameAction::triggered, &evLoop, &QEventLoop::quit );
evLoop.exec();
scene->removeComponent( frameAction );
dvdkon marked this conversation as resolved.
Show resolved Hide resolved
frameAction->deleteLater();

engine.renderSettings()->setRenderPolicy( oldPolicy );
}

QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene )
{
QImage resImage;
Expand All @@ -66,13 +85,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( engine, scene );

auto saveImageFcn = [&evLoop, &resImage]( const QImage &img ) {
resImage = img;
Expand All @@ -82,6 +95,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();
Expand Down Expand Up @@ -126,6 +147,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();
Expand Down
6 changes: 6 additions & 0 deletions src/3d/qgs3dutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 28 additions & 8 deletions src/3d/qgscameracontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "qgscameracontroller.h"
#include "moc_qgscameracontroller.cpp"
#include "qgseventtracing.h"
#include "qgsvector3d.h"
#include "qgswindow3dengine.h"
#include "qgs3dmapscene.h"
Expand Down Expand Up @@ -161,6 +162,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 )
Expand Down Expand Up @@ -233,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;

Expand All @@ -242,24 +249,31 @@ 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 ) ) );
}
}
}

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 )
// 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 < buffer.height(); ++y )
const QRgb *line = reinterpret_cast<const QRgb *>( mDepthBufferImage.constScanLine( y ) );
for ( int x = 0; x < mDepthBufferImage.width(); ++x )
{
double d = Qgs3DUtils::decodeDepth( buffer.pixel( x, y ) );
double d = Qgs3DUtils::decodeDepth( line[x] );
if ( d < 1 )
{
depth += d;
Expand All @@ -274,6 +288,8 @@ double QgsCameraController::sampleDepthBuffer( const QImage &buffer, int px, int
else
depth /= samplesCount;

mDepthBufferNonVoidAverage = depth;

return depth;
}

Expand All @@ -282,7 +298,7 @@ void QgsCameraController::updateCameraFromPose()
if ( mCamera )
mCameraPose.updateCamera( mCamera );

emit cameraChanged();
mCameraChanged = true;
}

void QgsCameraController::moveCameraPositionBy( const QVector3D &posDiff )
Expand All @@ -296,6 +312,8 @@ void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
if ( !mInputHandlersEnabled )
return;

QgsEventTracing::ScopedEvent traceEvent( QStringLiteral( "3D" ), QStringLiteral( "QgsCameraController::onPositionChanged" ) );

switch ( mCameraNavigationMode )
{
case Qgis::NavigationMode::TerrainBased:
Expand All @@ -310,7 +328,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 );
Expand Down Expand Up @@ -557,6 +575,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
{
Expand Down Expand Up @@ -1017,6 +1036,7 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
{
mDepthBufferImage = depthImage;
mDepthBufferIsReady = true;
mDepthBufferNonVoidAverage = -1;

if ( mCurrentOperation == MouseOperation::ZoomWheel )
{
Expand Down
8 changes: 7 additions & 1 deletion src/3d/qgscameracontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -350,6 +350,9 @@ class _3D_EXPORT QgsCameraController : public QObject

bool mDepthBufferIsReady = false;
QImage mDepthBufferImage;
// -1 when unset
// TODO: Change to std::optional<double>
benoitdm-oslandia marked this conversation as resolved.
Show resolved Hide resolved
double mDepthBufferNonVoidAverage = -1;

std::unique_ptr<Qt3DRender::QCamera> mCameraBefore;

Expand Down Expand Up @@ -385,6 +388,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;
Expand Down
49 changes: 33 additions & 16 deletions tests/src/3d/testqgs3dcameracontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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 ) );

Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand All @@ -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 );
Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -1173,5 +1172,23 @@ 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.
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;

// this call is not used but ensures to synchronize the scene
Qgs3DUtils::captureSceneImage( engine, scene );
} while ( scene->cameraController()->camera()->nearPlane() < atLeast ); //#spellok
dvdkon marked this conversation as resolved.
Show resolved Hide resolved
}

QGSTEST_MAIN( TestQgs3DCameraController )
#include "testqgs3dcameracontroller.moc"
Loading
Loading