From 073745db7aaa0b6bb1963e2f190302fb4d45b68b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 11 Dec 2023 10:31:04 +1000 Subject: [PATCH 01/34] Avoid workflow failure when markdown report doesn't exist --- .github/workflows/write_failure_comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/write_failure_comment.yml b/.github/workflows/write_failure_comment.yml index 895701b3007d..805ed0066f8b 100644 --- a/.github/workflows/write_failure_comment.yml +++ b/.github/workflows/write_failure_comment.yml @@ -49,7 +49,7 @@ jobs: - name: 'Unzip artifact' if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0 - run: unzip -j test-results-qt${{ matrix.qt-version }}.zip summary.md pr_number git_commit + run: unzip -j test-results-qt${{ matrix.qt-version }}.zip *.md pr_number git_commit - name: 'Post test report markdown summary as comment on PR' if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0 From 7af4536d0f6c1c405bca416ec982493d1922114b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 11 Dec 2023 13:55:35 +1000 Subject: [PATCH 02/34] Fix return code --- .github/workflows/write_failure_comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/write_failure_comment.yml b/.github/workflows/write_failure_comment.yml index 805ed0066f8b..05302d0de953 100644 --- a/.github/workflows/write_failure_comment.yml +++ b/.github/workflows/write_failure_comment.yml @@ -49,7 +49,7 @@ jobs: - name: 'Unzip artifact' if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0 - run: unzip -j test-results-qt${{ matrix.qt-version }}.zip *.md pr_number git_commit + run: unzip -j test-results-qt${{ matrix.qt-version }}.zip *.md pr_number git_commit || ( e=$? && if [ $e -ne 11 ]; then exit $e; fi ) - name: 'Post test report markdown summary as comment on PR' if: fromJSON(steps.download_artifact.outputs.artifact_id) > 0 From e0466ab5eb853a27fdbc6b34617f268deaf086f8 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Thu, 7 Dec 2023 13:52:11 +0100 Subject: [PATCH 03/34] plugins/tests: Port to QgisUnitTests `unittest.TestCase` is deprecated. --- python/plugins/grassprovider/tests/AlgorithmsTestBase.py | 7 ++++--- .../grassprovider/tests/Grass7AlgorithmsImageryTest.py | 6 +++--- .../grassprovider/tests/Grass7AlgorithmsRasterTestPt1.py | 7 ++++--- .../grassprovider/tests/Grass7AlgorithmsRasterTestPt2.py | 6 +++--- .../grassprovider/tests/Grass7AlgorithmsVectorTest.py | 6 +++--- python/plugins/processing/tests/AlgorithmsTestBase.py | 6 +++--- .../plugins/processing/tests/GdalAlgorithmsGeneralTest.py | 6 +++--- .../plugins/processing/tests/GdalAlgorithmsRasterTest.py | 5 +++-- .../plugins/processing/tests/GdalAlgorithmsVectorTest.py | 6 +++--- 9 files changed, 29 insertions(+), 26 deletions(-) diff --git a/python/plugins/grassprovider/tests/AlgorithmsTestBase.py b/python/plugins/grassprovider/tests/AlgorithmsTestBase.py index 06ced6c469f8..4e330e555d27 100644 --- a/python/plugins/grassprovider/tests/AlgorithmsTestBase.py +++ b/python/plugins/grassprovider/tests/AlgorithmsTestBase.py @@ -46,8 +46,9 @@ QgsProcessingFeedback) from qgis.analysis import (QgsNativeAlgorithms) from qgis.testing import (_UnexpectedSuccess, - start_app, - unittest) + QgisTestCase, + start_app) + from utilities import unitTestDataPath import processing @@ -391,7 +392,7 @@ def check_results(self, results, context, params, expected): self.assertRegex(data, rule) -class GenericAlgorithmsTest(unittest.TestCase): +class GenericAlgorithmsTest(QgisTestCase): """ General (non-provider specific) algorithm tests """ diff --git a/python/plugins/grassprovider/tests/Grass7AlgorithmsImageryTest.py b/python/plugins/grassprovider/tests/Grass7AlgorithmsImageryTest.py index aebe8e0f795f..aa5f40bfac37 100644 --- a/python/plugins/grassprovider/tests/Grass7AlgorithmsImageryTest.py +++ b/python/plugins/grassprovider/tests/Grass7AlgorithmsImageryTest.py @@ -26,14 +26,14 @@ from qgis.core import QgsApplication from qgis.testing import ( - start_app, - unittest + QgisTestCase, + start_app ) from grassprovider.Grass7AlgorithmProvider import Grass7AlgorithmProvider from grassprovider.Grass7Utils import Grass7Utils -class TestGrass7AlgorithmsImageryTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGrass7AlgorithmsImageryTest(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): diff --git a/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt1.py b/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt1.py index 5a30bf4c4b6b..8a93a312b43c 100644 --- a/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt1.py +++ b/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt1.py @@ -26,14 +26,15 @@ from qgis.core import QgsApplication from qgis.testing import ( - start_app, - unittest + QgisTestCase, + start_app + ) from grassprovider.Grass7AlgorithmProvider import Grass7AlgorithmProvider from grassprovider.Grass7Utils import Grass7Utils -class TestGrass7AlgorithmsRasterTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGrass7AlgorithmsRasterTest(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): diff --git a/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt2.py b/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt2.py index eb63d182b55b..ee7f71531874 100644 --- a/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt2.py +++ b/python/plugins/grassprovider/tests/Grass7AlgorithmsRasterTestPt2.py @@ -32,8 +32,8 @@ QgsProcessingFeedback ) from qgis.testing import ( - start_app, - unittest + QgisTestCase, + start_app ) from grassprovider.Grass7AlgorithmProvider import Grass7AlgorithmProvider from grassprovider.Grass7Utils import Grass7Utils @@ -42,7 +42,7 @@ testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') -class TestGrass7AlgorithmsRasterTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGrass7AlgorithmsRasterTest(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): diff --git a/python/plugins/grassprovider/tests/Grass7AlgorithmsVectorTest.py b/python/plugins/grassprovider/tests/Grass7AlgorithmsVectorTest.py index 742c271ab32d..d73f664a5c55 100644 --- a/python/plugins/grassprovider/tests/Grass7AlgorithmsVectorTest.py +++ b/python/plugins/grassprovider/tests/Grass7AlgorithmsVectorTest.py @@ -37,8 +37,8 @@ QgsProcessingFeedback, QgsProcessingFeatureSourceDefinition) from qgis.testing import ( - start_app, - unittest + QgisTestCase, + start_app ) from grassprovider.Grass7AlgorithmProvider import Grass7AlgorithmProvider from grassprovider.Grass7Utils import Grass7Utils @@ -47,7 +47,7 @@ testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') -class TestGrass7AlgorithmsVectorTest(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGrass7AlgorithmsVectorTest(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index e2281fe07146..dc1866def17d 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -48,8 +48,8 @@ QgsProcessingFeedback) from qgis.analysis import (QgsNativeAlgorithms) from qgis.testing import (_UnexpectedSuccess, - start_app, - unittest) + QgisTestCase, + start_app) from utilities import unitTestDataPath import processing @@ -435,7 +435,7 @@ def check_results(self, results, context, params, expected): self.assertRegex(data, rule) -class GenericAlgorithmsTest(unittest.TestCase): +class GenericAlgorithmsTest(QgisTestCase): """ General (non-provider specific) algorithm tests """ diff --git a/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py b/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py index 4bbb76c666d2..cd24123e6ebb 100644 --- a/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py +++ b/python/plugins/processing/tests/GdalAlgorithmsGeneralTest.py @@ -38,8 +38,8 @@ QgsProcessingException, QgsProcessingFeatureSourceDefinition) -from qgis.testing import (start_app, - unittest) +from qgis.testing import (QgisTestCase, + start_app) from processing.algs.gdal.GdalUtils import GdalUtils from processing.algs.gdal.ogr2ogr import ogr2ogr @@ -48,7 +48,7 @@ testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') -class TestGdalAlgorithms(unittest.TestCase): +class TestGdalAlgorithms(QgisTestCase): @classmethod def setUpClass(cls): diff --git a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py index 9e1752508067..bc3b333f2ad0 100644 --- a/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py +++ b/python/plugins/processing/tests/GdalAlgorithmsRasterTest.py @@ -35,7 +35,8 @@ QgsPointXY, QgsCoordinateReferenceSystem) -from qgis.testing import (start_app, +from qgis.testing import (QgisTestCase, + start_app, unittest) import AlgorithmsTestBase @@ -83,7 +84,7 @@ testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') -class TestGdalRasterAlgorithms(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGdalRasterAlgorithms(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): diff --git a/python/plugins/processing/tests/GdalAlgorithmsVectorTest.py b/python/plugins/processing/tests/GdalAlgorithmsVectorTest.py index eba19b20a85b..137358c92e10 100644 --- a/python/plugins/processing/tests/GdalAlgorithmsVectorTest.py +++ b/python/plugins/processing/tests/GdalAlgorithmsVectorTest.py @@ -29,8 +29,8 @@ QgsCoordinateReferenceSystem, QgsRectangle) -from qgis.testing import (start_app, - unittest) +from qgis.testing import (QgisTestCase, + start_app) import AlgorithmsTestBase from processing.algs.gdal.ogr2ogr import ogr2ogr @@ -45,7 +45,7 @@ testDataPath = os.path.join(os.path.dirname(__file__), 'testdata') -class TestGdalVectorAlgorithms(unittest.TestCase, AlgorithmsTestBase.AlgorithmsTest): +class TestGdalVectorAlgorithms(QgisTestCase, AlgorithmsTestBase.AlgorithmsTest): @classmethod def setUpClass(cls): From 3707510ede0eb9b128414d3a50da2e9e08677933 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:15 +0100 Subject: [PATCH 04/34] fix(Qgs3DMapScene): add const to const member functions --- python/3d/auto_generated/qgs3dmapscene.sip.in | 9 ++++----- src/3d/qgs3dmapscene.cpp | 6 +++--- src/3d/qgs3dmapscene.h | 14 +++++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index 531d0679370f..27ec0619cba6 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -13,7 +13,6 @@ - class Qgs3DMapScene : QObject { %Docstring(signature="appended") @@ -31,7 +30,7 @@ Entity that encapsulates our 3D scene - contains all other entities (such as ter %End public: - QgsCameraController *cameraController(); + QgsCameraController *cameraController() const; %Docstring Returns camera controller %End @@ -49,7 +48,7 @@ Resets camera view to show the extent ``extent`` (top view) .. versionadded:: 3.26 %End - QVector viewFrustum2DExtent(); + QVector viewFrustum2DExtent() const; %Docstring Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid @@ -79,7 +78,7 @@ Returns number of pending jobs for all chunked entities Returns the current state of the scene %End - float worldSpaceError( float epsilon, float distance ); + float worldSpaceError( float epsilon, float distance ) const; %Docstring Given screen error (in pixels) and distance from camera (in 3D world coordinates), this function estimates the error in world space. Takes into account camera's field of view and the screen (3D view) size. @@ -93,7 +92,7 @@ Exports the scene according to the scene export settings - QgsRectangle sceneExtent(); + QgsRectangle sceneExtent() const; %Docstring Returns the scene extent in the map's CRS diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index ba85f298592b..10e56eb95b0f 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -235,7 +235,7 @@ void Qgs3DMapScene::setViewFrom2DExtent( const QgsRectangle &extent ) } } -QVector Qgs3DMapScene::viewFrustum2DExtent() +QVector Qgs3DMapScene::viewFrustum2DExtent() const { Qt3DRender::QCamera *camera = mCameraController->camera(); QVector extent; @@ -278,7 +278,7 @@ int Qgs3DMapScene::totalPendingJobsCount() const return count; } -float Qgs3DMapScene::worldSpaceError( float epsilon, float distance ) +float Qgs3DMapScene::worldSpaceError( float epsilon, float distance ) const { Qt3DRender::QCamera *camera = mCameraController->camera(); float fov = camera->fieldOfView(); @@ -1087,7 +1087,7 @@ QVector Qgs3DMapScene::getLayerActiveChunkNodes( QgsMapLay return chunks; } -QgsRectangle Qgs3DMapScene::sceneExtent() +QgsRectangle Qgs3DMapScene::sceneExtent() const { return mMap.extent(); } diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 332875258f1d..f75bb0e72b01 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -80,7 +80,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine ) SIP_SKIP; //! Returns camera controller - QgsCameraController *cameraController() { return mCameraController; } + QgsCameraController *cameraController() const { return mCameraController; } /** * Returns terrain entity (may be temporarily NULLPTR) @@ -103,7 +103,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * * \since QGIS 3.26 */ - QVector viewFrustum2DExtent(); + QVector viewFrustum2DExtent() const; //! Returns number of pending jobs of the terrain entity int terrainPendingJobsCount() const; @@ -128,7 +128,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * Given screen error (in pixels) and distance from camera (in 3D world coordinates), this function * estimates the error in world space. Takes into account camera's field of view and the screen (3D view) size. */ - float worldSpaceError( float epsilon, float distance ); + float worldSpaceError( float epsilon, float distance ) const; //! Exports the scene according to the scene export settings void exportScene( const Qgs3DMapExportSettings &exportSettings ); @@ -145,7 +145,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * * \since QGIS 3.32 */ - QList layers() SIP_SKIP { return mLayerEntities.keys(); } + QList layers() const SIP_SKIP { return mLayerEntities.keys(); } /** * Returns the entity belonging to \a layer @@ -159,7 +159,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * * \since QGIS 3.20 */ - QgsRectangle sceneExtent(); + QgsRectangle sceneExtent() const; /** * Returns the scene's elevation range @@ -174,14 +174,14 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * * \since QGIS 3.26 */ - Qgs3DAxis *get3DAxis() SIP_SKIP { return m3DAxis; } + Qgs3DAxis *get3DAxis() const SIP_SKIP { return m3DAxis; } /** * Returns the abstract 3D engine * * \since QGIS 3.26 */ - QgsAbstract3DEngine *engine() SIP_SKIP { return mEngine; } + QgsAbstract3DEngine *engine() const SIP_SKIP { return mEngine; } /** * Returns the 3D map settings. From 51ed47325757bc4fb0b2d54bf9b2f87fea48a4c4 Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Thu, 7 Dec 2023 11:19:38 +0100 Subject: [PATCH 05/34] [processing] Generate XYZ tiles: fix bakcground color warning --- src/analysis/processing/qgsalgorithmxyztiles.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/analysis/processing/qgsalgorithmxyztiles.cpp b/src/analysis/processing/qgsalgorithmxyztiles.cpp index ae398de66cc8..df19b2da74d0 100644 --- a/src/analysis/processing/qgsalgorithmxyztiles.cpp +++ b/src/analysis/processing/qgsalgorithmxyztiles.cpp @@ -152,10 +152,6 @@ bool QgsXyzTilesBaseAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, mMaxZoom = parameterAsInt( parameters, QStringLiteral( "ZOOM_MAX" ), context ); mDpi = parameterAsInt( parameters, QStringLiteral( "DPI" ), context ); mBackgroundColor = parameterAsColor( parameters, QStringLiteral( "BACKGROUND_COLOR" ), context ); - if ( mTileFormat != QLatin1String( "PNG" ) && mBackgroundColor.alpha() != 255 ) - { - feedback->pushWarning( QObject::tr( "Background color setting ignored, the JPG format only supports fully opaque colors" ) ); - } mAntialias = parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context ); mTileFormat = parameterAsEnum( parameters, QStringLiteral( "TILE_FORMAT" ), context ) ? QStringLiteral( "JPG" ) : QStringLiteral( "PNG" ); mJpgQuality = parameterAsInt( parameters, QStringLiteral( "QUALITY" ), context ); @@ -181,6 +177,11 @@ bool QgsXyzTilesBaseAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, mTileHeight = parameterAsInt( parameters, QStringLiteral( "TILE_HEIGHT" ), context ); } + if ( mTileFormat != QLatin1String( "PNG" ) && mBackgroundColor.alpha() != 255 ) + { + feedback->pushWarning( QObject::tr( "Background color setting ignored, the JPG format only supports fully opaque colors" ) ); + } + return true; } From 0884f5c0ce331dbd26eaad1870d5d5d522a92be8 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Wed, 6 Dec 2023 16:11:50 +0100 Subject: [PATCH 06/34] Build PyQt6 with c++ 17 --- python/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 0a4b489b9087..01075332d7dd 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -174,7 +174,8 @@ GENERATE_SIP_PYTHON_MODULE_CODE(qgis._core core/core.sip "${sip_files_core}" cpp BUILD_SIP_PYTHON_MODULE(qgis._core core/core.sip ${cpp_files} "" qgis_core) set(SIP_CORE_CPP_FILES ${cpp_files}) -if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) +# TODO QGIS 4 : remove this hack when we switch completely to Qt 6 which supports only c++17 +if( ${QT_VERSION_MAJOR} EQUAL 5 AND ( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) ) # Bad hack to fix compilation with gcc 11 - for some reason it's ignoring # the c++ standard version set for the target in BUILD_SIP_PYTHON_MODULE! add_definitions(-std=c++14) From 1a3ebded80f7ee6cab245ef62d339c64a8bb9831 Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Mon, 11 Dec 2023 23:29:01 +0100 Subject: [PATCH 07/34] Add QGIS Server tab to QgsVectorTileLayerProperties for configuring shortname/abstract/etc --- .../qgsvectortilelayerproperties.cpp | 42 +++ src/ui/qgsvectortilelayerpropertiesbase.ui | 274 ++++++++++++++++++ 2 files changed, 316 insertions(+) diff --git a/src/gui/vectortile/qgsvectortilelayerproperties.cpp b/src/gui/vectortile/qgsvectortilelayerproperties.cpp index 6f3d13da0a48..208a712923ba 100644 --- a/src/gui/vectortile/qgsvectortilelayerproperties.cpp +++ b/src/gui/vectortile/qgsvectortilelayerproperties.cpp @@ -153,6 +153,22 @@ void QgsVectorTileLayerProperties::apply() mLayer->setScaleBasedVisibility( chkUseScaleDependentRendering->isChecked() ); mLayer->setMinimumScale( mScaleRangeWidget->minimumScale() ); mLayer->setMaximumScale( mScaleRangeWidget->maximumScale() ); + + //layer title and abstract + mLayer->setShortName( mLayerShortNameLineEdit->text() ); + mLayer->setTitle( mLayerTitleLineEdit->text() ); + mLayer->setAbstract( mLayerAbstractTextEdit->toPlainText() ); + mLayer->setKeywordList( mLayerKeywordListLineEdit->text() ); + mLayer->setDataUrl( mLayerDataUrlLineEdit->text() ); + mLayer->setDataUrlFormat( mLayerDataUrlFormatComboBox->currentText() ); + + //layer attribution + mLayer->setAttribution( mLayerAttributionLineEdit->text() ); + mLayer->setAttributionUrl( mLayerAttributionUrlLineEdit->text() ); + + // LegendURL + mLayer->setLegendUrl( mLayerLegendUrlLineEdit->text() ); + mLayer->setLegendUrlFormat( mLayerLegendUrlFormatComboBox->currentText() ); } void QgsVectorTileLayerProperties::syncToLayer() @@ -211,6 +227,32 @@ void QgsVectorTileLayerProperties::syncToLayer() */ chkUseScaleDependentRendering->setChecked( mLayer->hasScaleBasedVisibility() ); mScaleRangeWidget->setScaleRange( mLayer->minimumScale(), mLayer->maximumScale() ); + + /* + * Server + */ + //layer title and abstract + mLayer->setShortName( mLayerShortNameLineEdit->text() ); + mLayerTitleLineEdit->setText( mLayer->title() ); + mLayerAbstractTextEdit->setPlainText( mLayer->abstract() ); + mLayerKeywordListLineEdit->setText( mLayer->keywordList() ); + mLayerDataUrlLineEdit->setText( mLayer->dataUrl() ); + mLayerDataUrlFormatComboBox->setCurrentIndex( + mLayerDataUrlFormatComboBox->findText( + mLayer->dataUrlFormat() + ) + ); + //layer attribution + mLayerAttributionLineEdit->setText( mLayer->attribution() ); + mLayerAttributionUrlLineEdit->setText( mLayer->attributionUrl() ); + + // layer legend url + mLayerLegendUrlLineEdit->setText( mLayer->legendUrl() ); + mLayerLegendUrlFormatComboBox->setCurrentIndex( + mLayerLegendUrlFormatComboBox->findText( + mLayer->legendUrlFormat() + ) + ); } void QgsVectorTileLayerProperties::saveDefaultStyle() diff --git a/src/ui/qgsvectortilelayerpropertiesbase.ui b/src/ui/qgsvectortilelayerpropertiesbase.ui index d49943190c37..b14872684712 100644 --- a/src/ui/qgsvectortilelayerpropertiesbase.ui +++ b/src/ui/qgsvectortilelayerpropertiesbase.ui @@ -170,6 +170,15 @@ :/images/themes/default/propertyicons/editmetadata.svg:/images/themes/default/propertyicons/editmetadata.svg + + + QGIS Server + + + + :/images/themes/default/propertyicons/network_and_proxy.svg:/images/themes/default/propertyicons/network_and_proxy.svg + + @@ -473,6 +482,271 @@ + + + + + + Description + + + vectormeta + + + + + + Title + + + + + + + List of keywords separated by comma to help catalog searching. + + + List of keywords separated by comma to help catalog searching. + + + + + + + + + A URL of the data presentation. + + + A URL of the data presentation. + + + + + + + Format + + + + + + + + text/html + + + + + text/plain + + + + + application/pdf + + + + + + + + + + Keyword list + + + + + + + Short name + + + + + + + A name used to identify the layer. The short name is a text string used for machine-to-machine communication. + + + + + + + + + A name used to identify the layer. The short name is a text string used for machine-to-machine communication. + + + + + + + The title is for the benefit of humans to identify layer. + + + The title is for the benefit of humans to identify layer. + + + + + + + Abstract + + + + + + + + 0 + 0 + + + + + 16777215 + 150 + + + + The abstract is a descriptive narrative providing more information about the layer. + + + + + + + Data URL + + + + + + + + + + Attribution + + + vectormeta + + + + + + Title + + + + + + + Attribution's title indicates the provider of the layer. + + + Attribution's title indicates the provider of the data layer. + + + + + + + URL + + + + + + + Attribution's url gives a link to the webpage of the provider of the data layer. + + + Attribution's url gives a link to the webpage of the provider of the data layer. + + + + + + + + + + Legend URL + + + + + + + + URL + + + + + + + A URL of the legend image. + + + A URL of the legend image. + + + + + + + Format + + + + + + + + 137 + 0 + + + + 0 + + + + image/png + + + + + image/jpeg + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + From 5171e0597afd2b397617565f7c983394594882d3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 12 Dec 2023 09:01:13 +1000 Subject: [PATCH 08/34] Ensure more specialised exceptions are raised in PyQGIS instead of the generic QgsException one Refs warning in https://github.com/qgis/QGIS/issues/55481#issuecomment-1849001802 ``` /tmp/work/geography/qgis/work/qgis-3.28.13/build/python/core/build/_core/sip_corepart0.cpp:38168: warning: exception of type 'QgsProviderConnectionException' will be caught 38168 | catch (QgsProviderConnectionException &sipExceptionRef) | /tmp/work/geography/qgis/work/qgis-3.28.13/build/python/core/build/_core/sip_corepart0.cpp:38158: warning: by earlier handler for 'QgsException' 38158 | catch (QgsException &sipExceptionRef) ``` --- python/core/qgsexception.sip | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/python/core/qgsexception.sip b/python/core/qgsexception.sip index 404175d98aa3..94200a1027ac 100644 --- a/python/core/qgsexception.sip +++ b/python/core/qgsexception.sip @@ -22,51 +22,57 @@ %End }; -%Exception QgsException(SIP_Exception) /PyName=QgsException/ +%Exception QgsProviderConnectionException(SIP_Exception) /PyName=QgsProviderConnectionException/ { %TypeHeaderCode #include %End %RaiseCode SIP_BLOCK_THREADS - PyErr_SetString(sipException_QgsException, sipExceptionRef.what().toUtf8().constData() ); + PyErr_SetString(sipException_QgsProviderConnectionException, sipExceptionRef.what().toUtf8().constData() ); SIP_UNBLOCK_THREADS %End }; - -%Exception QgsProviderConnectionException(SIP_Exception) /PyName=QgsProviderConnectionException/ +%Exception QgsNotSupportedException(SIP_Exception) /PyName=QgsNotSupportedException/ { %TypeHeaderCode #include %End %RaiseCode SIP_BLOCK_THREADS - PyErr_SetString(sipException_QgsProviderConnectionException, sipExceptionRef.what().toUtf8().constData() ); + PyErr_SetString(sipException_QgsNotSupportedException, sipExceptionRef.what().toUtf8().constData() ); SIP_UNBLOCK_THREADS %End }; -%Exception QgsNotSupportedException(SIP_Exception) /PyName=QgsNotSupportedException/ +%Exception QgsSettingsException(SIP_Exception) /PyName=QgsSettingsException/ { %TypeHeaderCode #include %End %RaiseCode SIP_BLOCK_THREADS - PyErr_SetString(sipException_QgsNotSupportedException, sipExceptionRef.what().toUtf8().constData() ); + PyErr_SetString(sipException_QgsSettingsException, sipExceptionRef.what().toUtf8().constData() ); SIP_UNBLOCK_THREADS %End }; -%Exception QgsSettingsException(SIP_Exception) /PyName=QgsSettingsException/ +// IMPORTANT -- QgsException MUST be last listed, or it will greedily prevent the more +// specialized exceptions from being raised + +%Exception QgsException(SIP_Exception) /PyName=QgsException/ { %TypeHeaderCode #include %End %RaiseCode SIP_BLOCK_THREADS - PyErr_SetString(sipException_QgsSettingsException, sipExceptionRef.what().toUtf8().constData() ); + PyErr_SetString(sipException_QgsException, sipExceptionRef.what().toUtf8().constData() ); SIP_UNBLOCK_THREADS %End }; + +// IMPORTANT -- QgsException MUST be last listed, or it will greedily prevent the more +// specialized exceptions from being raised + From 212406f545a22efc8211919875601eef099e2b61 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 15:54:02 +0100 Subject: [PATCH 09/34] qgsterrainentity: Cosmetic changes --- src/3d/terrain/qgsterrainentity_p.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/3d/terrain/qgsterrainentity_p.cpp b/src/3d/terrain/qgsterrainentity_p.cpp index cdf479ee0537..a4702ff02b0e 100644 --- a/src/3d/terrain/qgsterrainentity_p.cpp +++ b/src/3d/terrain/qgsterrainentity_p.cpp @@ -167,11 +167,9 @@ void QgsTerrainEntity::invalidateMapImages() QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Invalidate textures" ) ); // handle active nodes - updateNodes( mActiveNodes, mUpdateJobFactory.get() ); // handle inactive nodes afterwards - QList inactiveNodes; const QList descendants = mRootNode->descendants(); for ( QgsChunkNode *node : descendants ) @@ -228,8 +226,8 @@ TerrainMapUpdateJob::TerrainMapUpdateJob( QgsTerrainTextureGenerator *textureGen , mTextureGenerator( textureGenerator ) { QgsTerrainTileEntity *entity = qobject_cast( node->entity() ); - connect( textureGenerator, &QgsTerrainTextureGenerator::tileReady, this, &TerrainMapUpdateJob::onTileReady ); - mJobId = textureGenerator->render( entity->textureImage()->imageExtent(), node->tileId(), entity->textureImage()->imageDebugText() ); + connect( mTextureGenerator, &QgsTerrainTextureGenerator::tileReady, this, &TerrainMapUpdateJob::onTileReady ); + mJobId = mTextureGenerator->render( entity->textureImage()->imageExtent(), node->tileId(), entity->textureImage()->imageDebugText() ); } void TerrainMapUpdateJob::cancel() From f4c2dee0dc00f106b8350ffcf1ec14e35481190f Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 16:41:02 +0100 Subject: [PATCH 10/34] qgs3dmapscene: Remove unused method terrainPendingJobsCount --- python/3d/auto_generated/qgs3dmapscene.sip.in | 5 ----- src/3d/qgs3dmapscene.cpp | 5 ----- src/3d/qgs3dmapscene.h | 3 --- 3 files changed, 13 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index 27ec0619cba6..a49557b39f90 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -53,11 +53,6 @@ Resets camera view to show the extent ``extent`` (top view) Calculates the 2D extent viewed by the 3D camera as the vertices of the viewed trapezoid .. versionadded:: 3.26 -%End - - int terrainPendingJobsCount() const; -%Docstring -Returns number of pending jobs of the terrain entity %End int totalPendingJobsCount() const; diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 10e56eb95b0f..d67ea7cdb7cb 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -265,11 +265,6 @@ QVector Qgs3DMapScene::viewFrustum2DExtent() const return extent; } -int Qgs3DMapScene::terrainPendingJobsCount() const -{ - return mTerrain ? mTerrain->pendingJobsCount() : 0; -} - int Qgs3DMapScene::totalPendingJobsCount() const { int count = 0; diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index f75bb0e72b01..3673d0ac6eaa 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -105,9 +105,6 @@ class _3D_EXPORT Qgs3DMapScene : public QObject */ QVector viewFrustum2DExtent() const; - //! Returns number of pending jobs of the terrain entity - int terrainPendingJobsCount() const; - /** * Returns number of pending jobs for all chunked entities * \since QGIS 3.12 From 300325407a95b0706cbff140afec1b407a229899 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:15 +0100 Subject: [PATCH 11/34] fix(qgs3dmapscene): make terrain layer to be handled like other layers This is achieved with the two following changes: - add QgsDummyLayer to handle technical layer (for example terrain). - add QgsTerrainLayer3DRenderer to handle terrain layer. With this change, this is now possible to remove `mSceneEntities` as the entities are now also saved in mLayerEntities. --- python/3d/auto_generated/qgs3dmapscene.sip.in | 4 - python/core/auto_generated/qgsmaplayer.sip.in | 27 ++++ src/3d/qgs3dmapscene.cpp | 148 ++++++++++-------- src/3d/qgs3dmapscene.h | 11 +- src/3d/terrain/qgsterrainentity_p.cpp | 37 ++++- src/3d/terrain/qgsterrainentity_p.h | 22 +++ src/core/qgsmaplayer.h | 24 +++ 7 files changed, 198 insertions(+), 75 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index a49557b39f90..698582159c7c 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -130,10 +130,6 @@ Returns a map of 3D map scenes (by name) open in the QGIS application. void terrainEntityChanged(); %Docstring Emitted when the current terrain entity is replaced by a new one -%End - void terrainPendingJobsCountChanged(); -%Docstring -Emitted when the number of terrain's pending jobs changes %End void totalPendingJobsCountChanged(); diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index 58ada48baee4..846947a1c1a8 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -2281,6 +2281,33 @@ QFlags operator|(QgsMapLayer::ReadFlag f1, QFlags() > Qgs3DMapScene::sOpenScenesFunction = [] { return QMap< QString, Qgs3DMapScene * >(); }; @@ -113,9 +114,9 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine addCameraRotationCenterEntity( mCameraController ); updateLights(); - // create terrain entity - + // create terrain entity and other entities from other layers createTerrainDeferred(); + connect( &map, &Qgs3DMapSettings::extentChanged, this, &Qgs3DMapScene::createTerrain ); connect( &map, &Qgs3DMapSettings::terrainGeneratorChanged, this, &Qgs3DMapScene::createTerrain ); connect( &map, &Qgs3DMapSettings::terrainVerticalScaleChanged, this, &Qgs3DMapScene::createTerrain ); @@ -201,6 +202,14 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine on3DAxisSettingsChanged(); } +QgsTerrainEntity* Qgs3DMapScene::terrainEntity() const +{ + if ( mTerrainLayer ) + return dynamic_cast( mLayerEntities[mTerrainLayer] ); + + return nullptr; +} + void Qgs3DMapScene::viewZoomFull() { const QgsDoubleRange yRange = elevationRange(); @@ -268,8 +277,14 @@ QVector Qgs3DMapScene::viewFrustum2DExtent() const int Qgs3DMapScene::totalPendingJobsCount() const { int count = 0; - for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) - count += entity->pendingJobsCount(); + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + { + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity ) + { + count += entity->pendingJobsCount(); + } + } return count; } @@ -287,13 +302,13 @@ float Qgs3DMapScene::worldSpaceError( float epsilon, float distance ) const return err; } -Qgs3DMapSceneEntity::SceneState sceneState_( QgsAbstract3DEngine *engine ) +Qgs3DMapSceneEntity::SceneState Qgs3DMapScene::buildSceneState( ) const { - Qt3DRender::QCamera *camera = engine->camera(); + Qt3DRender::QCamera *camera = mEngine->camera(); Qgs3DMapSceneEntity::SceneState state; state.cameraFov = camera->fieldOfView(); state.cameraPos = camera->position(); - const QSize size = engine->size(); + const QSize size = mEngine->size(); state.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct? state.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix(); return state; @@ -363,11 +378,15 @@ void Qgs3DMapScene::updateScene() { QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) ); - for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { - entity->handleSceneUpdate( sceneState_( mEngine ) ); - if ( entity->hasReachedGpuMemoryLimit() ) - emit gpuMemoryLimitReached(); + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity ) + { + entity->handleSceneUpdate( buildSceneState() ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); + } } updateSceneState(); @@ -394,12 +413,16 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() // Iterate all scene entities to make sure that they will not get // clipped by the near or far plane - for ( Qgs3DMapSceneEntity *se : std::as_const( mSceneEntities ) ) + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { - const QgsRange depthRange = se->getNearFarPlaneRange( viewMatrix ); + Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( qtEntity ); + if ( sceneEntity ) + { + const QgsRange depthRange = sceneEntity->getNearFarPlaneRange( viewMatrix ); - fnear = std::min( fnear, depthRange.lower() ); - ffar = std::max( ffar, depthRange.upper() ); + fnear = std::min( fnear, depthRange.lower() ); + ffar = std::max( ffar, depthRange.upper() ); + } } if ( fnear < 1 ) @@ -437,12 +460,13 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) { mCameraController->frameTriggered( dt ); - for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { - if ( entity->isEnabled() && entity->needsUpdate() ) + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity && entity->isEnabled() && entity->needsUpdate() ) { QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 ); - entity->handleSceneUpdate( sceneState_( mEngine ) ); + entity->handleSceneUpdate( buildSceneState() ); if ( entity->hasReachedGpuMemoryLimit() ) emit gpuMemoryLimitReached(); } @@ -474,12 +498,9 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) void Qgs3DMapScene::createTerrain() { - if ( mTerrain ) + if ( mTerrainLayer ) { - mSceneEntities.removeOne( mTerrain ); - - delete mTerrain; - mTerrain = nullptr; + removeLayerEntity( mTerrainLayer ); } if ( !mTerrainUpdateScheduled ) @@ -497,30 +518,22 @@ void Qgs3DMapScene::createTerrain() void Qgs3DMapScene::createTerrainDeferred() { - if ( mMap.terrainRenderingEnabled() && mMap.terrainGenerator() ) + if ( mMap.terrainRenderingEnabled() ) { - double tile0width = mMap.terrainGenerator()->rootChunkExtent().width(); - int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() ); - QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap ); - float rootError = mMap.terrainGenerator()->rootChunkError( mMap ); - const QgsAABB clippingBbox = Qgs3DUtils::mapToWorldExtent( mMap.extent(), rootBbox.zMin, rootBbox.zMax, mMap.origin() ); - mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel, clippingBbox ); - - mTerrain = new QgsTerrainEntity( mMap ); - mTerrain->setParent( this ); - mTerrain->setShowBoundingBoxes( mMap.showTerrainBoundingBoxes() ); - - mSceneEntities << mTerrain; - - connect( mTerrain, &QgsChunkedEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); - connect( mTerrain, &QgsTerrainEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::terrainPendingJobsCountChanged ); - } - else - { - mTerrain = nullptr; + if ( !mTerrainLayer ) + { + Qgis::LayerType type; + if ( dynamic_cast( mMap.terrainGenerator() ) ) + type = Qgis::LayerType::Mesh; + else + type = Qgis::LayerType::Raster; + mTerrainLayer = new QgsDummyLayer( type, "technicalTerrainLayer" ); + mTerrainLayer->setRenderer3D( new QgsTerrainLayer3DRenderer() ); + } + addLayerEntity( mTerrainLayer ); } - // make sure that renderers for layers are re-created as well + // make sure that renderers for layers are re-created as well to handle new terrain properties const QList layers = mMap.layers(); for ( QgsMapLayer *layer : layers ) { @@ -568,6 +581,9 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() QgsMapLayer *layer = qobject_cast( sender() ); Q_ASSERT( layer ); + if ( layer == mTerrainLayer ) + return; + // remove old entity - if any removeLayerEntity( layer ); @@ -595,6 +611,9 @@ void Qgs3DMapScene::onLayersChanged() // what is left in layersBefore are layers that have been removed for ( QgsMapLayer *layer : std::as_const( layersBefore ) ) { + if ( layer == mTerrainLayer ) + continue; + removeLayerEntity( layer ); } @@ -693,7 +712,6 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) if ( Qgs3DMapSceneEntity *sceneNewEntity = qobject_cast( newEntity ) ) { needsSceneUpdate = true; - mSceneEntities.append( sceneNewEntity ); connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity ) { @@ -716,13 +734,11 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) connect( vlayer, &QgsVectorLayer::selectionChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); connect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } - - if ( layer->type() == Qgis::LayerType::Mesh ) + else if ( layer->type() == Qgis::LayerType::Mesh ) { connect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } - - if ( layer->type() == Qgis::LayerType::PointCloud ) + else if ( layer->type() == Qgis::LayerType::PointCloud ) { QgsPointCloudLayer *pclayer = qobject_cast( layer ); connect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); @@ -734,13 +750,16 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) { Qt3DCore::QEntity *entity = mLayerEntities.take( layer ); - if ( Qgs3DMapSceneEntity *sceneEntity = qobject_cast( entity ) ) - { - mSceneEntities.removeOne( sceneEntity ); - } + mLayerEntities.remove( layer ); if ( entity ) + { + if ( Qgs3DMapSceneEntity * sceneEntity = qobject_cast( entity ) ) + { + disconnect( sceneEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); + } entity->deleteLater(); + } disconnect( layer, &QgsMapLayer::request3DUpdate, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); @@ -751,18 +770,20 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) disconnect( vlayer, &QgsVectorLayer::layerModified, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); mModelVectorLayers.removeAll( layer ); } - - if ( layer->type() == Qgis::LayerType::Mesh ) + else if ( layer->type() == Qgis::LayerType::Mesh ) { disconnect( layer, &QgsMapLayer::rendererChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } - - if ( layer->type() == Qgis::LayerType::PointCloud ) + else if ( layer->type() == Qgis::LayerType::PointCloud ) { QgsPointCloudLayer *pclayer = qobject_cast( layer ); disconnect( pclayer, &QgsPointCloudLayer::renderer3DChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); disconnect( pclayer, &QgsPointCloudLayer::subsetStringChanged, this, &Qgs3DMapScene::onLayerRenderer3DChanged ); } + else if ( layer == mTerrainLayer ) + { + mTerrainLayer = nullptr; + } } void Qgs3DMapScene::finalizeNewEntity( Qt3DCore::QEntity *newEntity ) @@ -900,9 +921,10 @@ void Qgs3DMapScene::updateSceneState() return; } - for ( Qgs3DMapSceneEntity *entity : std::as_const( mSceneEntities ) ) + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { - if ( entity->isEnabled() && entity->pendingJobsCount() > 0 ) + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity && entity->isEnabled() && entity->pendingJobsCount() > 0 ) { setSceneState( Updating ); return; @@ -1055,8 +1077,8 @@ void Qgs3DMapScene::exportScene( const Qgs3DMapExportSettings &exportSettings ) } } - if ( mTerrain ) - exporter.parseTerrain( mTerrain, "Terrain" ); + if ( mTerrainLayer ) + exporter.parseTerrain( terrainEntity(), "Terrain" ); exporter.save( exportSettings.sceneName(), exportSettings.sceneFolderPath() ); @@ -1091,9 +1113,9 @@ QgsDoubleRange Qgs3DMapScene::elevationRange() const { double yMin = std::numeric_limits< double >::max(); double yMax = std::numeric_limits< double >::lowest(); - if ( mMap.terrainRenderingEnabled() && mTerrain ) + if ( mMap.terrainRenderingEnabled() && mTerrainLayer ) { - const QgsAABB bbox = mTerrain->rootNode()->bbox(); + const QgsAABB bbox = terrainEntity()->rootNode()->bbox(); yMin = std::min( yMin, static_cast< double >( bbox.yMin ) ); yMax = std::max( yMax, static_cast< double >( bbox.yMax ) ); } diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 3673d0ac6eaa..3365a3aa34b4 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -22,6 +22,7 @@ #include "qgsrectangle.h" #include "qgscameracontroller.h" +#include "qgs3dmapsceneentity_p.h" #ifndef SIP_RUN namespace Qt3DRender @@ -57,8 +58,6 @@ class QgsShadowRenderingFrameGraph; class QgsPostprocessingEntity; class QgsChunkNode; class QgsDoubleRange; -class Qgs3DMapSceneEntity; - /** * \ingroup 3d @@ -86,7 +85,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * Returns terrain entity (may be temporarily NULLPTR) * \note Not available in Python bindings */ - QgsTerrainEntity *terrainEntity() SIP_SKIP { return mTerrain; } + QgsTerrainEntity *terrainEntity() const SIP_SKIP; //! Resets camera view to show the whole scene (top view) void viewZoomFull(); @@ -204,8 +203,6 @@ class _3D_EXPORT Qgs3DMapScene : public QObject signals: //! Emitted when the current terrain entity is replaced by a new one void terrainEntityChanged(); - //! Emitted when the number of terrain's pending jobs changes - void terrainPendingJobsCountChanged(); /** * Emitted when the total number of pending jobs changes @@ -277,6 +274,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject void updateScene(); void finalizeNewEntity( Qt3DCore::QEntity *newEntity ); int maximumTextureSize() const; + Qgs3DMapSceneEntity::SceneState buildSceneState( ) const; private: Qgs3DMapSettings &mMap; @@ -284,8 +282,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject //! Provides a way to have a synchronous function executed each frame Qt3DLogic::QFrameAction *mFrameAction = nullptr; QgsCameraController *mCameraController = nullptr; - QgsTerrainEntity *mTerrain = nullptr; - QList mSceneEntities; + QgsMapLayer *mTerrainLayer = nullptr; //! Entity that shows view center - useful for debugging camera issues Qt3DCore::QEntity *mEntityCameraViewCenter = nullptr; //! Keeps track of entities that belong to a particular layer diff --git a/src/3d/terrain/qgsterrainentity_p.cpp b/src/3d/terrain/qgsterrainentity_p.cpp index a4702ff02b0e..45033726145d 100644 --- a/src/3d/terrain/qgsterrainentity_p.cpp +++ b/src/3d/terrain/qgsterrainentity_p.cpp @@ -31,7 +31,7 @@ #include #include - +#include "qgs3dmapsettings.h" ///@cond PRIVATE @@ -248,4 +248,39 @@ void TerrainMapUpdateJob::onTileReady( int jobId, const QImage &image ) } } + +// ----------- + +QgsTerrainLayer3DRenderer::QgsTerrainLayer3DRenderer() +{ +} + +QString QgsTerrainLayer3DRenderer::type() const +{ + return "terrain"; +} + +QgsTerrainLayer3DRenderer *QgsTerrainLayer3DRenderer::clone() const +{ + return new QgsTerrainLayer3DRenderer(); +} + +Qt3DCore::QEntity *QgsTerrainLayer3DRenderer::createEntity( const Qgs3DMapSettings &map3DSettings ) const +{ + QgsTerrainEntity *terrainEntity = nullptr; + if ( map3DSettings.terrainRenderingEnabled() && map3DSettings.terrainGenerator() ) + { + double tile0width = map3DSettings.terrainGenerator()->rootChunkExtent().width(); + int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, map3DSettings.mapTileResolution(), map3DSettings.maxTerrainGroundError() ); + QgsAABB rootBbox = map3DSettings.terrainGenerator()->rootChunkBbox( map3DSettings ); + float rootError = map3DSettings.terrainGenerator()->rootChunkError( map3DSettings ); + const QgsAABB clippingBbox = Qgs3DUtils::mapToWorldExtent( map3DSettings.extent(), rootBbox.zMin, rootBbox.zMax, map3DSettings.origin() ); + map3DSettings.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel, clippingBbox ); + + terrainEntity = new QgsTerrainEntity( map3DSettings ); + terrainEntity->setShowBoundingBoxes( map3DSettings.showTerrainBoundingBoxes() ); + } + + return terrainEntity; +} /// @endcond diff --git a/src/3d/terrain/qgsterrainentity_p.h b/src/3d/terrain/qgsterrainentity_p.h index d4bcfa03b21a..309b2f45377b 100644 --- a/src/3d/terrain/qgsterrainentity_p.h +++ b/src/3d/terrain/qgsterrainentity_p.h @@ -34,6 +34,7 @@ #include +#include "qgsabstract3drenderer.h" namespace Qt3DCore { @@ -113,6 +114,27 @@ class TerrainMapUpdateJob : public QgsChunkQueueJob int mJobId; }; +/** + * \ingroup core + * \brief 3D renderer that renders all mesh triangles of a mesh layer. + * \since QGIS 3.6 + */ +class _3D_EXPORT QgsTerrainLayer3DRenderer : public QgsAbstract3DRenderer +{ + public: + //! Takes ownership of the symbol object + explicit QgsTerrainLayer3DRenderer(); + + QString type() const override; + QgsTerrainLayer3DRenderer *clone() const override SIP_FACTORY; + Qt3DCore::QEntity *createEntity( const Qgs3DMapSettings &map ) const override SIP_SKIP; + + void writeXml( QDomElement &, const QgsReadWriteContext & ) const override {} + void readXml( const QDomElement &, const QgsReadWriteContext & ) override {} + void resolveReferences( const QgsProject & ) override {} +}; + + /// @endcond #endif // QGSTERRAINENTITY_P_H diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index d2cf25d8a4b1..9d038fe8fc59 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -2409,4 +2409,28 @@ typedef QPointer< QgsMapLayer > QgsWeakMapLayerPointer; typedef QList< QgsWeakMapLayerPointer > QgsWeakMapLayerPointerList; #endif + +/** + * \ingroup core + * + * \brief Represents a fake/dummy map layer + * \since QGIS 3.36 + */ +class CORE_EXPORT QgsDummyLayer : public QgsMapLayer +{ + Q_OBJECT + public: + //! Default constructor + QgsDummyLayer( Qgis::LayerType type, const QString &name = QString() ): QgsMapLayer( type, name ) {} + virtual QgsMapLayer *clone() const override { return nullptr;} + virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext & ) override { return nullptr;} + virtual bool readSymbology( const QDomNode &, QString &, QgsReadWriteContext &, StyleCategories ) override + { return false;} + virtual bool writeSymbology( QDomNode &, QDomDocument &, QString &, + const QgsReadWriteContext &, QgsMapLayer::StyleCategories ) const override + { return false;} + + virtual void setTransformContext( const QgsCoordinateTransformContext & ) override {} +}; + #endif From 1bea903dcfe63a4313f01226ed8fb2ba2c0c8316 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:15 +0100 Subject: [PATCH 12/34] Fix(3D): rename Qgs3DMapSceneEntity::SceneState to SceneContext to avoid confusion with Qgs3DMapScene::SceneState --- src/3d/chunks/qgschunkedentity_p.cpp | 38 ++++++++++++------------- src/3d/chunks/qgschunkedentity_p.h | 6 ++-- src/3d/qgs3dmapscene.cpp | 24 ++++++++-------- src/3d/qgs3dmapscene.h | 2 +- src/3d/qgs3dmapsceneentity_p.h | 4 +-- src/3d/qgsvirtualpointcloudentity_p.cpp | 10 +++---- src/3d/qgsvirtualpointcloudentity_p.h | 2 +- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index b5a5341cd99b..a8cf1418c5c7 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -31,16 +31,16 @@ ///@cond PRIVATE -static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneState &state ) +static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneContext &sceneContext ) { if ( node->error() <= 0 ) //it happens for meshes return 0; - float dist = node->bbox().distanceFromPoint( state.cameraPos ); + float dist = node->bbox().distanceFromPoint( sceneContext.cameraPos ); // TODO: what to do when distance == 0 ? - float sse = Qgs3DUtils::screenSpaceError( node->error(), dist, state.screenSizePx, state.cameraFov ); + float sse = Qgs3DUtils::screenSpaceError( node->error(), dist, sceneContext.screenSizePx, sceneContext.cameraFov ); return sse; } @@ -120,7 +120,7 @@ QgsChunkedEntity::~QgsChunkedEntity() } -void QgsChunkedEntity::handleSceneUpdate( const SceneState &state ) +void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) { if ( !mIsValid ) return; @@ -130,7 +130,7 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneState &state ) // of the camera). Removing them keeps the loading queue shorter, // and we avoid loading chunks that we only wanted for a short period // of time when camera was moving. - pruneLoaderQueue( state ); + pruneLoaderQueue( sceneContext ); QElapsedTimer t; t.start(); @@ -142,7 +142,7 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneState &state ) mFrustumCulled = 0; mCurrentTime = QTime::currentTime(); - update( mRootNode, state ); + update( mRootNode, sceneContext ); #ifdef QGISDEBUG int enabled = 0, disabled = 0, unloaded = 0; @@ -333,7 +333,7 @@ void QgsChunkedEntity::updateNodes( const QList &nodes, QgsChunk startJobs(); } -void QgsChunkedEntity::pruneLoaderQueue( const SceneState &state ) +void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) { QList toRemoveFromLoaderQueue; @@ -344,7 +344,7 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneState &state ) while ( e ) { Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate ); - if ( Qgs3DUtils::isCullable( e->chunk->bbox(), state.viewProjectionMatrix ) ) + if ( Qgs3DUtils::isCullable( e->chunk->bbox(), sceneContext.viewProjectionMatrix ) ) { toRemoveFromLoaderQueue.append( e->chunk ); } @@ -405,7 +405,7 @@ struct } } ResidencyRequestSorter; -void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) +void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneContext ) { QSet nodes; QVector residencyRequests; @@ -417,14 +417,14 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) }; int renderedCount = 0; std::priority_queue, decltype( cmp_funct )> pq( cmp_funct ); - pq.push( std::make_pair( root, screenSpaceError( root, state ) ) ); + pq.push( std::make_pair( root, screenSpaceError( root, sceneContext ) ) ); while ( !pq.empty() && renderedCount <= mPrimitivesBudget ) { slotItem s = pq.top(); pq.pop(); QgsChunkNode *node = s.first; - if ( Qgs3DUtils::isCullable( node->bbox(), state.viewProjectionMatrix ) ) + if ( Qgs3DUtils::isCullable( node->bbox(), sceneContext.viewProjectionMatrix ) ) { ++mFrustumCulled; continue; @@ -453,7 +453,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) // make sure all nodes leading to children are always loaded // so that zooming out does not create issues - double dist = node->bbox().center().distanceToPoint( state.cameraPos ); + double dist = node->bbox().center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) ); if ( !node->entity() ) @@ -463,14 +463,14 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) } bool becomesActive = false; - // QgsDebugMsgLevel( QStringLiteral( "%1|%2|%3 %4 %5" ).arg( node->tileId().x ).arg( node->tileId().y ).arg( node->tileId().z ).arg( mTau ).arg( screenSpaceError( node, state ) ), 2 ); + // QgsDebugMsgLevel( QStringLiteral( "%1|%2|%3 %4 %5" ).arg( node->tileId().x ).arg( node->tileId().y ).arg( node->tileId().z ).arg( mTau ).arg( screenSpaceError( node, sceneContext ) ), 2 ); if ( node->childCount() == 0 ) { // there's no children available for this node, so regardless of whether it has an acceptable error // or not, it's the best we'll ever get... becomesActive = true; } - else if ( mTau > 0 && screenSpaceError( node, state ) <= mTau ) + else if ( mTau > 0 && screenSpaceError( node, sceneContext ) <= mTau ) { // acceptable error for the current chunk - let's render it becomesActive = true; @@ -495,15 +495,15 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) if ( children[i]->entity() || !children[i]->hasData() ) { // chunk is resident - let's visit it recursively - pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) ); + pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) ); } else { // chunk is not yet resident - let's try to load it - if ( Qgs3DUtils::isCullable( children[i]->bbox(), state.viewProjectionMatrix ) ) + if ( Qgs3DUtils::isCullable( children[i]->bbox(), sceneContext.viewProjectionMatrix ) ) continue; - double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos ); + double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) ); } } @@ -517,7 +517,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) { QgsChunkNode *const *children = node->children(); for ( int i = 0; i < node->childCount(); ++i ) - pq.push( std::make_pair( children[i], screenSpaceError( children[i], state ) ) ); + pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) ); } else { @@ -526,7 +526,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneState &state ) QgsChunkNode *const *children = node->children(); for ( int i = 0; i < node->childCount(); ++i ) { - double dist = children[i]->bbox().center().distanceToPoint( state.cameraPos ); + double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) ); } } diff --git a/src/3d/chunks/qgschunkedentity_p.h b/src/3d/chunks/qgschunkedentity_p.h index c2159a6b9b4a..022f0459239c 100644 --- a/src/3d/chunks/qgschunkedentity_p.h +++ b/src/3d/chunks/qgschunkedentity_p.h @@ -72,7 +72,7 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity ~QgsChunkedEntity() override; //! Called when e.g. camera changes and entity may need updated - void handleSceneUpdate( const SceneState &state ) override; + void handleSceneUpdate( const SceneContext &sceneContext ) override; //! Returns number of jobs pending for this entity until it is fully loaded/updated in the current view int pendingJobsCount() const override; @@ -112,10 +112,10 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity void setNeedsUpdate( bool needsUpdate ) { mNeedsUpdate = needsUpdate; } private: - void update( QgsChunkNode *node, const SceneState &state ); + void update( QgsChunkNode *node, const SceneContext &sceneContext ); //! Removes chunks for loading queue that are currently not needed - void pruneLoaderQueue( const SceneState &state ); + void pruneLoaderQueue( const SceneContext &sceneContext ); //! make sure that the chunk will be loaded soon (if not loaded yet) and not unloaded anytime soon (if loaded already) void requestResidency( QgsChunkNode *node ); diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 55c864d6b403..2ef2cbdbc9f6 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -202,10 +202,10 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine on3DAxisSettingsChanged(); } -QgsTerrainEntity* Qgs3DMapScene::terrainEntity() const +QgsTerrainEntity *Qgs3DMapScene::terrainEntity() const { if ( mTerrainLayer ) - return dynamic_cast( mLayerEntities[mTerrainLayer] ); + return dynamic_cast( mLayerEntities[mTerrainLayer] ); return nullptr; } @@ -302,16 +302,16 @@ float Qgs3DMapScene::worldSpaceError( float epsilon, float distance ) const return err; } -Qgs3DMapSceneEntity::SceneState Qgs3DMapScene::buildSceneState( ) const +Qgs3DMapSceneEntity::SceneContext Qgs3DMapScene::buildSceneContext( ) const { Qt3DRender::QCamera *camera = mEngine->camera(); - Qgs3DMapSceneEntity::SceneState state; - state.cameraFov = camera->fieldOfView(); - state.cameraPos = camera->position(); + Qgs3DMapSceneEntity::SceneContext sceneContext; + sceneContext.cameraFov = camera->fieldOfView(); + sceneContext.cameraPos = camera->position(); const QSize size = mEngine->size(); - state.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct? - state.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix(); - return state; + sceneContext.screenSizePx = std::max( size.width(), size.height() ); // TODO: is this correct? + sceneContext.viewProjectionMatrix = camera->projectionMatrix() * camera->viewMatrix(); + return sceneContext; } void Qgs3DMapScene::onCameraChanged() @@ -383,7 +383,7 @@ void Qgs3DMapScene::updateScene() Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); if ( entity ) { - entity->handleSceneUpdate( buildSceneState() ); + entity->handleSceneUpdate( buildSceneContext() ); if ( entity->hasReachedGpuMemoryLimit() ) emit gpuMemoryLimitReached(); } @@ -466,7 +466,7 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) if ( entity && entity->isEnabled() && entity->needsUpdate() ) { QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 ); - entity->handleSceneUpdate( buildSceneState() ); + entity->handleSceneUpdate( buildSceneContext() ); if ( entity->hasReachedGpuMemoryLimit() ) emit gpuMemoryLimitReached(); } @@ -754,7 +754,7 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) if ( entity ) { - if ( Qgs3DMapSceneEntity * sceneEntity = qobject_cast( entity ) ) + if ( Qgs3DMapSceneEntity *sceneEntity = qobject_cast( entity ) ) { disconnect( sceneEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); } diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 3365a3aa34b4..504397238ef8 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -274,7 +274,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject void updateScene(); void finalizeNewEntity( Qt3DCore::QEntity *newEntity ); int maximumTextureSize() const; - Qgs3DMapSceneEntity::SceneState buildSceneState( ) const; + Qgs3DMapSceneEntity::SceneContext buildSceneContext( ) const; private: Qgs3DMapSettings &mMap; diff --git a/src/3d/qgs3dmapsceneentity_p.h b/src/3d/qgs3dmapsceneentity_p.h index 65db6ff93fee..d5e4f9a523d6 100644 --- a/src/3d/qgs3dmapsceneentity_p.h +++ b/src/3d/qgs3dmapsceneentity_p.h @@ -55,7 +55,7 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity } //! Records some bits about the scene (context for handleSceneUpdate() method) - struct SceneState + struct SceneContext { QVector3D cameraPos; //!< Camera position float cameraFov; //!< Field of view (in degrees) @@ -64,7 +64,7 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity }; //! Called when e.g. camera changes and entity may need updated - virtual void handleSceneUpdate( const SceneState &state ) { Q_UNUSED( state ) } + virtual void handleSceneUpdate( const SceneContext &sceneContext ) { Q_UNUSED( sceneContext ) } //! Returns number of jobs pending for this entity until it is fully loaded/updated in the current view virtual int pendingJobsCount() const { return 0; } diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index 88535053111c..66204dbe5ceb 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -101,7 +101,7 @@ void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i ) emit newEntityCreated( newChunkedEntity ); } -void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneState &state ) +void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext ) { const QVector subIndexes = provider()->subIndexes(); for ( int i = 0; i < subIndexes.size(); ++i ) @@ -114,19 +114,19 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneState &state ) // magic number 256 is the common span value for a COPC root node constexpr int SPAN = 256; const float epsilon = std::min( bbox.xExtent(), bbox.yExtent() ) / SPAN; - const float distance = bbox.distanceFromPoint( state.cameraPos ); - const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, state.screenSizePx, state.cameraFov ); + const float distance = bbox.distanceFromPoint( sceneContext.cameraPos ); + const float sse = Qgs3DUtils::screenSpaceError( epsilon, distance, sceneContext.screenSizePx, sceneContext.cameraFov ); constexpr float THRESHOLD = .2; // always display as bbox for the initial temporary camera pos (0, 0, 0) // then once the camera changes we display as bbox depending on screen space error - const bool displayAsBbox = state.cameraPos.isNull() || sse < THRESHOLD; + const bool displayAsBbox = sceneContext.cameraPos.isNull() || sse < THRESHOLD; if ( !displayAsBbox && !subIndexes.at( i ).index() ) emit subIndexNeedsLoading( i ); setRenderSubIndexAsBbox( i, displayAsBbox ); if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) ) - mChunkedEntitiesMap[i]->handleSceneUpdate( state ); + mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext ); } updateBboxEntity(); } diff --git a/src/3d/qgsvirtualpointcloudentity_p.h b/src/3d/qgsvirtualpointcloudentity_p.h index d3f9c8af9f14..918b1d529eea 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.h +++ b/src/3d/qgsvirtualpointcloudentity_p.h @@ -60,7 +60,7 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity double zValueScale, double zValueOffset, int pointBudget ); //! This is called when the camera moves. It's responsible for loading new indexes and decides if subindex will be rendered as bbox or chunked entity. - void handleSceneUpdate( const SceneState &state ) override; + void handleSceneUpdate( const SceneContext &sceneContext ) override; QgsRange getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const override; From c99c090a72c08ce5ab4d767bbee9669833922465 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:16 +0100 Subject: [PATCH 13/34] (TO REMOVE) : debug --- src/3d/qgs3dmapscene.cpp | 19 +++++++++++++++++++ src/3d/terrain/qgsterrainentity_p.cpp | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 2ef2cbdbc9f6..c61023ae4755 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -498,13 +498,16 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) void Qgs3DMapScene::createTerrain() { + qDebug() << "=============== Qgs3DMapScene::createTerrain !!!"; if ( mTerrainLayer ) { + qDebug() << "=============== Qgs3DMapScene::createTerrain should removeLayerEntity"; removeLayerEntity( mTerrainLayer ); } if ( !mTerrainUpdateScheduled ) { + qDebug() << "=============== Qgs3DMapScene::createTerrain should createTerrainDeferred"; // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred ); mTerrainUpdateScheduled = true; @@ -518,20 +521,29 @@ void Qgs3DMapScene::createTerrain() void Qgs3DMapScene::createTerrainDeferred() { + qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred !!!"; + // TODO: should be in a new QgsAbstract3DRenderer if ( mMap.terrainRenderingEnabled() ) { if ( !mTerrainLayer ) { + qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred no layer ==> creating"; Qgis::LayerType type; if ( dynamic_cast( mMap.terrainGenerator() ) ) type = Qgis::LayerType::Mesh; else type = Qgis::LayerType::Raster; + qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred type:" << type; mTerrainLayer = new QgsDummyLayer( type, "technicalTerrainLayer" ); mTerrainLayer->setRenderer3D( new QgsTerrainLayer3DRenderer() ); } + qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred should addLayerEntity"; addLayerEntity( mTerrainLayer ); } + else + { + qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred terrain disabled!"; + } // make sure that renderers for layers are re-created as well to handle new terrain properties const QList layers = mMap.layers(); @@ -581,8 +593,12 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() QgsMapLayer *layer = qobject_cast( sender() ); Q_ASSERT( layer ); + qDebug() << "=============== Qgs3DMapScene::onLayerRenderer3DChanged layer:" << layer->name(); if ( layer == mTerrainLayer ) + { +// createTerrain(); return; + } // remove old entity - if any removeLayerEntity( layer ); @@ -593,6 +609,7 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() void Qgs3DMapScene::onLayersChanged() { + qDebug() << "=============== Qgs3DMapScene::onLayersChanged "; QSet layersBefore = qgis::listToSet( mLayerEntities.keys() ); QList layersAdded; const QList layers = mMap.layers(); @@ -641,6 +658,7 @@ void Qgs3DMapScene::updateTemporal() void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) { + qDebug() << "=============== Qgs3DMapScene::addLayerEntity layer:" << layer->name(); bool needsSceneUpdate = false; QgsAbstract3DRenderer *renderer = layer->renderer3D(); if ( renderer ) @@ -748,6 +766,7 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) { + qDebug() << "=============== Qgs3DMapScene::removeLayerEntity layer:" << layer->name(); Qt3DCore::QEntity *entity = mLayerEntities.take( layer ); mLayerEntities.remove( layer ); diff --git a/src/3d/terrain/qgsterrainentity_p.cpp b/src/3d/terrain/qgsterrainentity_p.cpp index 45033726145d..d56725c24331 100644 --- a/src/3d/terrain/qgsterrainentity_p.cpp +++ b/src/3d/terrain/qgsterrainentity_p.cpp @@ -267,6 +267,7 @@ QgsTerrainLayer3DRenderer *QgsTerrainLayer3DRenderer::clone() const Qt3DCore::QEntity *QgsTerrainLayer3DRenderer::createEntity( const Qgs3DMapSettings &map3DSettings ) const { + qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity"; QgsTerrainEntity *terrainEntity = nullptr; if ( map3DSettings.terrainRenderingEnabled() && map3DSettings.terrainGenerator() ) { @@ -275,12 +276,14 @@ Qt3DCore::QEntity *QgsTerrainLayer3DRenderer::createEntity( const Qgs3DMapSettin QgsAABB rootBbox = map3DSettings.terrainGenerator()->rootChunkBbox( map3DSettings ); float rootError = map3DSettings.terrainGenerator()->rootChunkError( map3DSettings ); const QgsAABB clippingBbox = Qgs3DUtils::mapToWorldExtent( map3DSettings.extent(), rootBbox.zMin, rootBbox.zMax, map3DSettings.origin() ); + qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will setup quadtree"; map3DSettings.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel, clippingBbox ); + qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will create terrain"; terrainEntity = new QgsTerrainEntity( map3DSettings ); terrainEntity->setShowBoundingBoxes( map3DSettings.showTerrainBoundingBoxes() ); } - + qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity done!"; return terrainEntity; } /// @endcond From 382a58d463c648bbe00ed7d7a7e73f8393a39378 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:16 +0100 Subject: [PATCH 14/34] fix(qgs3dmapscene): refactorize duplicated code into updateScene The update scene logic is duplicated in two different places: - `Qgs3DMapScene::updateScene` - `Qgs3DMapScene::onFrameTriggered` In `Qgs3DMapScene::updateScene`, it iterates through the different mapscene entities, check if th gpu limit has been reached and updates the scene state flags accordingly. In `Qgs3DMapScene::onFrameTriggered`, it makes the same thing as `Qgs3DMapScene::updateScene` but only if needed. These similar approaches are merged into one by adding a `forceupdate` boolean. --- src/3d/qgs3dmapscene.cpp | 35 ++++++++++++----------------------- src/3d/qgs3dmapscene.h | 2 +- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index c61023ae4755..6de04aca0a3a 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -324,14 +324,14 @@ void Qgs3DMapScene::onCameraChanged() mEngine->camera()->lens()->setOrthographicProjection( -viewWidthFromCenter, viewWidthFromCenter, -viewHeightFromCenter, viewHeightFromCenter, mEngine->camera()->nearPlane(), mEngine->camera()->farPlane() ); } - updateScene(); + updateScene( true ); bool changedCameraPlanes = updateCameraNearFarPlanes(); if ( changedCameraPlanes ) { // repeat update of entities - because we have updated camera's near/far planes, // the active nodes may have changed as well - updateScene(); + updateScene( true ); updateCameraNearFarPlanes(); } @@ -374,19 +374,21 @@ void addQLayerComponentsToHierarchy( Qt3DCore::QEntity *entity, const QVector( qtEntity ); if ( entity ) - { - entity->handleSceneUpdate( buildSceneContext() ); - if ( entity->hasReachedGpuMemoryLimit() ) - emit gpuMemoryLimitReached(); - } + if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) + { + entity->handleSceneUpdate( buildSceneContext() ); + if ( entity->hasReachedGpuMemoryLimit() ) + emit gpuMemoryLimitReached(); + } } updateSceneState(); @@ -460,19 +462,7 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) { mCameraController->frameTriggered( dt ); - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) - { - Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); - if ( entity && entity->isEnabled() && entity->needsUpdate() ) - { - QgsDebugMsgLevel( QStringLiteral( "need for update" ), 2 ); - entity->handleSceneUpdate( buildSceneContext() ); - if ( entity->hasReachedGpuMemoryLimit() ) - emit gpuMemoryLimitReached(); - } - } - - updateSceneState(); + updateScene(); // lock changing the FPS counter to 5 fps static int frameCount = 0; @@ -522,7 +512,6 @@ void Qgs3DMapScene::createTerrain() void Qgs3DMapScene::createTerrainDeferred() { qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred !!!"; - // TODO: should be in a new QgsAbstract3DRenderer if ( mMap.terrainRenderingEnabled() ) { if ( !mTerrainLayer ) diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 504397238ef8..5af7c18be955 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -271,7 +271,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject void addCameraRotationCenterEntity( QgsCameraController *controller ); void setSceneState( SceneState state ); void updateSceneState(); - void updateScene(); + void updateScene( bool forceUpdate = false ); void finalizeNewEntity( Qt3DCore::QEntity *newEntity ); int maximumTextureSize() const; Qgs3DMapSceneEntity::SceneContext buildSceneContext( ) const; From b93bc949b622b5b0f38fd51f2cfde50d2328e709 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:16 +0100 Subject: [PATCH 15/34] (TO REMOVE) feat(logger): homogeneize log tools: QgsLogger, QgsMessageLog and qInstallMessageHandler) This produces: * a cleaner and more understandable log management * all log logic is in QgsLogger::qtMessageHandler * log format cutomisation via QT_MESSAGE_PATTERN (useful for dev/server usage) * log message formating is delegated to Qt * add QGS_LOG_LVL_XXX to help in understand map between number and level --- python/core/auto_generated/qgslogger.sip.in | 25 +- src/app/main.cpp | 256 +----------- src/core/qgslogger.cpp | 412 +++++++++++++++++--- src/core/qgslogger.h | 69 +++- src/core/qgsmessagelog.cpp | 20 +- tests/src/python/test_qgslogger.py | 12 +- 6 files changed, 463 insertions(+), 331 deletions(-) diff --git a/python/core/auto_generated/qgslogger.sip.in b/python/core/auto_generated/qgslogger.sip.in index 5c4eeeff68d5..4dfed33cf692 100644 --- a/python/core/auto_generated/qgslogger.sip.in +++ b/python/core/auto_generated/qgslogger.sip.in @@ -13,6 +13,7 @@ + class QgsLogger { %Docstring(signature="appended") @@ -37,7 +38,9 @@ to this file rather than to stdout. %End public: - static void debug( const QString &msg, int debuglevel = 1, const char *file = 0, const char *function = 0, int line = -1 ); + + + static void debug( const QString &msg, int debuglevel = QGS_LOG_LVL_DEBUG, const char *file = 0, const char *function = 0, int line = -1 ); %Docstring Goes to qDebug. @@ -48,13 +51,18 @@ Goes to qDebug. :param line: place in file where the message comes from %End - static void debug( const QString &var, int val, int debuglevel = 1, const char *file = 0, const char *function = 0, int line = -1 ); + static void debug( const QString &var, int val, int debuglevel = QGS_LOG_LVL_DEBUG, const char *file = 0, const char *function = 0, int line = -1 ); %Docstring Similar to the previous method, but prints a variable int-value pair %End + static void info( const QString &msg ); +%Docstring +Goes to qInfo. +%End + static void warning( const QString &msg ); %Docstring Goes to qWarning. @@ -70,21 +78,24 @@ Goes to qCritical. Goes to qFatal. %End + + + static int debugLevel(); %Docstring Reads the environment variable QGIS_DEBUG and converts it to int. If QGIS_DEBUG is not set, the function returns 1 if QGISDEBUG is defined and 0 if not. -%End - - static void logMessageToFile( const QString &message ); -%Docstring -Logs the message passed in to the logfile defined in QGIS_LOG_FILE if any. %End static QString logFile(); %Docstring Reads the environment variable QGIS_LOG_FILE. Returns NULL if the variable is not set, otherwise returns a file name for writing log messages to. +%End + + static QString logFormat(); +%Docstring +Returns the log format used by qgis. %End }; diff --git a/src/app/main.cpp b/src/app/main.cpp index 18024f71d85c..f40cce7b1095 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -69,11 +69,6 @@ typedef SInt32 SRefCon; #ifdef HAVE_CRASH_HANDLER #if defined(__GLIBC__) || defined(__FreeBSD__) #define QGIS_CRASH -#include -#include -#include -#include -#include #endif #endif @@ -211,252 +206,12 @@ bool bundleclicked( int argc, char *argv[] ) return ( argc > 1 && memcmp( argv[1], "-psn_", 5 ) == 0 ); } -void myPrint( const char *fmt, ... ) -{ - va_list ap; - va_start( ap, fmt ); -#if defined(Q_OS_WIN) - char buffer[1024]; - vsnprintf( buffer, sizeof buffer, fmt, ap ); - OutputDebugString( buffer ); -#else - vfprintf( stderr, fmt, ap ); -#endif - va_end( ap ); -} - -static void dumpBacktrace( unsigned int depth ) -{ - if ( depth == 0 ) - depth = 20; - -#ifdef QGIS_CRASH - // Below there is a bunch of operations that are not safe in multi-threaded - // environment (dup()+close() combo, wait(), juggling with file descriptors). - // Maybe some problems could be resolved with dup2() and waitpid(), but it seems - // that if the operations on descriptors are not serialized, things will get nasty. - // That's why there's this lovely mutex here... - static QMutex sMutex; - QMutexLocker locker( &sMutex ); - - int stderr_fd = -1; - if ( access( "/usr/bin/c++filt", X_OK ) < 0 ) - { - myPrint( "Stacktrace (c++filt NOT FOUND):\n" ); - } - else - { - int fd[2]; - - if ( pipe( fd ) == 0 && fork() == 0 ) - { - close( STDIN_FILENO ); // close stdin - - // stdin from pipe - if ( dup( fd[0] ) != STDIN_FILENO ) - { - QgsDebugError( QStringLiteral( "dup to stdin failed" ) ); - } - - close( fd[1] ); // close writing end - execl( "/usr/bin/c++filt", "c++filt", static_cast< char * >( nullptr ) ); - perror( "could not start c++filt" ); - exit( 1 ); - } - - myPrint( "Stacktrace (piped through c++filt):\n" ); - stderr_fd = dup( STDERR_FILENO ); - close( fd[0] ); // close reading end - close( STDERR_FILENO ); // close stderr - - // stderr to pipe - int stderr_new = dup( fd[1] ); - if ( stderr_new != STDERR_FILENO ) - { - if ( stderr_new >= 0 ) - close( stderr_new ); - QgsDebugError( QStringLiteral( "dup to stderr failed" ) ); - } - - close( fd[1] ); // close duped pipe - } - - void **buffer = new void *[ depth ]; - int nptrs = backtrace( buffer, depth ); - backtrace_symbols_fd( buffer, nptrs, STDERR_FILENO ); - delete [] buffer; - if ( stderr_fd >= 0 ) - { - int status; - close( STDERR_FILENO ); - int dup_stderr = dup( stderr_fd ); - if ( dup_stderr != STDERR_FILENO ) - { - close( dup_stderr ); - QgsDebugError( QStringLiteral( "dup to stderr failed" ) ); - } - close( stderr_fd ); - wait( &status ); - } -#elif defined(Q_OS_WIN) - // TODO Replace with incoming QgsStackTrace -#else - Q_UNUSED( depth ) -#endif -} - -#ifdef QGIS_CRASH void qgisCrash( int signal ) { - fprintf( stderr, "QGIS died on signal %d", signal ); - - QgsCrashHandler::handle( 0 ); - - if ( access( "/usr/bin/gdb", X_OK ) == 0 ) - { - // take full stacktrace using gdb - // http://stackoverflow.com/questions/3151779/how-its-better-to-invoke-gdb-from-program-to-print-its-stacktrace - // unfortunately, this is not so simple. the proper method is way more OS-specific - // than this code would suggest, see http://stackoverflow.com/a/1024937 - - char exename[512]; -#if defined(__FreeBSD__) - int len = readlink( "/proc/curproc/file", exename, sizeof( exename ) - 1 ); -#else - int len = readlink( "/proc/self/exe", exename, sizeof( exename ) - 1 ); -#endif - if ( len < 0 ) - { - myPrint( "Could not read link (%d: %s)\n", errno, strerror( errno ) ); - } - else - { - exename[ len ] = 0; - - char pidstr[32]; - snprintf( pidstr, sizeof pidstr, "--pid=%d", getpid() ); - - int gdbpid = fork(); - if ( gdbpid == 0 ) - { - // attach, backtrace and continue - execl( "/usr/bin/gdb", "gdb", "-q", "-batch", "-n", pidstr, "-ex", "thread", "-ex", "bt full", exename, NULL ); - perror( "cannot exec gdb" ); - exit( 1 ); - } - else if ( gdbpid >= 0 ) - { - int status; - waitpid( gdbpid, &status, 0 ); - myPrint( "gdb returned %d\n", status ); - } - else - { - myPrint( "Cannot fork (%d: %s)\n", errno, strerror( errno ) ); - dumpBacktrace( 256 ); - } - } - } - - abort(); -} -#endif - -/* - * Hook into the qWarning/qFatal mechanism so that we can channel messages - * from libpng to the user. - * - * Some JPL WMS images tend to overload the libpng 1.2.2 implementation - * somehow (especially when zoomed in) - * and it would be useful for the user to know why their picture turned up blank - * - * Based on qInstallMsgHandler example code in the Qt documentation. - * - */ -void myMessageOutput( QtMsgType type, const QMessageLogContext &, const QString &msg ) -{ - switch ( type ) - { - case QtDebugMsg: - myPrint( "%s\n", msg.toLocal8Bit().constData() ); - if ( msg.startsWith( QLatin1String( "Backtrace" ) ) ) - { - const QString trace = msg.mid( 9 ); - dumpBacktrace( atoi( trace.toLocal8Bit().constData() ) ); - } - break; - case QtCriticalMsg: - myPrint( "Critical: %s\n", msg.toLocal8Bit().constData() ); - -#ifdef QGISDEBUG - dumpBacktrace( 20 ); -#endif - - break; - case QtWarningMsg: - { - /* Ignore: - * - libpng iCPP known incorrect SRGB profile errors (which are thrown by 3rd party components - * we have no control over and have low value anyway); - * - QtSVG warnings with regards to lack of implementation beyond Tiny SVG 1.2 - */ - if ( msg.contains( QLatin1String( "QXcbClipboard" ), Qt::CaseInsensitive ) || - msg.contains( QLatin1String( "QGestureManager::deliverEvent" ), Qt::CaseInsensitive ) || - msg.startsWith( QLatin1String( "libpng warning: iCCP: known incorrect sRGB profile" ), Qt::CaseInsensitive ) || - msg.contains( QLatin1String( "Could not add child element to parent element because the types are incorrect" ), Qt::CaseInsensitive ) || - msg.contains( QLatin1String( "OpenType support missing for" ), Qt::CaseInsensitive ) ) - break; - - myPrint( "Warning: %s\n", msg.toLocal8Bit().constData() ); - -#ifdef QGISDEBUG - // Print all warnings except setNamedColor. - // Only seems to happen on windows - if ( !msg.startsWith( QLatin1String( "QColor::setNamedColor: Unknown color name 'param" ), Qt::CaseInsensitive ) - && !msg.startsWith( QLatin1String( "Trying to create a QVariant instance of QMetaType::Void type, an invalid QVariant will be constructed instead" ), Qt::CaseInsensitive ) - && !msg.startsWith( QLatin1String( "Logged warning" ), Qt::CaseInsensitive ) ) - { - // TODO: Verify this code in action. - dumpBacktrace( 20 ); - - // also be super obnoxious -- we DON'T want to allow these errors to be ignored!! - if ( QgisApp::instance() && QgisApp::instance()->messageBar() && QgisApp::instance()->thread() == QThread::currentThread() ) - { - QgisApp::instance()->messageBar()->pushCritical( QStringLiteral( "Qt" ), msg ); - } - else - { - QgsMessageLog::logMessage( msg, QStringLiteral( "Qt" ) ); - } - } -#endif - - // TODO: Verify this code in action. - if ( msg.startsWith( QLatin1String( "libpng error:" ), Qt::CaseInsensitive ) ) - { - // Let the user know - QgsMessageLog::logMessage( msg, QStringLiteral( "libpng" ) ); - } - - break; - } - - case QtFatalMsg: - { - myPrint( "Fatal: %s\n", msg.toLocal8Bit().constData() ); #ifdef QGIS_CRASH - qgisCrash( -1 ); -#else - dumpBacktrace( 256 ); - abort(); // deliberately dump core + QgsCrashHandler::handle( 0 ); #endif - break; // silence warnings - } - - case QtInfoMsg: - myPrint( "Info: %s\n", msg.toLocal8Bit().constData() ); - break; - } + QgsLogger::qgisCrash( signal ); } #ifdef _MSC_VER @@ -512,6 +267,8 @@ int main( int argc, char *argv[] ) #endif QgsDebugMsgLevel( QStringLiteral( "Starting qgis main" ), 1 ); + QgsDebugMsgLevel( QStringLiteral( "Qgis logger will use this format: %1" ).arg( QgsLogger::logFormat().toLocal8Bit().constData() ), QGS_LOG_LVL_DEBUG ); + #ifdef WIN32 // Windows #ifdef _MSC_VER _set_fmode( _O_BINARY ); @@ -520,11 +277,6 @@ int main( int argc, char *argv[] ) #endif // _MSC_VER #endif // WIN32 - // Set up the custom qWarning/qDebug custom handler -#ifndef ANDROID - qInstallMessageHandler( myMessageOutput ); -#endif - #ifdef QGIS_CRASH signal( SIGQUIT, qgisCrash ); signal( SIGILL, qgisCrash ); diff --git a/src/core/qgslogger.cpp b/src/core/qgslogger.cpp index 16e1bae4f1b5..71532be83ba0 100644 --- a/src/core/qgslogger.cpp +++ b/src/core/qgslogger.cpp @@ -22,24 +22,318 @@ #include #include #include - +#include +#include +#include #ifndef CMAKE_SOURCE_DIR #error CMAKE_SOURCE_DIR undefined #endif // CMAKE_SOURCE_DIR +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CRASH_HANDLER +# if defined(__GLIBC__) || defined(__FreeBSD__) +# define QGIS_CRASH +# include +# include +# include +# include +# include +# endif +#endif + int QgsLogger::sDebugLevel = -999; // undefined value int QgsLogger::sPrefixLength = -1; Q_GLOBAL_STATIC( QString, sFileFilter ) Q_GLOBAL_STATIC( QString, sLogFile ) +Q_GLOBAL_STATIC( QString, sLogFormat ) Q_GLOBAL_STATIC( QElapsedTimer, sTime ) +void myPrint( const char *fmt, ... ) +{ + va_list ap; + va_start( ap, fmt ); +#if defined(Q_OS_WIN) + char buffer[1024]; + vsnprintf( buffer, sizeof buffer, fmt, ap ); + OutputDebugString( buffer ); +#else + vfprintf( stderr, fmt, ap ); +#endif + va_end( ap ); +} + +void QgsLogger::dumpBacktrace( unsigned int depth ) +{ + if ( depth == 0 ) + depth = 20; + +#ifdef QGIS_CRASH + // Below there is a bunch of operations that are not safe in multi-threaded + // environment (dup()+close() combo, wait(), juggling with file descriptors). + // Maybe some problems could be resolved with dup2() and waitpid(), but it seems + // that if the operations on descriptors are not serialized, things will get nasty. + // That's why there's this lovely mutex here... + static QMutex sMutex; + QMutexLocker locker( &sMutex ); + + int stderr_fd = -1; + if ( access( "/usr/bin/c++filt", X_OK ) < 0 ) + { + myPrint( "Stacktrace (c++filt NOT FOUND):\n" ); + } + else + { + int fd[2]; + + if ( pipe( fd ) == 0 && fork() == 0 ) + { + close( STDIN_FILENO ); // close stdin + + // stdin from pipe + if ( dup( fd[0] ) != STDIN_FILENO ) + { + QgsDebugError( QStringLiteral( "dup to stdin failed" ) ); + } + + close( fd[1] ); // close writing end + execl( "/usr/bin/c++filt", "c++filt", static_cast< char * >( nullptr ) ); + perror( "could not start c++filt" ); + exit( 1 ); + } + + myPrint( "Stacktrace (piped through c++filt):\n" ); + stderr_fd = dup( STDERR_FILENO ); + close( fd[0] ); // close reading end + close( STDERR_FILENO ); // close stderr + + // stderr to pipe + int stderr_new = dup( fd[1] ); + if ( stderr_new != STDERR_FILENO ) + { + if ( stderr_new >= 0 ) + close( stderr_new ); + QgsDebugError( QStringLiteral( "dup to stderr failed" ) ); + } + + close( fd[1] ); // close duped pipe + } + + void **buffer = new void *[ depth ]; + int nptrs = backtrace( buffer, depth ); + backtrace_symbols_fd( buffer, nptrs, STDERR_FILENO ); + delete [] buffer; + if ( stderr_fd >= 0 ) + { + int status; + close( STDERR_FILENO ); + int dup_stderr = dup( stderr_fd ); + if ( dup_stderr != STDERR_FILENO ) + { + close( dup_stderr ); + QgsDebugError( QStringLiteral( "dup to stderr failed" ) ); + } + close( stderr_fd ); + wait( &status ); + } +#elif defined(Q_OS_WIN) + // TODO Replace with incoming QgsStackTrace +#else + Q_UNUSED( depth ) +#endif +} + +void QgsLogger::qgisCrash( int signal ) +{ +#ifdef QGIS_CRASH + fprintf( stderr, "QGIS died on signal %d", signal ); + + if ( access( "/usr/bin/gdb", X_OK ) == 0 ) + { + // take full stacktrace using gdb + // http://stackoverflow.com/questions/3151779/how-its-better-to-invoke-gdb-from-program-to-print-its-stacktrace + // unfortunately, this is not so simple. the proper method is way more OS-specific + // than this code would suggest, see http://stackoverflow.com/a/1024937 + + char exename[512]; +#if defined(__FreeBSD__) + int len = readlink( "/proc/curproc/file", exename, sizeof( exename ) - 1 ); +#else + int len = readlink( "/proc/self/exe", exename, sizeof( exename ) - 1 ); +#endif + if ( len < 0 ) + { + myPrint( "Could not read link (%d: %s)\n", errno, strerror( errno ) ); + } + else + { + exename[ len ] = 0; + + char pidstr[32]; + snprintf( pidstr, sizeof pidstr, "--pid=%d", getpid() ); + + int gdbpid = fork(); + if ( gdbpid == 0 ) + { + // attach, backtrace and continue + execl( "/usr/bin/gdb", "gdb", "-q", "-batch", "-n", pidstr, "-ex", "thread", "-ex", "bt full", exename, NULL ); + perror( "cannot exec gdb" ); + exit( 1 ); + } + else if ( gdbpid >= 0 ) + { + int status; + waitpid( gdbpid, &status, 0 ); + myPrint( "gdb returned %d\n", status ); + } + else + { + myPrint( "Cannot fork (%d: %s)\n", errno, strerror( errno ) ); + QgsLogger::dumpBacktrace( 256 ); + } + } + } + + abort(); +#else + Q_UNUSED( signal ) +#endif +} + +/* + * Hook into the qWarning/qFatal mechanism so that we can channel messages + * from libpng to the user. + * + * Some JPL WMS images tend to overload the libpng 1.2.2 implementation + * somehow (especially when zoomed in) + * and it would be useful for the user to know why their picture turned up blank + * + * Based on qInstallMsgHandler example code in the Qt documentation. + * + */ +void QgsLogger::qtMessageHandler( QtMsgType type, const QMessageLogContext &context, const QString &msg ) +{ +// static QMutex mutex; +// QMutexLocker lock(&mutex); + + QString formatedMessage = qPrintable( qFormatLogMessage( type, context, msg ) ); + if ( type == QtFatalMsg ) + { + std::cerr << formatedMessage.toUtf8().constData() << std::endl; +#ifndef QGIS_CRASH + QgsLogger::dumpBacktrace( 256 ); +#endif + } + else if ( type == QtCriticalMsg ) + { + std::cerr << formatedMessage.toUtf8().constData() << std::endl; +#ifdef QGISDEBUG + QgsLogger::dumpBacktrace( 20 ); +#endif + } + else if ( type == QtWarningMsg ) + { + /* Ignore: + * - libpng iCPP known incorrect SRGB profile errors (which are thrown by 3rd party components + * we have no control over and have low value anyway); + * - QtSVG warnings with regards to lack of implementation beyond Tiny SVG 1.2 + */ + if ( msg.contains( QLatin1String( "QXcbClipboard" ), Qt::CaseInsensitive ) || + msg.contains( QLatin1String( "QGestureManager::deliverEvent" ), Qt::CaseInsensitive ) || + msg.startsWith( QLatin1String( "libpng warning: iCCP: known incorrect sRGB profile" ), Qt::CaseInsensitive ) || + msg.contains( QLatin1String( "Could not add child element to parent element because the types are incorrect" ), Qt::CaseInsensitive ) || + msg.contains( QLatin1String( "OpenType support missing for" ), Qt::CaseInsensitive ) ) + return; + + std::cout << formatedMessage.toUtf8().constData() << std::endl; + +#ifdef QGISDEBUG + // Print all warnings except setNamedColor. + // Only seems to happen on windows + if ( !msg.startsWith( QLatin1String( "QColor::setNamedColor: Unknown color name 'param" ), Qt::CaseInsensitive ) + && !msg.startsWith( QLatin1String( "Trying to create a QVariant instance of QMetaType::Void type, an invalid QVariant will be constructed instead" ), Qt::CaseInsensitive ) + && !msg.startsWith( QLatin1String( "Logged warning" ), Qt::CaseInsensitive ) ) + { + // TODO: Verify this code in action. + QgsLogger::dumpBacktrace( 20 ); + + // TO RE-ENABLE? + /* + // also be super obnoxious -- we DON'T want to allow these errors to be ignored!! + if ( QgisApp::instance() && QgisApp::instance()->messageBar() && QgisApp::instance()->thread() == QThread::currentThread() ) + { + QgisApp::instance()->messageBar()->pushCritical( QStringLiteral( "Qt" ), msg ); + } + else + { + QgsMessageLog::logMessage( msg, QStringLiteral( "Qt" ) ); + }*/ + } +#endif + + // TODO: Verify this code in action. + if ( msg.startsWith( QLatin1String( "libpng error:" ), Qt::CaseInsensitive ) ) + { + // Let the user know + // // TO RE-ENABLE? QgsMessageLog::logMessage( msg, QStringLiteral( "libpng" ) ); + } + } + else + { + std::cout << formatedMessage.toUtf8().constData() << std::endl; + if ( msg.startsWith( QLatin1String( "Backtrace" ) ) ) + { + const QString trace = msg.mid( 9 ); + QgsLogger::dumpBacktrace( atoi( trace.toLocal8Bit().constData() ) ); + } + } + + // at last log to file if needed + if ( !sLogFile()->isEmpty() ) + { + static QFile logFile( *sLogFile() ); + static bool logFileIsOpen = logFile.open( QIODevice::Append | QIODevice::Text ); + + if ( logFileIsOpen ) + { + logFile.write( formatedMessage.toUtf8() + '\n' ); + logFile.flush(); + } + } + + // final action for fatal message + if ( type == QtFatalMsg ) + { +#ifdef QGIS_CRASH + QgsLogger::qgisCrash( -1 ); +#else + abort(); // deliberately dump core +#endif + } +} + void QgsLogger::init() { + static QMutex mutex; + QMutexLocker lock( &mutex ); if ( sDebugLevel != -999 ) return; sTime()->start(); + *sLogFormat() = getenv( "QGIS_LOG_FORMAT" ) + ? getenv( "QGIS_LOG_FORMAT" ) + : ( getenv( "QT_MESSAGE_PATTERN" ) + ? getenv( "QT_MESSAGE_PATTERN" ) + : LOG_FORMAT_LEGACY ); + if ( *sLogFormat() == "PRETTY" ) + *sLogFormat() = LOG_FORMAT_PRETTY; + else if ( *sLogFormat() == "PIPED" ) + *sLogFormat() = LOG_FORMAT_PIPED; + *sLogFile() = getenv( "QGIS_LOG_FILE" ) ? getenv( "QGIS_LOG_FILE" ) : ""; *sFileFilter() = getenv( "QGIS_DEBUG_FILE" ) ? getenv( "QGIS_DEBUG_FILE" ) : ""; sDebugLevel = getenv( "QGIS_DEBUG" ) ? atoi( getenv( "QGIS_DEBUG" ) ) : @@ -54,6 +348,11 @@ void QgsLogger::init() // cppcheck-suppress internalAstError if ( CMAKE_SOURCE_DIR[sPrefixLength - 1] == '/' ) sPrefixLength++; + + qSetMessagePattern( *sLogFormat() ); +#ifndef ANDROID + qInstallMessageHandler( QgsLogger::qtMessageHandler ); +#endif } void QgsLogger::debug( const QString &msg, int debuglevel, const char *file, const char *function, int line ) @@ -63,58 +362,59 @@ void QgsLogger::debug( const QString &msg, int debuglevel, const char *file, con if ( !file && !sFileFilter()->isEmpty() && !sFileFilter()->endsWith( file ) ) return; - if ( sDebugLevel == 0 || debuglevel > sDebugLevel ) + if ( debuglevel != QGS_LOG_LVL_WARNING && debuglevel > sDebugLevel ) return; QString m = msg; - if ( file ) + // compute file/function/line data + QString funcStr; + if ( function ) { - if ( qApp && qApp->thread() != QThread::currentThread() ) - { - m.prepend( QStringLiteral( "[thread:0x%1] " ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) ); - } + funcStr = function; + } + else + { + funcStr = "nofunc"; + } + QString fileStr; + if ( file ) + { + // compute elapsed time m.prepend( QStringLiteral( "[%1ms] " ).arg( sTime()->elapsed() ) ); sTime()->restart(); - if ( function ) - { - m.prepend( QStringLiteral( " (%1) " ).arg( function ) ); - } - - if ( line != -1 ) - { #ifndef _MSC_VER - m.prepend( QStringLiteral( ":%1 :" ).arg( line ) ); + fileStr = file + ( file[0] == '/' ? sPrefixLength : 0 ); #else - m.prepend( QString( "(%1) :" ).arg( line ) ); -#endif - } - -#ifndef _MSC_VER - m.prepend( file + ( file[0] == '/' ? sPrefixLength : 0 ) ); -#else - m.prepend( file ); + fileStr = file; #endif } + else + { + fileStr = "nofile"; + } - if ( sLogFile()->isEmpty() ) + // compute log level + if ( debuglevel == QGS_LOG_LVL_FATAL ) + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).fatal( "%s", m.toUtf8().constData() ); + else if ( debuglevel == QGS_LOG_LVL_CRITICAL ) + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).critical( "%s", m.toUtf8().constData() ); + else if ( debuglevel == QGS_LOG_LVL_WARNING ) + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).warning( "%s", m.toUtf8().constData() ); + else if ( debuglevel == QGS_LOG_LVL_INFO ) + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).info( "%s", m.toUtf8().constData() ); + else if ( debuglevel == QGS_LOG_LVL_DEBUG ) { - if ( debuglevel == 0 ) - { - // debug level 0 is for errors only, so highlight these by dumping them to stderr - std::cerr << m.toUtf8().constData() << std::endl; - } - else - { - qDebug( "%s", m.toUtf8().constData() ); - } + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).debug( "%s", m.toUtf8().constData() ); } else { - logMessageToFile( m ); + QString level = QString( " " ).arg( debuglevel ); + m.prepend( level ); + QMessageLogger( fileStr.toUtf8().constData(), line, funcStr.toUtf8().constData() ).debug( "%s", m.toUtf8().constData() ); } } @@ -128,40 +428,44 @@ void QgsLogger::debug( const QString &var, double val, int debuglevel, const cha debug( QStringLiteral( "%1: %2" ).arg( var ).arg( val ), debuglevel, file, function, line ); } +void QgsLogger::info( const QString &msg ) +{ + if ( sDebugLevel == -999 ) + init(); + qInfo( "%s", msg.toLocal8Bit().constData() ); +} + void QgsLogger::warning( const QString &msg ) { - logMessageToFile( msg ); - qWarning( "Logged warning: %s", msg.toLocal8Bit().constData() ); + if ( sDebugLevel == -999 ) + init(); + qWarning( "%s", msg.toLocal8Bit().constData() ); } void QgsLogger::critical( const QString &msg ) { - logMessageToFile( msg ); - qCritical( "Logged critical: %s", msg.toLocal8Bit().constData() ); + if ( sDebugLevel == -999 ) + init(); + qCritical( "%s", msg.toLocal8Bit().constData() ); } void QgsLogger::fatal( const QString &msg ) { - logMessageToFile( msg ); - qFatal( "Logged fatal: %s", msg.toLocal8Bit().constData() ); + if ( sDebugLevel == -999 ) + init(); + qFatal( "%s", msg.toLocal8Bit().constData() ); } -void QgsLogger::logMessageToFile( const QString &message ) +QString QgsLogger::logFile() { - if ( sLogFile()->isEmpty() ) - return; - - //Maybe more efficient to keep the file open for the life of qgis... - QFile file( *sLogFile() ); - if ( !file.open( QIODevice::Append ) ) - return; - file.write( message.toLocal8Bit().constData() ); - file.write( "\n" ); - file.close(); + if ( sDebugLevel == -999 ) + init(); + return *sLogFile(); } -QString QgsLogger::logFile() +QString QgsLogger::logFormat() { - init(); - return *sLogFile(); + if ( sDebugLevel == -999 ) + init(); + return *sLogFormat(); } diff --git a/src/core/qgslogger.h b/src/core/qgslogger.h index 9d93cebd82fc..d8865f3b483f 100644 --- a/src/core/qgslogger.h +++ b/src/core/qgslogger.h @@ -29,9 +29,20 @@ class QFile; +// There was no defined value to log WARNING or FATAL message. To preserve the current code base using QgsDebugMsgLevel +// the WARNING value can not be in the good order. This induces a choice: if QGIS_DEBUG is 0 (ie. critical), +// we will also display the WARNING messages. +// TODO: enum for python users? +#define QGS_LOG_LVL_FATAL -1 +#define QGS_LOG_LVL_CRITICAL 0 +#define QGS_LOG_LVL_WARNING 500 +#define QGS_LOG_LVL_INFO 1 +#define QGS_LOG_LVL_DEBUG 2 +#define QGS_LOG_LVL_TRACE 3 + #ifdef QGISDEBUG -#define QgsDebugError(str) QgsLogger::debug(QString(str), 0, __FILE__, __FUNCTION__, __LINE__) -#define QgsDebugMsgLevel(str, level) if ( level <= QgsLogger::debugLevel() ) { QgsLogger::debug(QString(str), (level), __FILE__, __FUNCTION__, __LINE__); }(void)(0) +#define QgsDebugError(str) QgsLogger::debug(QString(str), QGS_LOG_LVL_CRITICAL, __FILE__, __FUNCTION__, __LINE__) +#define QgsDebugMsgLevel(str, level) if ( level <= QgsLogger::debugLevel() || level == QGS_LOG_LVL_WARNING ) { QgsLogger::debug(QString(str), (level), __FILE__, __FUNCTION__, __LINE__); }(void)(0) #define QgsDebugCall QgsScopeLogger _qgsScopeLogger(__FILE__, __FUNCTION__, __LINE__) #else #define QgsDebugCall do {} while(false) @@ -39,6 +50,10 @@ class QFile; #define QgsDebugMsgLevel(str, level) do {} while(false) #endif +#define LOG_FORMAT_LEGACY "%{file}:%{line} : (%{function}) [thread:%{qthreadptr}] %{message}" +#define LOG_FORMAT_PRETTY "%{time yyyy-MM-ddThh:mm:ss.zzz} [%{qthreadptr}] %{if-debug}DEBUG%{endif}%{if-info}INFO %{endif}%{if-warning}WARN %{endif}%{if-critical}ERROR%{endif}%{if-fatal}FATAL%{endif} %{file}:%{line}(%{function}) - %{message}" +#define LOG_FORMAT_PIPED "%{time yyyy-MM-ddThh:mm:ss.zzz} | %{if-debug}DEBUG%{endif}%{if-info}INFO %{endif}%{if-warning}WARN %{endif}%{if-critical}ERROR%{endif}%{if-fatal}FATAL%{endif} | %{qthreadptr} | %{function}(%{file}:%{line}) | %{message}" + /** * \ingroup core * \brief QgsLogger is a class to print debug/warning/error messages to the console. @@ -56,11 +71,15 @@ class QFile; * QGIS_LOG_FILE may contain a file name. If set, all messages will be appended * to this file rather than to stdout. */ - class CORE_EXPORT QgsLogger { public: + // TODO: With c++20 we could use [source_location](https://en.cppreference.com/w/cpp/utility/source_location) + // to retrieve file, line and function params for warning, critical, etc. functions. + + // TODO: should be renammed as logMessage (for example) and add a new function to do debug as info, warning, critical, etc. + /** * Goes to qDebug. * \param msg the message to be printed @@ -69,29 +88,32 @@ class CORE_EXPORT QgsLogger * \param function function where the message comes from * \param line place in file where the message comes from */ - static void debug( const QString &msg, int debuglevel = 1, const char *file = nullptr, const char *function = nullptr, int line = -1 ); + static void debug( const QString &msg, int debuglevel = QGS_LOG_LVL_DEBUG, const char *file = nullptr, const char *function = nullptr, int line = -1 ); //! Similar to the previous method, but prints a variable int-value pair - static void debug( const QString &var, int val, int debuglevel = 1, const char *file = nullptr, const char *function = nullptr, int line = -1 ); + static void debug( const QString &var, int val, int debuglevel = QGS_LOG_LVL_DEBUG, const char *file = nullptr, const char *function = nullptr, int line = -1 ); /** * Similar to the previous method, but prints a variable double-value pair * \note not available in Python bindings */ - static void debug( const QString &var, double val, int debuglevel = 1, const char *file = nullptr, const char *function = nullptr, int line = -1 ) SIP_SKIP SIP_SKIP; + static void debug( const QString &var, double val, int debuglevel = QGS_LOG_LVL_DEBUG, const char *file = nullptr, const char *function = nullptr, int line = -1 ) SIP_SKIP SIP_SKIP; /** * Prints out a variable/value pair for types with overloaded operator<< * \note not available in Python bindings */ template static void debug( const QString &var, T val, const char *file = nullptr, const char *function = nullptr, - int line = -1, int debuglevel = 1 ) SIP_SKIP SIP_SKIP + int line = -1, int debuglevel = QGS_LOG_LVL_DEBUG ) SIP_SKIP SIP_SKIP { std::ostringstream os; os << var.toLocal8Bit().data() << " = " << val; debug( var, os.str().c_str(), file, function, line, debuglevel ); } + //! Goes to qInfo. + static void info( const QString &msg ); + //! Goes to qWarning. static void warning( const QString &msg ); @@ -101,6 +123,27 @@ class CORE_EXPORT QgsLogger //! Goes to qFatal. static void fatal( const QString &msg ); + /** + * Tries to dump stack trace through c++filt + */ + static void dumpBacktrace( unsigned int depth ) SIP_SKIP; + + /** + * Hook into the qWarning/qFatal mechanism so that we can channel messages + * from libpng to the user. + * + * Some JPL WMS images tend to overload the libpng 1.2.2 implementation + * somehow (especially when zoomed in) + * and it would be useful for the user to know why their picture turned up blank + * + * Based on qInstallMsgHandler example code in the Qt documentation. + * + */ + static void qtMessageHandler( QtMsgType type, const QMessageLogContext &context, const QString &msg ) SIP_SKIP; + + //! Handle qgis crash + static void qgisCrash( int signal ) SIP_SKIP; + /** * Reads the environment variable QGIS_DEBUG and converts it to int. If QGIS_DEBUG is not set, * the function returns 1 if QGISDEBUG is defined and 0 if not. @@ -112,15 +155,17 @@ class CORE_EXPORT QgsLogger return sDebugLevel; } - //! Logs the message passed in to the logfile defined in QGIS_LOG_FILE if any. - static void logMessageToFile( const QString &message ); - /** * Reads the environment variable QGIS_LOG_FILE. Returns NULL if the variable is not set, * otherwise returns a file name for writing log messages to. */ static QString logFile(); + /** + * Returns the log format used by qgis. + */ + static QString logFormat(); + private: static void init(); @@ -140,11 +185,11 @@ class CORE_EXPORT QgsScopeLogger // clazy:exclude=rule-of-three , _func( func ) , _line( line ) { - QgsLogger::debug( QStringLiteral( "Entering." ), 2, _file, _func, _line ); + QgsLogger::debug( QStringLiteral( "Entering." ), QGS_LOG_LVL_DEBUG, _file, _func, _line ); } ~QgsScopeLogger() { - QgsLogger::debug( QStringLiteral( "Leaving." ), 2, _file, _func, _line ); + QgsLogger::debug( QStringLiteral( "Leaving." ), QGS_LOG_LVL_DEBUG, _file, _func, _line ); } private: const char *_file = nullptr; diff --git a/src/core/qgsmessagelog.cpp b/src/core/qgsmessagelog.cpp index 9a0446e8452a..5e2a4d9b9dcb 100644 --- a/src/core/qgsmessagelog.cpp +++ b/src/core/qgsmessagelog.cpp @@ -26,20 +26,36 @@ class QgsMessageLogConsole; void QgsMessageLog::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level, bool notifyUser ) { + // convert Qgis::MessageLevel to QgsLogger levels + int loggerLevel; switch ( level ) { case Qgis::MessageLevel::Info: case Qgis::MessageLevel::Success: case Qgis::MessageLevel::NoLevel: - QgsDebugMsgLevel( QStringLiteral( "%1 %2[%3] %4" ).arg( QDateTime::currentDateTime().toString( Qt::ISODate ), tag ).arg( static_cast< int >( level ) ).arg( message ), 1 ); + loggerLevel = QGS_LOG_LVL_INFO; break; case Qgis::MessageLevel::Warning: + loggerLevel = QGS_LOG_LVL_WARNING; + break; + case Qgis::MessageLevel::Critical: - QgsDebugError( QStringLiteral( "%1 %2[%3] %4" ).arg( QDateTime::currentDateTime().toString( Qt::ISODate ), tag ).arg( static_cast< int >( level ) ).arg( message ) ); + loggerLevel = QGS_LOG_LVL_CRITICAL; break; + + default: + loggerLevel = QGS_LOG_LVL_INFO; } + // TODO must use the QLoggingCategory + Q_UNUSED( loggerLevel ); + QgsDebugMsgLevel( QStringLiteral( "%1[%2] %3" ) + .arg( tag ) + .arg( static_cast< int >( level ) ) + .arg( message ) + , loggerLevel ); + QgsApplication::messageLog()->emitMessage( message, tag, level, notifyUser ); } diff --git a/tests/src/python/test_qgslogger.py b/tests/src/python/test_qgslogger.py index afa70645a977..23e1b7b3c4b4 100644 --- a/tests/src/python/test_qgslogger.py +++ b/tests/src/python/test_qgslogger.py @@ -30,11 +30,14 @@ class TestQgsLogger(unittest.TestCase): def testLogger(self): try: + os.environ["QGIS_LOG_FORMAT"] = "%{type}: %{message}" myFile = os.fdopen(myFileHandle, "w") myFile.write("QGIS Logger Unit Test\n") myFile.close() myLogger = QgsLogger() - myLogger.debug('This is a debug') + # debug has 'info' type due to sip bug in reading default value for `debuglevel`. We force it temporarily. + myLogger.debug('This is a debug', 2) + myLogger.info('This is a info') myLogger.warning('This is a warning') myLogger.critical('This is critical') # myLogger.fatal('Aaaargh...fatal'); #kills QGIS not testable @@ -42,9 +45,10 @@ def testLogger(self): myText = myFile.readlines() myFile.close() myExpectedText = ['QGIS Logger Unit Test\n', - 'This is a debug\n', - 'This is a warning\n', - 'This is critical\n'] + 'debug: This is a debug\n', + 'info: This is a info\n', + 'warning: This is a warning\n', + 'critical: This is critical\n'] myMessage = ('Expected:\n---\n%s\n---\nGot:\n---\n%s\n---\n' % (myExpectedText, myText)) self.assertEqual(myText, myExpectedText, myMessage) From bc56f6628423457d58ea6579dad84425c6638ada Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:16 +0100 Subject: [PATCH 16/34] (TO REMOVE) qgstessellator: disable boring message --- src/core/qgstessellator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgstessellator.cpp b/src/core/qgstessellator.cpp index 3a1351e48ed3..94cf87318ff4 100644 --- a/src/core/qgstessellator.cpp +++ b/src/core/qgstessellator.cpp @@ -623,7 +623,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh { // Failed to fix that. It could be a really tiny geometry... or maybe they gave us // geometry in unprojected lat/lon coordinates - QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) ); + // QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) ); return; } else From 1597472fdca11db18c0148d2ad916869992565cf Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Fri, 8 Dec 2023 16:09:16 +0100 Subject: [PATCH 17/34] qgs3dutils: Add a function to estimate available gpu memory This routine tries to estimate the GPU available memory in kb. It uses some vendor OpenGL extensions. It works for AMD and Nvidia but there is no available extension for an Intel card. It returns -1 in case of failure. --- src/3d/qgs3dutils.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ src/3d/qgs3dutils.h | 10 +++++++++ 2 files changed, 60 insertions(+) diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 418e1d0a3620..79f3e2b9b42c 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -45,6 +45,9 @@ #include "qgspointcloudclassifiedrenderer.h" #include +#include +#include +#include #include #include @@ -56,6 +59,14 @@ typedef Qt3DRender::QBuffer Qt3DQBuffer; typedef Qt3DCore::QBuffer Qt3DQBuffer; #endif +#ifndef GL_TEXTURE_FREE_MEMORY_ATI +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#endif + +#ifndef GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#endif + // declared here as Qgs3DTypes has no cpp file const char *Qgs3DTypes::PROP_NAME_3D_RENDERER_FLAG = "PROP_NAME_3D_RENDERER_FLAG"; @@ -167,6 +178,45 @@ double Qgs3DUtils::calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ) return usedGpuMemory / 1024.0 / 1024.0; } +int Qgs3DUtils::estimateGpuMemoryAvailable() +{ + int availableMemoryKB = -1; + + QOffscreenSurface offscreen; + QOpenGLContext ctx; + + offscreen.setFormat( QSurfaceFormat::defaultFormat() ); + offscreen.create(); + Q_ASSERT_X( offscreen.isValid(), Q_FUNC_INFO, "Unable to create offscreen surface to gather capabilities" ); + + ctx.setFormat( QSurfaceFormat::defaultFormat() ); + if ( ctx.create() ) + { + ctx.makeCurrent( &offscreen ); + const QSurfaceFormat format = ctx.format(); + auto funcs = ctx.functions(); + + // nvidia memory cards + if ( ctx.hasExtension( "GL_NVX_gpu_memory_info" ) ) + { + funcs->glGetIntegerv( GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &availableMemoryKB ); + } + // amd memory cards + else if ( ctx.hasExtension( "GL_ATI_meminfo" ) ) + { + GLint info[4]; + funcs->glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, info ); + availableMemoryKB = info[0]; + } + + if ( availableMemoryKB == 0 ) + { + availableMemoryKB = -1; + } + } + + return availableMemoryKB; +} bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings, Qgs3DMapSettings &mapSettings, diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index a427e6626bb5..f972d008e522 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -81,6 +81,16 @@ class _3D_EXPORT Qgs3DUtils */ static double calculateEntityGpuMemorySize( Qt3DCore::QEntity *entity ); + /** + * This routine tries to estimate the GPU available memory in kb; + * It uses some vendor OpenGL extensions. It works for AMD and Nvidia + * but there is no available extension for an Intel card. + * It returns -1 in case of failure. + * + * \since QGIS 3.36 + */ + static int estimateGpuMemoryAvailable(); + /** * Captures 3D animation frames to the selected folder * From e4ce7402e0c1e24c8f57248eb7b21a4fe3824b81 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 18/34] feat(3D): add layer name to qgs3dmapsceneentity to improve log and migrate log from qDebug to QgsDebugMsgLevel --- src/3d/chunks/qgschunkedentity_p.cpp | 41 ++++++++++++++++++++----- src/3d/qgs3dmapscene.cpp | 46 ++++++++++++++++++++-------- src/3d/qgs3dmapsceneentity_p.h | 7 +++++ 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index a8cf1418c5c7..f293e390a9e0 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -30,6 +30,12 @@ ///@cond PRIVATE +static QString _logHeader( const QString &layerName ) +{ + if ( layerName.isEmpty() ) + return QStringLiteral( "{layer:} " ); + return QStringLiteral( "{layer:%1} " ).arg( layerName ); +} static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneContext &sceneContext ) { @@ -158,7 +164,9 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) { if ( !node->entity() ) { - QgsDebugError( "Active node has null entity - this should never happen!" ); + QgsDebugError( _logHeader( mLayerName ) + + QString( "Active node %1 has null entity - this should never happen!" ) + .arg( node->tileId().text() ) ); continue; } node->entity()->setEnabled( true ); @@ -173,7 +181,9 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) { if ( !node->entity() ) { - QgsDebugError( "Active node has null entity - this should never happen!" ); + QgsDebugError( _logHeader( mLayerName ) + + QString( "Active node %1 has null entity - this should never happen!" ) + .arg( node->tileId().text() ) ); continue; } node->entity()->setEnabled( false ); @@ -206,14 +216,20 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) if ( pendingJobsCount() != oldJobsCount ) emit pendingJobsCountChanged(); - QgsDebugMsgLevel( QStringLiteral( "update: active %1 enabled %2 disabled %3 | culled %4 | loading %5 loaded %6 | unloaded %7 elapsed %8ms" ).arg( mActiveNodes.count() ) +#ifdef QGISDEBUG + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "update: enabled %1 disabled %2 | unloaded %3" ) .arg( enabled ) .arg( disabled ) + .arg( unloaded ), QGS_LOG_LVL_TRACE ); +#endif + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "update: active %1 | culled %2 | loading %3 loaded %4 | elapsed %5ms" ) + .arg( mActiveNodes.count() ) .arg( mFrustumCulled ) .arg( mChunkLoaderQueue->count() ) .arg( mReplacementQueue->count() ) - .arg( unloaded ) - .arg( t.elapsed() ), 2 ); + .arg( t.elapsed() ), QGS_LOG_LVL_DEBUG ); } @@ -222,11 +238,16 @@ int QgsChunkedEntity::unloadNodes() double usedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); if ( usedGpuMemory <= mGpuMemoryLimit ) { + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "Nothing to unload! (now_using: %1MB)" ) + .arg( usedGpuMemory ), QGS_LOG_LVL_DEBUG ); setHasReachedGpuMemoryLimit( false ); return 0; } - QgsDebugMsgLevel( QStringLiteral( "Going to unload nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 ); + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "Going to unload nodes to free GPU memory (now_using: %1MB)" ) + .arg( usedGpuMemory ), QGS_LOG_LVL_DEBUG ); int unloaded = 0; @@ -258,7 +279,10 @@ int QgsChunkedEntity::unloadNodes() if ( usedGpuMemory > mGpuMemoryLimit ) { setHasReachedGpuMemoryLimit( true ); - QgsDebugMsgLevel( QStringLiteral( "Unable to unload enough nodes to free GPU memory (used: %1 MB, limit: %2 MB)" ).arg( usedGpuMemory ).arg( mGpuMemoryLimit ), 2 ); + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "Unable to unload enough nodes to free GPU memory (now_using: %1MB, limit: %2MB)" ) + .arg( usedGpuMemory ) + .arg( mGpuMemoryLimit ), QGS_LOG_LVL_DEBUG ); } return unloaded; @@ -369,7 +393,8 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) if ( !toRemoveFromLoaderQueue.isEmpty() ) { - QgsDebugMsgLevel( QStringLiteral( "Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), 2 ); + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "Pruned %1 chunks in loading queue" ).arg( toRemoveFromLoaderQueue.count() ), QGS_LOG_LVL_DEBUG ); } } diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 6de04aca0a3a..34dc7cc72474 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -82,6 +82,13 @@ std::function< QMap< QString, Qgs3DMapScene * >() > Qgs3DMapScene::sOpenScenesFunction = [] { return QMap< QString, Qgs3DMapScene * >(); }; +static QString _logHeader( const QString &layerName ) +{ + if ( layerName.isEmpty() ) + return QStringLiteral( "{layer:} " ); + return QStringLiteral( "{layer:%1} " ).arg( layerName ); +} + Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine ) : mMap( map ) , mEngine( engine ) @@ -488,16 +495,16 @@ void Qgs3DMapScene::onFrameTriggered( float dt ) void Qgs3DMapScene::createTerrain() { - qDebug() << "=============== Qgs3DMapScene::createTerrain !!!"; + QgsDebugMsgLevel( "createTerrain begin!!!", QGS_LOG_LVL_DEBUG ); if ( mTerrainLayer ) { - qDebug() << "=============== Qgs3DMapScene::createTerrain should removeLayerEntity"; + QgsDebugMsgLevel( "should removeLayerEntity", QGS_LOG_LVL_DEBUG ); removeLayerEntity( mTerrainLayer ); } if ( !mTerrainUpdateScheduled ) { - qDebug() << "=============== Qgs3DMapScene::createTerrain should createTerrainDeferred"; + QgsDebugMsgLevel( "should createTerrainDeferred", QGS_LOG_LVL_DEBUG ); // defer re-creation of terrain: there may be multiple invocations of this slot, so create the new entity just once QTimer::singleShot( 0, this, &Qgs3DMapScene::createTerrainDeferred ); mTerrainUpdateScheduled = true; @@ -511,27 +518,27 @@ void Qgs3DMapScene::createTerrain() void Qgs3DMapScene::createTerrainDeferred() { - qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred !!!"; + QgsDebugMsgLevel( "createTerrainDeferred begin!!!", QGS_LOG_LVL_DEBUG ); if ( mMap.terrainRenderingEnabled() ) { if ( !mTerrainLayer ) { - qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred no layer ==> creating"; + QgsDebugMsgLevel( "no layer ==> creating", QGS_LOG_LVL_DEBUG ); Qgis::LayerType type; if ( dynamic_cast( mMap.terrainGenerator() ) ) type = Qgis::LayerType::Mesh; else type = Qgis::LayerType::Raster; - qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred type:" << type; + QgsDebugMsgLevel( QString( "terrain layer type: %1" ).arg( ( int )type ), QGS_LOG_LVL_DEBUG );; mTerrainLayer = new QgsDummyLayer( type, "technicalTerrainLayer" ); mTerrainLayer->setRenderer3D( new QgsTerrainLayer3DRenderer() ); } - qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred should addLayerEntity"; + QgsDebugMsgLevel( "should addLayerEntity", QGS_LOG_LVL_DEBUG ); addLayerEntity( mTerrainLayer ); } else { - qDebug() << "=============== Qgs3DMapScene::createTerrainDeferred terrain disabled!"; + QgsDebugMsgLevel( "terrain disabled!", QGS_LOG_LVL_DEBUG ); } // make sure that renderers for layers are re-created as well to handle new terrain properties @@ -582,7 +589,8 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() QgsMapLayer *layer = qobject_cast( sender() ); Q_ASSERT( layer ); - qDebug() << "=============== Qgs3DMapScene::onLayerRenderer3DChanged layer:" << layer->name(); + QgsDebugMsgLevel( _logHeader( layer->name() ) + QString( "onLayerRenderer3DChanged begin!!!" ), QGS_LOG_LVL_DEBUG ); + if ( layer == mTerrainLayer ) { // createTerrain(); @@ -598,7 +606,7 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() void Qgs3DMapScene::onLayersChanged() { - qDebug() << "=============== Qgs3DMapScene::onLayersChanged "; + QgsDebugMsgLevel( "onLayersChanged begin!!!", QGS_LOG_LVL_DEBUG ); QSet layersBefore = qgis::listToSet( mLayerEntities.keys() ); QList layersAdded; const QList layers = mMap.layers(); @@ -647,11 +655,16 @@ void Qgs3DMapScene::updateTemporal() void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) { - qDebug() << "=============== Qgs3DMapScene::addLayerEntity layer:" << layer->name(); + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "type: %1" ) + .arg( static_cast( layer->type() ) ), QGS_LOG_LVL_DEBUG ); bool needsSceneUpdate = false; QgsAbstract3DRenderer *renderer = layer->renderer3D(); if ( renderer ) { + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "renderer type: %1" ) + .arg( renderer->type() ), QGS_LOG_LVL_DEBUG ); // Fix vector layer's renderer to make sure the renderer is pointing to its layer. // It has happened before that renderer pointed to a different layer (probably after copying a style). // This is a bit of a hack and it should be handled in QgsMapLayer::setRenderer3D() but in qgis_core @@ -718,6 +731,7 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) if ( Qgs3DMapSceneEntity *sceneNewEntity = qobject_cast( newEntity ) ) { + sceneNewEntity->setLayerName( layer->name() ); needsSceneUpdate = true; connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity ) @@ -727,8 +741,16 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) connect( sceneNewEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); } + else + { + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "is not a Qgs3DMapSceneEntity" ), QGS_LOG_LVL_INFO ); + } } } + else + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "NO RENDERER for this layer!" ), QGS_LOG_LVL_DEBUG ); if ( needsSceneUpdate ) onCameraChanged(); // needed for chunked entities @@ -755,7 +777,7 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) { - qDebug() << "=============== Qgs3DMapScene::removeLayerEntity layer:" << layer->name(); + QgsDebugMsgLevel( _logHeader( layer->name() ) + QString( "removeLayerEntity begin!!!" ), QGS_LOG_LVL_DEBUG );; Qt3DCore::QEntity *entity = mLayerEntities.take( layer ); mLayerEntities.remove( layer ); diff --git a/src/3d/qgs3dmapsceneentity_p.h b/src/3d/qgs3dmapsceneentity_p.h index d5e4f9a523d6..85522f5bdc16 100644 --- a/src/3d/qgs3dmapsceneentity_p.h +++ b/src/3d/qgs3dmapsceneentity_p.h @@ -85,6 +85,11 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Returns whether the entity has reached GPU memory limit bool hasReachedGpuMemoryLimit() const { return mHasReachedGpuMemoryLimit; } + //! Updates layer name in which this entity in included + virtual void setLayerName( const QString &layerName ) { mLayerName = layerName; } + //! Returns layer name in which this entity in included + virtual QString layerName() const { return mLayerName; } + protected: //! Sets whether the GPU memory limit has been reached void setHasReachedGpuMemoryLimit( bool reached ) { mHasReachedGpuMemoryLimit = reached; } @@ -101,6 +106,8 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity double mGpuMemoryLimit = 500.0; // in megabytes //! Whether the entity is currently over the GPU memory limit (used to report a warning to the user) bool mHasReachedGpuMemoryLimit = false; + //! Layer name in which this entity in included + QString mLayerName = "unknown"; }; /// @endcond From eba6ee453ffa4289f918f32104dd49df64bc9c34 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 16:01:59 +0100 Subject: [PATCH 19/34] qgschunkedentity: Fix some comments The entity is deleted, not the entry. --- src/3d/chunks/qgschunkedentity_p.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index f293e390a9e0..85917e9989e0 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -113,7 +113,7 @@ QgsChunkedEntity::~QgsChunkedEntity() QgsChunkListEntry *entry = mReplacementQueue->takeFirst(); // remove loaded data from node - entry->chunk->unloadChunk(); // also deletes the entry + entry->chunk->unloadChunk(); // also deletes the entity! } delete mReplacementQueue; @@ -266,7 +266,7 @@ int QgsChunkedEntity::unloadNodes() mReplacementQueue->takeEntry( entry ); usedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() ); mActiveNodes.removeOne( entry->chunk ); - entry->chunk->unloadChunk(); // also deletes the entry + entry->chunk->unloadChunk(); // also deletes the entity! ++unloaded; entry = entryPrev; } @@ -387,7 +387,7 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) { n->cancelQueuedForUpdate(); mReplacementQueue->takeEntry( n->replacementQueueEntry() ); - n->unloadChunk(); + n->unloadChunk(); // also deletes the entity! } } From 4c5bd45e891bb61ca619c477fea8612cccf31c8a Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 20/34] feat(3dmapescene): Move gpu memory management to chunked entities --- python/3d/auto_generated/qgs3dmapscene.sip.in | 14 + src/3d/chunks/qgschunkedentity_p.cpp | 250 +++++++++++++++--- src/3d/chunks/qgschunkedentity_p.h | 13 +- src/3d/qgs3dmapscene.cpp | 150 ++++++++++- src/3d/qgs3dmapscene.h | 12 + src/3d/qgs3dmapsceneentity_p.h | 35 ++- src/3d/qgsvirtualpointcloudentity_p.cpp | 10 +- src/3d/qgsvirtualpointcloudentity_p.h | 4 +- src/app/3d/qgs3dmapcanvas.cpp | 9 + src/app/3d/qgs3dmapcanvaswidget.cpp | 31 +++ src/app/3d/qgs3dmapcanvaswidget.h | 2 + 11 files changed, 455 insertions(+), 75 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index 698582159c7c..ee10c9082823 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -64,6 +64,7 @@ Returns number of pending jobs for all chunked entities enum SceneState { + Canceled, Ready, Updating, }; @@ -112,6 +113,19 @@ Returns the scene's elevation range Returns the 3D map settings. .. versionadded:: 3.30 +%End + + double usedGpuMemory() const; +%Docstring +Returns the used gpu memory for this 3D scene +%End + double maxAvailableGpuMemory() const; +%Docstring +Returns the max available gpu memory for this 3D scene +%End + QList frozenLayers() const; +%Docstring +Returns frozen layer name list %End static QMap< QString, Qgs3DMapScene * > openScenes(); diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 85917e9989e0..c35b968f5488 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -28,6 +28,8 @@ #include +/// BDE TO REMOVE: +#define QGISDEBUG 1 ///@cond PRIVATE static QString _logHeader( const QString &layerName ) @@ -107,6 +109,7 @@ QgsChunkedEntity::~QgsChunkedEntity() } delete mChunkLoaderQueue; + mChunkLoaderQueue = nullptr; while ( !mReplacementQueue->isEmpty() ) { @@ -117,18 +120,64 @@ QgsChunkedEntity::~QgsChunkedEntity() } delete mReplacementQueue; + mReplacementQueue = nullptr; delete mRootNode; + mRootNode = nullptr; if ( mOwnsFactory ) { delete mChunkLoaderFactory; } + mChunkLoaderFactory = nullptr; } +void QgsChunkedEntity::setHasReachedGpuMemoryLimit( bool reached ) +{ + if ( mHasReachedGpuMemoryLimit != reached ) + { + mHasReachedGpuMemoryLimit = reached; + if ( reached ) + { + if ( mActiveJobs.isEmpty() && mChunkLoaderQueue->isEmpty() && mReplacementQueue->isEmpty() ) + return; -void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "has reached gpu memory limit. Cleaning up: active %1 | culled %2 | loading %3 loaded %4" ) + .arg( mActiveNodes.count() ) + .arg( mFrustumCulled ) + .arg( mChunkLoaderQueue->count() ) + .arg( mReplacementQueue->count() ), QGS_LOG_LVL_DEBUG ); + + while ( !mActiveJobs.isEmpty() ) + { + QgsChunkQueueJob *job = mActiveJobs.takeFirst(); + job->cancel(); + job->deleteLater(); + } + + while ( !mChunkLoaderQueue->isEmpty() ) + { + QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst(); + if ( entry->chunk->state() == QgsChunkNode::QueuedForUpdate ) + entry->chunk->cancelQueuedForUpdate(); + else if ( entry->chunk->state() == QgsChunkNode::QueuedForLoad ) + entry->chunk->cancelQueuedForLoad(); + else if ( entry->chunk->state() == QgsChunkNode::Loading ) + entry->chunk->cancelLoading(); + else if ( entry->chunk->state() == QgsChunkNode::Updating ) + entry->chunk->cancelUpdating(); + } + + emit pendingJobsCountChanged(); + } + } +} + +void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext, double availableGpuMemory ) { - if ( !mIsValid ) + mLastKnownAvailableGpuMemory = availableGpuMemory; + + if ( !mIsValid || mHasReachedGpuMemoryLimit ) return; // Let's start the update by removing from loader queue chunks that @@ -235,26 +284,37 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) int QgsChunkedEntity::unloadNodes() { - double usedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); - if ( usedGpuMemory <= mGpuMemoryLimit ) + double currentlyUsedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); + if ( mUsedGpuMemory < currentlyUsedGpuMemory ) + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "GPU memory usage increased! (now_using: %1MB, was: %2MB)" ) + .arg( currentlyUsedGpuMemory ) + .arg( mUsedGpuMemory ), QGS_LOG_LVL_DEBUG ); + + double usableGpuMemory = mLastKnownAvailableGpuMemory + mUsedGpuMemory; + if ( currentlyUsedGpuMemory <= usableGpuMemory ) { QgsDebugMsgLevel( _logHeader( mLayerName ) - + QStringLiteral( "Nothing to unload! (now_using: %1MB)" ) - .arg( usedGpuMemory ), QGS_LOG_LVL_DEBUG ); + + QStringLiteral( "Nothing to unload! (now_using: %1MB, limit: %2MB)" ) + .arg( currentlyUsedGpuMemory ) + .arg( usableGpuMemory ), QGS_LOG_LVL_TRACE ); + mUsedGpuMemory = currentlyUsedGpuMemory; setHasReachedGpuMemoryLimit( false ); return 0; } QgsDebugMsgLevel( _logHeader( mLayerName ) - + QStringLiteral( "Going to unload nodes to free GPU memory (now_using: %1MB)" ) - .arg( usedGpuMemory ), QGS_LOG_LVL_DEBUG ); + + QStringLiteral( "Going to unload nodes to free GPU memory (was_used: %1MB, now_using: %2MB, limit: %3MB)" ) + .arg( mUsedGpuMemory ) + .arg( currentlyUsedGpuMemory ) + .arg( usableGpuMemory ), QGS_LOG_LVL_DEBUG ); int unloaded = 0; // unload nodes starting from the back of the queue with currently loaded // nodes - i.e. those that have been least recently used QgsChunkListEntry *entry = mReplacementQueue->last(); - while ( entry && usedGpuMemory > mGpuMemoryLimit ) + while ( entry && currentlyUsedGpuMemory > usableGpuMemory ) { // not all nodes are safe to unload: we do not want to unload nodes // that are currently active, or have their descendants active or their @@ -264,7 +324,7 @@ int QgsChunkedEntity::unloadNodes() { QgsChunkListEntry *entryPrev = entry->prev; mReplacementQueue->takeEntry( entry ); - usedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() ); + currentlyUsedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() ); mActiveNodes.removeOne( entry->chunk ); entry->chunk->unloadChunk(); // also deletes the entity! ++unloaded; @@ -276,15 +336,19 @@ int QgsChunkedEntity::unloadNodes() } } - if ( usedGpuMemory > mGpuMemoryLimit ) + if ( currentlyUsedGpuMemory > usableGpuMemory ) { setHasReachedGpuMemoryLimit( true ); QgsDebugMsgLevel( _logHeader( mLayerName ) - + QStringLiteral( "Unable to unload enough nodes to free GPU memory (now_using: %1MB, limit: %2MB)" ) - .arg( usedGpuMemory ) - .arg( mGpuMemoryLimit ), QGS_LOG_LVL_DEBUG ); + + QStringLiteral( "Unable to unload enough nodes to free GPU memory (was_used: %1 MB, now_using: %2 MB, limit: %3 MB)" ) + .arg( mUsedGpuMemory ) + .arg( currentlyUsedGpuMemory ) + .arg( usableGpuMemory ), QGS_LOG_LVL_DEBUG ); } + mLastKnownAvailableGpuMemory += ( currentlyUsedGpuMemory - mUsedGpuMemory ); + mUsedGpuMemory = currentlyUsedGpuMemory; + return unloaded; } @@ -336,6 +400,9 @@ void QgsChunkedEntity::updateNodes( const QList &nodes, QgsChunk { for ( QgsChunkNode *node : nodes ) { + if ( mHasReachedGpuMemoryLimit ) + return; + if ( node->state() == QgsChunkNode::QueuedForUpdate ) { mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() ); @@ -432,6 +499,9 @@ struct void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneContext ) { + if ( mHasReachedGpuMemoryLimit ) + return; + QSet nodes; QVector residencyRequests; @@ -488,7 +558,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont } bool becomesActive = false; - // QgsDebugMsgLevel( QStringLiteral( "%1|%2|%3 %4 %5" ).arg( node->tileId().x ).arg( node->tileId().y ).arg( node->tileId().z ).arg( mTau ).arg( screenSpaceError( node, sceneContext ) ), 2 ); + // QgsDebugMsgLevel( QStringLiteral( "%1|%2|%3 %4 %5" ).arg( node->tileId().x ).arg( node->tileId().y ).arg( node->tileId().z ).arg( mTau ).arg( screenSpaceError( node, sceneContext ) ), QGS_LOG_LVL_DEBUG ); if ( node->childCount() == 0 ) { // there's no children available for this node, so regardless of whether it has an acceptable error @@ -560,15 +630,24 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont if ( becomesActive ) { - mActiveNodes << node; - // if we are not using additive strategy we need to make sure the parent primitives are not counted - if ( node->refinementProcess() != Qgis::TileRefinementProcess::Additive && node->parent() && nodes.contains( node->parent() ) ) + if ( !node->entity() ) { - nodes.remove( node->parent() ); - renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() ); + QgsDebugError( _logHeader( mLayerName ) + + QString( "Active node %1 has null entity - this should never happen!" ) + .arg( node->tileId().text() ) ); + } + else + { + mActiveNodes << node; + // if we are not using additive strategy we need to make sure the parent primitives are not counted + if ( node->refinementProcess() != Qgis::TileRefinementProcess::Additive && node->parent() && nodes.contains( node->parent() ) ) + { + nodes.remove( node->parent() ); + renderedCount -= mChunkLoaderFactory->primitivesCount( node->parent() ); + } + renderedCount += mChunkLoaderFactory->primitivesCount( node ); + nodes.insert( node ); } - renderedCount += mChunkLoaderFactory->primitivesCount( node ); - nodes.insert( node ); } } @@ -623,8 +702,20 @@ void QgsChunkedEntity::onActiveJobFinished() QgsChunkQueueJob *job = qobject_cast( sender() ); Q_ASSERT( job ); + + if ( mHasReachedGpuMemoryLimit ) + { + // cleanup the job that has just finished + mActiveJobs.removeOne( job ); + job->deleteLater(); + emit pendingJobsCountChanged(); + + return; + } + Q_ASSERT( mActiveJobs.contains( job ) ); + // this <==> root entity --> node <==> sub node/chunk loaded by this QgsChunkNode *node = job->chunk(); if ( QgsChunkLoader *loader = qobject_cast( job ) ) @@ -638,25 +729,86 @@ void QgsChunkedEntity::onActiveJobFinished() QgsEventTracing::ScopedEvent e( "3D", QString( "create" ) ); // mark as loaded + create entity Qt3DCore::QEntity *entity = node->loader()->createEntity( this ); + // entity <==> sub node entity (attached to this, ie. root entity) if ( entity ) { - // The returned QEntity is initially enabled, so let's add it to active nodes too. - // Soon afterwards updateScene() will be called, which would remove it from the scene - // if the node should not be shown anymore. Ideally entities should be initially disabled, - // but there seems to be a bug in Qt3D - if entity is disabled initially, showing it - // by setting setEnabled(true) is not reliable (entity eventually gets shown, but only after - // some more changes in the scene) - see https://github.com/qgis/QGIS/issues/48334 - mActiveNodes << node; + double rootEntityGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); + double prevUsedGpuMemory = mUsedGpuMemory; + double entityGpuMemory = rootEntityGpuMemory - prevUsedGpuMemory; +#ifdef QGISDEBUG + // search layer name for log purpose + Qgs3DMapSceneEntity *ent = dynamic_cast( this ); + while ( ent->layerName().isEmpty() || ent->layerName() == "unknown" ) + { + if ( ent->parent() && dynamic_cast( ent->parent() ) ) + ent = dynamic_cast( ent->parent() ); + else + break; + } + QString ln = ent->layerName(); + if ( ln.isEmpty() || ln == "unknown" ) + ln = "unknown_deep"; + + QgsDebugMsgLevel( _logHeader( ln ) + + QStringLiteral( "Checking entity %1. new node_size: %2, old node_size: %3, cur entity_size: %4, " ) + .arg( node->tileId().text() ) + .arg( rootEntityGpuMemory ) + .arg( mUsedGpuMemory ) + .arg( entityGpuMemory ) + , QGS_LOG_LVL_DEBUG ); +#endif + + // check memory consumption, ie. if we are near or will reach available memory + if ( entityGpuMemory * mActiveJobs.size() / 2.0 > mLastKnownAvailableGpuMemory ) + { + // we need to cancel this pre-loaded entity and set mHasReachedGpuMemoryLimit to true + setHasReachedGpuMemoryLimit( true ); + node->cancelLoading(); + entity->deleteLater(); // delete the failed sub entity now + + // compute what we can gain: + double rootEntityGpuMemoryAfter = Qgs3DUtils::calculateEntityGpuMemorySize( this ); + mLastKnownAvailableGpuMemory -= ( rootEntityGpuMemoryAfter - rootEntityGpuMemory ); + mUsedGpuMemory = rootEntityGpuMemoryAfter; +#ifdef QGISDEBUG + QgsDebugMsgLevel( _logHeader( ln ) + + QStringLiteral( "Cancel loading %1. node_size: %2, availGpuMem: %3" ) + .arg( node->tileId().text() ) + .arg( mUsedGpuMemory ) + .arg( mLastKnownAvailableGpuMemory ), QGS_LOG_LVL_DEBUG ); +#endif + } + else + { + // we can keep this pre-loaded entity and add it to mActiveNodes + mLastKnownAvailableGpuMemory -= ( rootEntityGpuMemory - prevUsedGpuMemory ); + mUsedGpuMemory = rootEntityGpuMemory; +#ifdef QGISDEBUG + QgsDebugMsgLevel( _logHeader( ln ) + + QStringLiteral( "New entity loaded %1. node_size: %2, availGpuMem: %3" ) + .arg( node->tileId().text() ) + .arg( mUsedGpuMemory ) + .arg( mLastKnownAvailableGpuMemory ), QGS_LOG_LVL_DEBUG ); +#endif - // load into node (should be in main thread again) - node->setLoaded( entity ); + // The returned QEntity is initially enabled, so let's add it to active nodes too. + // Soon afterwards updateScene() will be called, which would remove it from the scene + // if the node should not be shown anymore. Ideally entities should be initially disabled, + // but there seems to be a bug in Qt3D - if entity is disabled initially, showing it + // by setting setEnabled(true) is not reliable (entity eventually gets shown, but only after + // some more changes in the scene) - see https://github.com/qgis/QGIS/issues/48334 + mActiveNodes << node; - mReplacementQueue->insertFirst( node->replacementQueueEntry() ); + // load into node (should be in main thread again) + node->setLoaded( entity ); - emit newEntityCreated( entity ); + mReplacementQueue->insertFirst( node->replacementQueueEntry() ); + + emit newEntityCreated( entity, entityGpuMemory ); + } } - else + else // entity is NULL { node->setHasData( false ); node->cancelLoading(); @@ -665,11 +817,22 @@ void QgsChunkedEntity::onActiveJobFinished() // now we need an update! mNeedsUpdate = true; } - else + else // job is NOT a QgsChunkLoader { - Q_ASSERT( node->state() == QgsChunkNode::Updating ); - QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Update" ), node->tileId().text() ); - node->setUpdated(); + if ( ! mHasReachedGpuMemoryLimit ) // if mHasReachedGpuMemoryLimit some job cannot be finished yet, node will have bad state + { + if ( node->state() != QgsChunkNode::Updating ) + { + QgsDebugMsgLevel( QStringLiteral( "Node %1 should be in state Updating but is state: '%2'" ) + .arg( node->tileId().text() ) + .arg( node->state() ), QGS_LOG_LVL_WARNING ); + } + else + { + QgsEventTracing::addEvent( QgsEventTracing::AsyncEnd, QStringLiteral( "3D" ), QStringLiteral( "Update" ), node->tileId().text() ); + node->setUpdated(); + } + } } // cleanup the job that has just finished @@ -685,7 +848,7 @@ void QgsChunkedEntity::onActiveJobFinished() void QgsChunkedEntity::startJobs() { - while ( mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() ) + while ( !mHasReachedGpuMemoryLimit && mActiveJobs.count() < 4 && !mChunkLoaderQueue->isEmpty() ) { QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst(); Q_ASSERT( entry ); @@ -726,12 +889,17 @@ QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node ) void QgsChunkedEntity::cancelActiveJob( QgsChunkQueueJob *job ) { - Q_ASSERT( job ); + if ( !job || !mActiveJobs.contains( job ) ) + { + QgsDebugError( _logHeader( mLayerName ) + + "Cannot cancel null job!" ); + return; + } QgsChunkNode *node = job->chunk(); disconnect( job, &QgsChunkQueueJob::finished, this, &QgsChunkedEntity::onActiveJobFinished ); - if ( qobject_cast( job ) ) + if ( dynamic_cast( job ) ) { // return node back to skeleton node->cancelLoading(); diff --git a/src/3d/chunks/qgschunkedentity_p.h b/src/3d/chunks/qgschunkedentity_p.h index 022f0459239c..691dd5380876 100644 --- a/src/3d/chunks/qgschunkedentity_p.h +++ b/src/3d/chunks/qgschunkedentity_p.h @@ -72,7 +72,7 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity ~QgsChunkedEntity() override; //! Called when e.g. camera changes and entity may need updated - void handleSceneUpdate( const SceneContext &sceneContext ) override; + void handleSceneUpdate( const SceneContext &sceneContext, double availableGpuMemory ) override; //! Returns number of jobs pending for this entity until it is fully loaded/updated in the current view int pendingJobsCount() const override; @@ -93,6 +93,12 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity //! Returns the root node of the whole quadtree hierarchy of nodes QgsChunkNode *rootNode() const { return mRootNode; } + /** + * Returns the limit of the GPU memory used to render the entity in megabytes + * \since QGIS 3.36 + */ + double usedGpuMemory() const override { return mUsedGpuMemory; } + /** * Checks if \a ray intersects the entity by using the specified parameters in \a context and returns information about the hits. * This method is typically used by map tools that need to identify the exact location on a 3d entity that the mouse cursor points at, @@ -104,6 +110,8 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity */ virtual QVector rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const; + void setHasReachedGpuMemoryLimit( bool reached ) override; + protected: //! Cancels the background job that is currently in progress void cancelActiveJob( QgsChunkQueueJob *job ); @@ -168,6 +176,9 @@ class QgsChunkedEntity : public Qgs3DMapSceneEntity bool mIsValid = true; int mPrimitivesBudget = std::numeric_limits::max(); + + double mLastKnownAvailableGpuMemory = 0.0; // in megabytes + double mUsedGpuMemory = 0.0; // in megabytes }; /// @endcond diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 34dc7cc72474..b591a63dfa27 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -79,6 +79,7 @@ #include "qgswindow3dengine.h" #include "qgspointcloudlayer.h" #include "qgsmeshterraingenerator.h" +#include "qgsrasterlayer.h" std::function< QMap< QString, Qgs3DMapScene * >() > Qgs3DMapScene::sOpenScenesFunction = [] { return QMap< QString, Qgs3DMapScene * >(); }; @@ -121,6 +122,36 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine addCameraRotationCenterEntity( mCameraController ); updateLights(); + // read defaut gpu memory in 3d map settings + { + const QgsSettings settings; + QString fromSetting; + double defaultGpuMemory = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); + if ( settings.value( QStringLiteral( "map3d/gpuMemoryLimitAuto" ), true, QgsSettings::App ).toBool() ) + { + int memoryAvailableKB = Qgs3DUtils::estimateGpuMemoryAvailable(); + if ( memoryAvailableKB == -1 ) + { + mMaxAvailableGpuMemory = defaultGpuMemory; + fromSetting = "user setting, auto failed"; + } + else + { + mMaxAvailableGpuMemory = 0.8 * static_cast( memoryAvailableKB ) / 1024.0; + fromSetting = "auto computed"; + } + } + else + { + mMaxAvailableGpuMemory = defaultGpuMemory; + fromSetting = "user setting"; + } + QgsDebugMsgLevel( QStringLiteral( "Gpu limit for '%1' is set to %2MB (%3)" ) + .arg( objectName() ) + .arg( mMaxAvailableGpuMemory ) + .arg( fromSetting ), QGS_LOG_LVL_INFO ); + } + // create terrain entity and other entities from other layers createTerrainDeferred(); @@ -389,12 +420,18 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); - if ( entity ) + if ( entity && !entity->hasReachedGpuMemoryLimit() ) if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) { - entity->handleSceneUpdate( buildSceneContext() ); + entity->handleSceneUpdate( buildSceneContext(), mMaxAvailableGpuMemory - mUsedGpuMemory ); if ( entity->hasReachedGpuMemoryLimit() ) + { + QgsDebugMsgLevel( _logHeader( entity->layerName() ) + + QString( "has been disabled!" ), QGS_LOG_LVL_WARNING ); emit gpuMemoryLimitReached(); + setSceneState( Canceled ); + break; + } } } @@ -592,14 +629,14 @@ void Qgs3DMapScene::onLayerRenderer3DChanged() QgsDebugMsgLevel( _logHeader( layer->name() ) + QString( "onLayerRenderer3DChanged begin!!!" ), QGS_LOG_LVL_DEBUG ); if ( layer == mTerrainLayer ) - { -// createTerrain(); return; - } // remove old entity - if any removeLayerEntity( layer ); + // TODO?? + if ( dynamic_cast( layer ) ) + QgsDebugMsgLevel( _logHeader( layer->name() ) + "todo: add new QgsRasterLayer?", QGS_LOG_LVL_DEBUG ); // add new entity - if any 3D renderer addLayerEntity( layer ); } @@ -635,6 +672,24 @@ void Qgs3DMapScene::onLayersChanged() { addLayerEntity( layer ); } + + QgsDebugMsgLevel( QString( "to remove: %1, to add: %2" ) + .arg( layersBefore.size() ) + .arg( layersAdded.size() ), QGS_LOG_LVL_DEBUG ); + + if ( layersBefore.size() != 0 ) // layers have been disabled, we try to reactivate frozen layers + { + for ( Qt3DCore::QEntity *entity : mLayerEntities.values() ) + { + Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( entity ); + if ( sceneEntity && sceneEntity->hasReachedGpuMemoryLimit() ) + { + QgsDebugMsgLevel( _logHeader( sceneEntity->layerName() ) + + QStringLiteral( "Frreeedd! Will try to load new entities!" ), QGS_LOG_LVL_DEBUG ); + sceneEntity->setHasReachedGpuMemoryLimit( false ); + } + } + } } void Qgs3DMapScene::updateTemporal() @@ -727,22 +782,38 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) newEntity->setParent( this ); mLayerEntities.insert( layer, newEntity ); - finalizeNewEntity( newEntity ); + // finalizeNewEntity( newEntity ); // TODO: need to finalize newEntity? if ( Qgs3DMapSceneEntity *sceneNewEntity = qobject_cast( newEntity ) ) { sceneNewEntity->setLayerName( layer->name() ); needsSceneUpdate = true; - connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this]( Qt3DCore::QEntity * entity ) + connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this, layer]( Qt3DCore::QEntity * entity, double entityGpuMem ) { finalizeNewEntity( entity ); + + if ( std::isnan( entityGpuMem ) ) + { + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "Successfully loaded new entity (unknown memory usage!)." ), QGS_LOG_LVL_DEBUG ); + } + else + { + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QStringLiteral( "Successfully loaded new entity (%1MB). Total used gpu mem: %2MB" ) + .arg( entityGpuMem ) + .arg( mUsedGpuMemory + entityGpuMem ), QGS_LOG_LVL_DEBUG ); + mUsedGpuMemory += entityGpuMem; + } } ); connect( sceneNewEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); } else { + finalizeNewEntity( newEntity ); + QgsDebugMsgLevel( _logHeader( layer->name() ) + QStringLiteral( "is not a Qgs3DMapSceneEntity" ), QGS_LOG_LVL_INFO ); } @@ -787,6 +858,11 @@ void Qgs3DMapScene::removeLayerEntity( QgsMapLayer *layer ) if ( Qgs3DMapSceneEntity *sceneEntity = qobject_cast( entity ) ) { disconnect( sceneEntity, &Qgs3DMapSceneEntity::pendingJobsCountChanged, this, &Qgs3DMapScene::totalPendingJobsCountChanged ); + mUsedGpuMemory -= sceneEntity->usedGpuMemory(); + QgsDebugMsgLevel( _logHeader( layer->name() ) + + QString( "removed! Used gpu mem: %1MB (was reduced by %2MB)" ) + .arg( mUsedGpuMemory ) + .arg( sceneEntity->usedGpuMemory() ), QGS_LOG_LVL_DEBUG ); } entity->deleteLater(); } @@ -951,16 +1027,54 @@ void Qgs3DMapScene::updateSceneState() return; } - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + if ( mSceneState == Canceled ) { - Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); - if ( entity && entity->isEnabled() && entity->pendingJobsCount() > 0 ) + // TODO: really nothing to do? + } + else + { + for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { - setSceneState( Updating ); - return; + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity && entity->isEnabled() && !entity->hasReachedGpuMemoryLimit() && entity->pendingJobsCount() > 0 ) + { + setSceneState( Updating ); + return; + } } } + if ( mSceneState != Ready ) + { + // display layer sumup: + double usedGpuMemorySum = 0.0; + QgsDebugMsgLevel( QStringLiteral( "Whole scene uses %1MB over %2MB GPU memory. Per layer:" ) + .arg( mUsedGpuMemory ) + .arg( mMaxAvailableGpuMemory ), QGS_LOG_LVL_DEBUG ); + + for ( QgsMapLayer *layer : mLayerEntities.keys() ) + { + Qt3DCore::QEntity *qtEntity = mLayerEntities[layer]; + Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + if ( entity && entity->isEnabled() ) + { + QgsDebugMsgLevel( QStringLiteral( " '%1' uses %2MB (frozen: %3)." ) + .arg( layer->name() ) + .arg( entity->usedGpuMemory() ) + .arg( entity->hasReachedGpuMemoryLimit() ), QGS_LOG_LVL_DEBUG ); + usedGpuMemorySum += entity->usedGpuMemory(); + } + } + if ( mUsedGpuMemory != usedGpuMemorySum ) + { + mUsedGpuMemory = usedGpuMemorySum; + QgsDebugMsgLevel( QStringLiteral( "/!\\ UPDATED ==> whole scene uses %1MB over %2MB." ) + .arg( mUsedGpuMemory ) + .arg( mMaxAvailableGpuMemory ), QGS_LOG_LVL_DEBUG ); + } + + } + setSceneState( Ready ); } @@ -1254,3 +1368,15 @@ void Qgs3DMapScene::on3DAxisSettingsChanged() } } } + +QList Qgs3DMapScene::frozenLayers() const +{ + QList out; + for ( QgsMapLayer *l : mLayerEntities.keys() ) + { + Qgs3DMapSceneEntity *entity = dynamic_cast( mLayerEntities[l] ); + if ( entity && entity->hasReachedGpuMemoryLimit() ) + out << l->name(); + } + return out; +} diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 5af7c18be955..7fc4dea43a4b 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -113,6 +113,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject //! Enumeration of possible states of the 3D scene enum SceneState { + Canceled, //!< The scene load has been canceled Ready, //!< The scene is fully loaded/updated Updating, //!< The scene is still being loaded/updated }; @@ -186,6 +187,13 @@ class _3D_EXPORT Qgs3DMapScene : public QObject */ Qgs3DMapSettings *mapSettings() const { return &mMap; } + //! Returns the used gpu memory for this 3D scene + double usedGpuMemory() const { return mUsedGpuMemory;} + //! Returns the max available gpu memory for this 3D scene + double maxAvailableGpuMemory() const { return mMaxAvailableGpuMemory;} + //! Returns frozen layer name list + QList frozenLayers() const; + /** * Returns a map of 3D map scenes (by name) open in the QGIS application. * @@ -299,5 +307,9 @@ class _3D_EXPORT Qgs3DMapScene : public QObject //! 3d axis visualization Qgs3DAxis *m3DAxis = nullptr; + //! max gpu memory available for this 3D scene (at scene creation) + double mMaxAvailableGpuMemory = 0.0; + //! current gpu memory used by this 3D scene + double mUsedGpuMemory = 0.0; }; #endif // QGS3DMAPSCENE_H diff --git a/src/3d/qgs3dmapsceneentity_p.h b/src/3d/qgs3dmapsceneentity_p.h index 85522f5bdc16..763a2893a61d 100644 --- a/src/3d/qgs3dmapsceneentity_p.h +++ b/src/3d/qgs3dmapsceneentity_p.h @@ -32,7 +32,6 @@ #include #include "qgsrange.h" -#include "qgssettings.h" #define SIP_NO_FILE @@ -49,10 +48,7 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Constructs a chunked entity Qgs3DMapSceneEntity( Qt3DCore::QNode *parent = nullptr ) : Qt3DCore::QEntity( parent ) - { - const QgsSettings settings; - mGpuMemoryLimit = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); - } + {} //! Records some bits about the scene (context for handleSceneUpdate() method) struct SceneContext @@ -63,8 +59,15 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity QMatrix4x4 viewProjectionMatrix; //!< For frustum culling }; - //! Called when e.g. camera changes and entity may need updated - virtual void handleSceneUpdate( const SceneContext &sceneContext ) { Q_UNUSED( sceneContext ) } + /** + * Called when e.g. camera changes and entity may need updated + * \param availableGpuMemory remaining gpu memory for the 3D scene + */ + virtual void handleSceneUpdate( const SceneContext &sceneContext, double availableGpuMemory ) + { + Q_UNUSED( sceneContext ) + Q_UNUSED( availableGpuMemory ) + } //! Returns number of jobs pending for this entity until it is fully loaded/updated in the current view virtual int pendingJobsCount() const { return 0; } @@ -75,12 +78,11 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Returns the near to far plane range for the entity using the specified \a viewMatrix virtual QgsRange getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const { Q_UNUSED( viewMatrix ) return QgsRange( 1e9, 0 ); } - - //! Sets the limit of the GPU memory used to render the entity - void setGpuMemoryLimit( double gpuMemoryLimit ) { mGpuMemoryLimit = gpuMemoryLimit; } - - //! Returns the limit of the GPU memory used to render the entity in megabytes - double gpuMemoryLimit() const { return mGpuMemoryLimit; } + /** + * Returns the GPU memory currently used to render the entity in megabytes + * \since QGIS 3.34 + */ + virtual double usedGpuMemory() const { return 0.0; } //! Returns whether the entity has reached GPU memory limit bool hasReachedGpuMemoryLimit() const { return mHasReachedGpuMemoryLimit; } @@ -90,20 +92,17 @@ class Qgs3DMapSceneEntity : public Qt3DCore::QEntity //! Returns layer name in which this entity in included virtual QString layerName() const { return mLayerName; } - protected: //! Sets whether the GPU memory limit has been reached - void setHasReachedGpuMemoryLimit( bool reached ) { mHasReachedGpuMemoryLimit = reached; } + virtual void setHasReachedGpuMemoryLimit( bool reached ) { mHasReachedGpuMemoryLimit = reached; } signals: //! Emitted when the number of pending jobs changes (some jobs have finished or some jobs have been just created) void pendingJobsCountChanged(); //! Emitted when a new 3D entity has been created. Other components can use that to do extra work - void newEntityCreated( Qt3DCore::QEntity *entity ); + void newEntityCreated( Qt3DCore::QEntity *entity, double usedGpuSize = std::numeric_limits::quiet_NaN() ); protected: - //! Limit how much GPU memory this entity can use - double mGpuMemoryLimit = 500.0; // in megabytes //! Whether the entity is currently over the GPU memory limit (used to report a warning to the user) bool mHasReachedGpuMemoryLimit = false; //! Layer name in which this entity in included diff --git a/src/3d/qgsvirtualpointcloudentity_p.cpp b/src/3d/qgsvirtualpointcloudentity_p.cpp index 66204dbe5ceb..44ec7b934f25 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.cpp +++ b/src/3d/qgsvirtualpointcloudentity_p.cpp @@ -101,7 +101,7 @@ void QgsVirtualPointCloudEntity::createChunkedEntityForSubIndex( int i ) emit newEntityCreated( newChunkedEntity ); } -void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext ) +void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneContext, double availableGpuMemory ) { const QVector subIndexes = provider()->subIndexes(); for ( int i = 0; i < subIndexes.size(); ++i ) @@ -126,7 +126,7 @@ void QgsVirtualPointCloudEntity::handleSceneUpdate( const SceneContext &sceneCon setRenderSubIndexAsBbox( i, displayAsBbox ); if ( !displayAsBbox && mChunkedEntitiesMap.contains( i ) ) - mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext ); + mChunkedEntitiesMap[i]->handleSceneUpdate( sceneContext, availableGpuMemory ); } updateBboxEntity(); } @@ -208,4 +208,10 @@ void QgsVirtualPointCloudEntity::setRenderSubIndexAsBbox( int i, bool asBbox ) mChunkedEntitiesMap[i]->setEnabled( !asBbox ); } + +double QgsVirtualPointCloudEntity::usedGpuMemory() const +{ + // TODO: to implement + return 0.0; +} /// @endcond diff --git a/src/3d/qgsvirtualpointcloudentity_p.h b/src/3d/qgsvirtualpointcloudentity_p.h index 918b1d529eea..7c75b287f465 100644 --- a/src/3d/qgsvirtualpointcloudentity_p.h +++ b/src/3d/qgsvirtualpointcloudentity_p.h @@ -60,7 +60,7 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity double zValueScale, double zValueOffset, int pointBudget ); //! This is called when the camera moves. It's responsible for loading new indexes and decides if subindex will be rendered as bbox or chunked entity. - void handleSceneUpdate( const SceneContext &sceneContext ) override; + void handleSceneUpdate( const SceneContext &sceneContext, double availableGpuMemory ) override; QgsRange getNearFarPlaneRange( const QMatrix4x4 &viewMatrix ) const override; @@ -68,6 +68,8 @@ class QgsVirtualPointCloudEntity : public Qgs3DMapSceneEntity bool needsUpdate() const override; + double usedGpuMemory() const override; + public slots: //! Creates a child QgsPointCloudLayerChunkedEntity for the \a i th sub index void createChunkedEntityForSubIndex( int i ); diff --git a/src/app/3d/qgs3dmapcanvas.cpp b/src/app/3d/qgs3dmapcanvas.cpp index ff6506443d6e..04ea7c9775db 100644 --- a/src/app/3d/qgs3dmapcanvas.cpp +++ b/src/app/3d/qgs3dmapcanvas.cpp @@ -29,6 +29,7 @@ #include "qgs3dnavigationwidget.h" #include "qgssettings.h" #include "qgstemporalcontroller.h" +#include "qgs3dmapcanvaswidget.h" Qgs3DMapCanvas::Qgs3DMapCanvas( QWidget *parent ) : QWidget( parent ) @@ -117,6 +118,14 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map ) mScene->deleteLater(); } mScene = newScene; + + // update scene name + if ( parent() && dynamic_cast( parent() ) ) + { + Qgs3DMapCanvasWidget *parentWidget = dynamic_cast( parent() ); + mScene->setObjectName( parentWidget->canvasName() ); + } + connect( mScene, &Qgs3DMapScene::fpsCountChanged, this, &Qgs3DMapCanvas::fpsCountChanged ); connect( mScene, &Qgs3DMapScene::fpsCounterEnabledChanged, this, &Qgs3DMapCanvas::fpsCounterEnabledChanged ); connect( mScene, &Qgs3DMapScene::viewed2DExtentFrom3DChanged, this, &Qgs3DMapCanvas::viewed2DExtentFrom3DChanged ); diff --git a/src/app/3d/qgs3dmapcanvaswidget.cpp b/src/app/3d/qgs3dmapcanvaswidget.cpp index 206dcad23a4d..4d28b3f6d899 100644 --- a/src/app/3d/qgs3dmapcanvaswidget.cpp +++ b/src/app/3d/qgs3dmapcanvaswidget.cpp @@ -230,6 +230,10 @@ Qgs3DMapCanvasWidget::Qgs3DMapCanvasWidget( const QString &name, bool isDocked ) mLabelFpsCounter = new QLabel( this ); mLabelNavigationSpeed = new QLabel( this ); + mProgressGpuMemory = new QProgressBar( this ); + mProgressGpuMemory->setTextVisible( true ); + mProgressGpuMemory->setVisible( true ); + mProgressGpuMemory->setAlignment( Qt::AlignLeft ); mAnimationWidget = new Qgs3DAnimationWidget( this ); mAnimationWidget->setVisible( false ); @@ -240,6 +244,7 @@ Qgs3DMapCanvasWidget::Qgs3DMapCanvasWidget( const QString &name, bool isDocked ) topLayout->setContentsMargins( 0, 0, 0, 0 ); topLayout->setSpacing( style()->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ) ); topLayout->addWidget( toolBar ); + topLayout->addWidget( mProgressGpuMemory ); topLayout->addStretch( 1 ); topLayout->addWidget( mLabelPendingJobs ); topLayout->addWidget( mProgressPendingJobs ); @@ -376,7 +381,9 @@ void Qgs3DMapCanvasWidget::setMapSettings( Qgs3DMapSettings *map ) connect( mCanvas->scene(), &Qgs3DMapScene::totalPendingJobsCountChanged, this, &Qgs3DMapCanvasWidget::onTotalPendingJobsCountChanged ); connect( mCanvas->scene(), &Qgs3DMapScene::gpuMemoryLimitReached, this, &Qgs3DMapCanvasWidget::onGpuMemoryLimitReached ); + connect( mCanvas->scene(), &Qgs3DMapScene::sceneStateChanged, this, &Qgs3DMapCanvasWidget::onSceneStateChanged ); + mProgressGpuMemory->setRange( 0, std::ceil( mCanvas->scene()->maxAvailableGpuMemory() * 1024.0 ) ); mAnimationWidget->setCameraController( mCanvas->scene()->cameraController() ); mAnimationWidget->setMap( map ); @@ -534,6 +541,30 @@ void Qgs3DMapCanvasWidget::onMainCanvasColorChanged() mCanvas->map()->setBackgroundColor( mMainCanvas->canvasColor() ); } +void Qgs3DMapCanvasWidget::onSceneStateChanged() +{ + double newMem = std::min( mCanvas->scene()->usedGpuMemory(), mCanvas->scene()->maxAvailableGpuMemory() ); + mProgressGpuMemory->setValue( std::ceil( newMem * 1024.0 ) ); + mProgressGpuMemory->setToolTip( QStringLiteral( "GPU memory used: %1MB / available: %2MB" ) + .arg( mCanvas->scene()->usedGpuMemory() ) + .arg( mCanvas->scene()->maxAvailableGpuMemory() ) ); + + if ( mCanvas->scene()->frozenLayers().isEmpty() ) + { + mProgressGpuMemory->setFormat( "Gpu %p%" ); + } + else + { + mProgressGpuMemory->setFormat( "Frozen!" ); + } + + QgsDebugMsgLevel( QStringLiteral( "ProgressBar set to: %1 < %2 (%3MB) < %4" ) + .arg( mProgressGpuMemory->minimum() ) + .arg( mProgressGpuMemory->value() ) + .arg( mCanvas->scene()->usedGpuMemory() ) + .arg( mProgressGpuMemory->maximum() ), QGS_LOG_LVL_DEBUG ); +} + void Qgs3DMapCanvasWidget::onTotalPendingJobsCountChanged() { const int count = mCanvas->scene() ? mCanvas->scene()->totalPendingJobsCount() : 0; diff --git a/src/app/3d/qgs3dmapcanvaswidget.h b/src/app/3d/qgs3dmapcanvaswidget.h index ad6835a05f5e..3db19f27340c 100644 --- a/src/app/3d/qgs3dmapcanvaswidget.h +++ b/src/app/3d/qgs3dmapcanvaswidget.h @@ -86,6 +86,7 @@ class APP_EXPORT Qgs3DMapCanvasWidget : public QWidget void onMainCanvasLayersChanged(); void onMainCanvasColorChanged(); + void onSceneStateChanged(); void onTotalPendingJobsCountChanged(); void updateFpsCount( float fpsCount ); void cameraNavigationSpeedChanged( double speed ); @@ -105,6 +106,7 @@ class APP_EXPORT Qgs3DMapCanvasWidget : public QWidget Qgs3DAnimationWidget *mAnimationWidget = nullptr; QgsMapCanvas *mMainCanvas = nullptr; QProgressBar *mProgressPendingJobs = nullptr; + QProgressBar *mProgressGpuMemory = nullptr; QLabel *mLabelPendingJobs = nullptr; QLabel *mLabelFpsCounter = nullptr; QLabel *mLabelNavigationSpeed = nullptr; From ab7117bf48d01e76da1b8dee1a28de07a0b547fa Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 16:21:40 +0100 Subject: [PATCH 21/34] qgschunkedentity: Handle QgsChunkListEntry deletion This way, it is created and deleted by the same class. --- src/3d/chunks/qgschunkedentity_p.cpp | 22 ++++++++++++++-------- src/3d/chunks/qgschunknode_p.cpp | 2 -- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index c35b968f5488..e49b8bcd92e0 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -106,6 +106,8 @@ QgsChunkedEntity::~QgsChunkedEntity() node->cancelQueuedForUpdate(); else Q_ASSERT( false ); // impossible! + + delete entry; // created here, deleted here } delete mChunkLoaderQueue; @@ -405,8 +407,10 @@ void QgsChunkedEntity::updateNodes( const QList &nodes, QgsChunk if ( node->state() == QgsChunkNode::QueuedForUpdate ) { - mChunkLoaderQueue->takeEntry( node->loaderQueueEntry() ); + QgsChunkListEntry *entry = node->loaderQueueEntry(); + mChunkLoaderQueue->takeEntry( entry ); node->cancelQueuedForUpdate(); + delete entry; } else if ( node->state() == QgsChunkNode::Updating ) { @@ -443,19 +447,21 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) } // Step 2: remove collected chunks from the loading queue - for ( QgsChunkNode *n : toRemoveFromLoaderQueue ) + for ( QgsChunkNode *node : toRemoveFromLoaderQueue ) { - mChunkLoaderQueue->takeEntry( n->loaderQueueEntry() ); - if ( n->state() == QgsChunkNode::QueuedForLoad ) + QgsChunkListEntry *entry = node->loaderQueueEntry(); + mChunkLoaderQueue->takeEntry( entry ); + if ( node->state() == QgsChunkNode::QueuedForLoad ) { - n->cancelQueuedForLoad(); + node->cancelQueuedForLoad(); } else // queued for update { - n->cancelQueuedForUpdate(); - mReplacementQueue->takeEntry( n->replacementQueueEntry() ); - n->unloadChunk(); // also deletes the entity! + node->cancelQueuedForUpdate(); + mReplacementQueue->takeEntry( node->replacementQueueEntry() ); + node->unloadChunk(); // also deletes the entity! } + delete entry; } if ( !toRemoveFromLoaderQueue.isEmpty() ) diff --git a/src/3d/chunks/qgschunknode_p.cpp b/src/3d/chunks/qgschunknode_p.cpp index b28acd8bb5b5..e3091bc12486 100644 --- a/src/3d/chunks/qgschunknode_p.cpp +++ b/src/3d/chunks/qgschunknode_p.cpp @@ -111,7 +111,6 @@ void QgsChunkNode::cancelQueuedForLoad() Q_ASSERT( mState == QueuedForLoad ); Q_ASSERT( mLoaderQueueEntry ); - delete mLoaderQueueEntry; mLoaderQueueEntry = nullptr; mState = QgsChunkNode::Skeleton; @@ -196,7 +195,6 @@ void QgsChunkNode::cancelQueuedForUpdate() mState = Loaded; mUpdaterFactory = nullptr; // not owned by the node - delete mLoaderQueueEntry; mLoaderQueueEntry = nullptr; } From e70c42de4bdd383a6028379c9a3c23321f5b4973 Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 16:28:27 +0100 Subject: [PATCH 22/34] qgschunkedentity: Delete the loader once the active job is finished This prevents a memory leak. --- src/3d/chunks/qgschunkedentity_p.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index e49b8bcd92e0..7f3e81e33648 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -820,6 +820,8 @@ void QgsChunkedEntity::onActiveJobFinished() node->cancelLoading(); } + loader->deleteLater(); + // now we need an update! mNeedsUpdate = true; } From cab4dab2029fb8097c2febe25068a878c6a1789b Mon Sep 17 00:00:00 2001 From: Jean Felder Date: Mon, 11 Dec 2023 17:01:51 +0100 Subject: [PATCH 23/34] qgschunknode: Use the same enum syntax everywhere --- src/3d/chunks/qgschunknode_p.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/3d/chunks/qgschunknode_p.cpp b/src/3d/chunks/qgschunknode_p.cpp index e3091bc12486..22fbb0d1bdf9 100644 --- a/src/3d/chunks/qgschunknode_p.cpp +++ b/src/3d/chunks/qgschunknode_p.cpp @@ -27,7 +27,7 @@ QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, f , mError( error ) , mNodeId( nodeId ) , mParent( parent ) - , mState( Skeleton ) + , mState( QgsChunkNode::Skeleton ) , mLoaderQueueEntry( nullptr ) , mReplacementQueueEntry( nullptr ) , mLoader( nullptr ) @@ -39,7 +39,7 @@ QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, f QgsChunkNode::~QgsChunkNode() { - Q_ASSERT( mState == Skeleton ); + Q_ASSERT( mState == QgsChunkNode::Skeleton ); Q_ASSERT( !mLoaderQueueEntry ); Q_ASSERT( !mReplacementQueueEntry ); Q_ASSERT( !mLoader ); // should be deleted when removed from loader queue @@ -98,7 +98,7 @@ QList QgsChunkNode::descendants() void QgsChunkNode::setQueuedForLoad( QgsChunkListEntry *entry ) { - Q_ASSERT( mState == Skeleton ); + Q_ASSERT( mState == QgsChunkNode::Skeleton ); Q_ASSERT( !mLoaderQueueEntry ); Q_ASSERT( !mLoader ); @@ -108,7 +108,7 @@ void QgsChunkNode::setQueuedForLoad( QgsChunkListEntry *entry ) void QgsChunkNode::cancelQueuedForLoad() { - Q_ASSERT( mState == QueuedForLoad ); + Q_ASSERT( mState == QgsChunkNode::QueuedForLoad ); Q_ASSERT( mLoaderQueueEntry ); mLoaderQueueEntry = nullptr; @@ -118,11 +118,11 @@ void QgsChunkNode::cancelQueuedForLoad() void QgsChunkNode::setLoading( QgsChunkLoader *chunkLoader ) { - Q_ASSERT( mState == QueuedForLoad ); + Q_ASSERT( mState == QgsChunkNode::QueuedForLoad ); Q_ASSERT( !mLoader ); Q_ASSERT( mLoaderQueueEntry ); - mState = Loading; + mState = QgsChunkNode::Loading; mLoader = chunkLoader; mLoaderQueueEntry = nullptr; } @@ -179,20 +179,20 @@ void QgsChunkNode::setQueuedForUpdate( QgsChunkListEntry *entry, QgsChunkQueueJo Q_ASSERT( !mUpdater ); Q_ASSERT( !mUpdaterFactory ); - mState = QueuedForUpdate; + mState = QgsChunkNode::QueuedForUpdate; mLoaderQueueEntry = entry; mUpdaterFactory = updateJobFactory; } void QgsChunkNode::cancelQueuedForUpdate() { - Q_ASSERT( mState == QueuedForUpdate ); + Q_ASSERT( mState == QgsChunkNode::QueuedForUpdate ); Q_ASSERT( mEntity ); Q_ASSERT( mLoaderQueueEntry ); Q_ASSERT( mUpdaterFactory ); Q_ASSERT( !mUpdater ); - mState = Loaded; + mState = QgsChunkNode::Loaded; mUpdaterFactory = nullptr; // not owned by the node mLoaderQueueEntry = nullptr; @@ -207,7 +207,7 @@ void QgsChunkNode::setUpdating() Q_ASSERT( !mUpdater ); Q_ASSERT( mUpdaterFactory ); - mState = Updating; + mState = QgsChunkNode::Updating; mUpdater = mUpdaterFactory->createJob( this ); mUpdaterFactory = nullptr; // not owned by the node mLoaderQueueEntry = nullptr; @@ -221,7 +221,7 @@ void QgsChunkNode::cancelUpdating() mUpdater = nullptr; // not owned by chunk node - mState = Loaded; + mState = QgsChunkNode::Loaded; } void QgsChunkNode::setUpdated() From f8f9af626ad9e74bd6db8ab2225c5145a0e88aa1 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 24/34] fix: node state / memory access issues --- src/3d/chunks/qgschunkedentity_p.cpp | 46 +++++++++++------- src/3d/chunks/qgschunknode_p.cpp | 72 +++++++++++++++++++++++----- src/3d/chunks/qgschunknode_p.h | 5 ++ src/3d/qgs3dmapscene.cpp | 4 +- 4 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 7f3e81e33648..1f5d4763ad4b 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -159,17 +159,14 @@ void QgsChunkedEntity::setHasReachedGpuMemoryLimit( bool reached ) while ( !mChunkLoaderQueue->isEmpty() ) { - QgsChunkListEntry *entry = mChunkLoaderQueue->takeFirst(); - if ( entry->chunk->state() == QgsChunkNode::QueuedForUpdate ) - entry->chunk->cancelQueuedForUpdate(); - else if ( entry->chunk->state() == QgsChunkNode::QueuedForLoad ) - entry->chunk->cancelQueuedForLoad(); - else if ( entry->chunk->state() == QgsChunkNode::Loading ) - entry->chunk->cancelLoading(); - else if ( entry->chunk->state() == QgsChunkNode::Updating ) - entry->chunk->cancelUpdating(); + mChunkLoaderQueue->takeFirst(); } + // We do not prune mReplacementQueue as the entries are created in the QgsChunkNode and they will lose prev/next references + // If we delete the QgsChunkNode::mReplacementQueueEntry in the QgsChunkNode::freeze function, we will need to recreate it + // later when the layer is un-frozen. This mechanism will be too ricky. + mRootNode->freezeAllChildren(); + emit pendingJobsCountChanged(); } } @@ -328,7 +325,8 @@ int QgsChunkedEntity::unloadNodes() mReplacementQueue->takeEntry( entry ); currentlyUsedGpuMemory -= Qgs3DUtils::calculateEntityGpuMemorySize( entry->chunk->entity() ); mActiveNodes.removeOne( entry->chunk ); - entry->chunk->unloadChunk(); // also deletes the entity! + if ( entry->chunk->state() == QgsChunkNode::Loaded ) // will unload a real loaded node. It may have been frozen during the interval + entry->chunk->unloadChunk(); // also deletes the entity! ++unloaded; entry = entryPrev; } @@ -438,11 +436,11 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) QgsChunkListEntry *e = mChunkLoaderQueue->first(); while ( e ) { - Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate ); - if ( Qgs3DUtils::isCullable( e->chunk->bbox(), sceneContext.viewProjectionMatrix ) ) - { - toRemoveFromLoaderQueue.append( e->chunk ); - } + if ( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate ) + if ( Qgs3DUtils::isCullable( e->chunk->bbox(), sceneContext.viewProjectionMatrix ) ) + { + toRemoveFromLoaderQueue.append( e->chunk ); + } e = e->next; } @@ -757,7 +755,7 @@ void QgsChunkedEntity::onActiveJobFinished() ln = "unknown_deep"; QgsDebugMsgLevel( _logHeader( ln ) - + QStringLiteral( "Checking entity %1. new node_size: %2, old node_size: %3, cur entity_size: %4, " ) + + QStringLiteral( "Checking entity %1. new node_size: %2, old node_size: %3, cur entity_size: %4" ) .arg( node->tileId().text() ) .arg( rootEntityGpuMemory ) .arg( mUsedGpuMemory ) @@ -770,7 +768,15 @@ void QgsChunkedEntity::onActiveJobFinished() { // we need to cancel this pre-loaded entity and set mHasReachedGpuMemoryLimit to true setHasReachedGpuMemoryLimit( true ); - node->cancelLoading(); + if ( node->state() == QgsChunkNode::Loading ) // can be frozen from elsewhere + node->cancelLoading(); +#ifdef QGISDEBUG + else + QgsDebugMsgLevel( _logHeader( ln ) + + QStringLiteral( "Entity %1 is no more in loading state (now %2)" ) + .arg( node->tileId().text() ) + .arg( node->state() ), QGS_LOG_LVL_DEBUG ); +#endif entity->deleteLater(); // delete the failed sub entity now // compute what we can gain: @@ -870,6 +876,12 @@ void QgsChunkedEntity::startJobs() QgsChunkQueueJob *QgsChunkedEntity::startJob( QgsChunkNode *node ) { + if ( mHasReachedGpuMemoryLimit ) + { + node->freeze(); + return nullptr; + } + if ( node->state() == QgsChunkNode::QueuedForLoad ) { QgsEventTracing::addEvent( QgsEventTracing::AsyncBegin, QStringLiteral( "3D" ), QStringLiteral( "Load" ), node->tileId().text() ); diff --git a/src/3d/chunks/qgschunknode_p.cpp b/src/3d/chunks/qgschunknode_p.cpp index 22fbb0d1bdf9..efae166535ea 100644 --- a/src/3d/chunks/qgschunknode_p.cpp +++ b/src/3d/chunks/qgschunknode_p.cpp @@ -19,6 +19,9 @@ #include "qgschunklist_p.h" #include "qgschunkloader_p.h" #include +#include "qgslogger.h" + +#define CHECK_NULL(field) if(field!=nullptr) { QgsLogger::debug(QStringLiteral( "Node %1 should have null field: '" #field "'.").arg( mNodeId.text() ), QGS_LOG_LVL_WARNING, __FILE__, __FUNCTION__, __LINE__); } field=nullptr ///@cond PRIVATE @@ -39,15 +42,24 @@ QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, f QgsChunkNode::~QgsChunkNode() { - Q_ASSERT( mState == QgsChunkNode::Skeleton ); - Q_ASSERT( !mLoaderQueueEntry ); - Q_ASSERT( !mReplacementQueueEntry ); - Q_ASSERT( !mLoader ); // should be deleted when removed from loader queue - Q_ASSERT( !mEntity ); // should be deleted when removed from replacement queue - Q_ASSERT( !mUpdater ); - Q_ASSERT( !mUpdaterFactory ); - + if ( mState != QgsChunkNode::Skeleton ) + { + QgsDebugMsgLevel( QStringLiteral( "Node %1 should be in state Skeleton but is state: '%2'" ) + .arg( mNodeId.text() ) + .arg( mState ), QGS_LOG_LVL_WARNING ); + } qDeleteAll( mChildren ); + + if ( mEntity ) + mEntity->deleteLater(); + CHECK_NULL( mEntity ); // should be deleted when removed from replacement queue + + CHECK_NULL( mUpdater ); + CHECK_NULL( mUpdaterFactory ); + + if ( mReplacementQueueEntry ) + delete mReplacementQueueEntry; // own by us + CHECK_NULL( mReplacementQueueEntry ); } bool QgsChunkNode::allChildChunksResident( QTime currentTime ) const @@ -158,15 +170,27 @@ void QgsChunkNode::setLoaded( Qt3DCore::QEntity *newEntity ) void QgsChunkNode::unloadChunk() { - Q_ASSERT( mState == QgsChunkNode::Loaded ); - Q_ASSERT( mEntity ); - Q_ASSERT( mReplacementQueueEntry ); + if ( mState != QgsChunkNode::Loaded ) + { + QgsDebugMsgLevel( QStringLiteral( "Node %1 should be in state Loaded but is state: '%2'" ) + .arg( mNodeId.text() ) + .arg( mState ), QGS_LOG_LVL_WARNING ); + } + else + { + Q_ASSERT( mState == QgsChunkNode::Loaded ); + Q_ASSERT( mEntity ); + Q_ASSERT( mReplacementQueueEntry ); + } - delete mEntity; + if ( mEntity ) + mEntity->deleteLater(); mEntity = nullptr; - delete mReplacementQueueEntry; + if ( mReplacementQueueEntry ) + delete mReplacementQueueEntry; // own by us mReplacementQueueEntry = nullptr; + mState = QgsChunkNode::Skeleton; } @@ -236,6 +260,28 @@ void QgsChunkNode::setUpdated() mState = QgsChunkNode::Loaded; } +void QgsChunkNode::freeze() +{ + if ( state() == QgsChunkNode::QueuedForUpdate ) + cancelQueuedForUpdate(); + else if ( state() == QgsChunkNode::QueuedForLoad ) + cancelQueuedForLoad(); + else if ( state() == QgsChunkNode::Loading ) + cancelLoading(); + else if ( state() == QgsChunkNode::Updating ) + cancelUpdating(); +} + +void QgsChunkNode::freezeAllChildren() +{ + freeze(); + for ( int i = 0; i < childCount(); ++i ) + { + QgsChunkNode *child = mChildren[i]; + child->freezeAllChildren(); + } +} + void QgsChunkNode::setExactBbox( const QgsAABB &box ) { mBbox = box; diff --git a/src/3d/chunks/qgschunknode_p.h b/src/3d/chunks/qgschunknode_p.h index 11cc0ede33b8..c5f6078aedde 100644 --- a/src/3d/chunks/qgschunknode_p.h +++ b/src/3d/chunks/qgschunknode_p.h @@ -256,6 +256,11 @@ class QgsChunkNode //! Returns whether the node has any data to be displayed. If not, it will be kept as a skeleton node and will not get loaded anymore bool hasData() const { return mHasData; } + //! Freezes node: cancel any loading/uploading and cancel any queued states + void freeze(); + //! Freezes node and child nodes + void freezeAllChildren(); + private: QgsAABB mBbox; //!< Bounding box in world coordinates float mError; //!< Error of the node in world coordinates (negative error means that chunk at this level has no data, but there may be children that do) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index b591a63dfa27..3b3c9cd8961b 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -423,7 +423,7 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) if ( entity && !entity->hasReachedGpuMemoryLimit() ) if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) { - entity->handleSceneUpdate( buildSceneContext(), mMaxAvailableGpuMemory - mUsedGpuMemory ); + entity->handleSceneUpdate( buildSceneContext(), mMaxAvailableGpuMemory * 0.95 - mUsedGpuMemory ); if ( entity->hasReachedGpuMemoryLimit() ) { QgsDebugMsgLevel( _logHeader( entity->layerName() ) @@ -685,7 +685,7 @@ void Qgs3DMapScene::onLayersChanged() if ( sceneEntity && sceneEntity->hasReachedGpuMemoryLimit() ) { QgsDebugMsgLevel( _logHeader( sceneEntity->layerName() ) - + QStringLiteral( "Frreeedd! Will try to load new entities!" ), QGS_LOG_LVL_DEBUG ); + + QStringLiteral( "Un-frozen! Will try to load new entities!" ), QGS_LOG_LVL_DEBUG ); sceneEntity->setHasReachedGpuMemoryLimit( false ); } } From ae639a685a61fa33318854b2342e2d010c76ecb6 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 25/34] fix: improve unloading when frozen --- src/3d/chunks/qgschunkedentity_p.cpp | 25 +++++++++++++++++++++---- src/3d/qgs3dmapscene.cpp | 18 ++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 1f5d4763ad4b..bca373d7df9c 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -176,7 +176,7 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext, doub { mLastKnownAvailableGpuMemory = availableGpuMemory; - if ( !mIsValid || mHasReachedGpuMemoryLimit ) + if ( !mIsValid ) return; // Let's start the update by removing from loader queue chunks that @@ -186,6 +186,16 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext, doub // of time when camera was moving. pruneLoaderQueue( sceneContext ); + if ( mHasReachedGpuMemoryLimit ) + { + if ( unloadNodes() && mUsedGpuMemory < mLastKnownAvailableGpuMemory ) + { + setHasReachedGpuMemoryLimit( false ); + } + mNeedsUpdate = false; // just updated + return; + } + QElapsedTimer t; t.start(); @@ -345,6 +355,15 @@ int QgsChunkedEntity::unloadNodes() .arg( currentlyUsedGpuMemory ) .arg( usableGpuMemory ), QGS_LOG_LVL_DEBUG ); } + else + { + QgsDebugMsgLevel( _logHeader( mLayerName ) + + QStringLiteral( "Unloaded %1 nodes to free GPU memory (was_used: %2 MB, now_using: %3 MB, limit: %4 MB)" ) + .arg( unloaded ) + .arg( mUsedGpuMemory ) + .arg( currentlyUsedGpuMemory ) + .arg( usableGpuMemory ), QGS_LOG_LVL_DEBUG ); + } mLastKnownAvailableGpuMemory += ( currentlyUsedGpuMemory - mUsedGpuMemory ); mUsedGpuMemory = currentlyUsedGpuMemory; @@ -707,7 +726,7 @@ void QgsChunkedEntity::onActiveJobFinished() QgsChunkQueueJob *job = qobject_cast( sender() ); Q_ASSERT( job ); - if ( mHasReachedGpuMemoryLimit ) + if ( mHasReachedGpuMemoryLimit || !mActiveJobs.contains( job ) ) { // cleanup the job that has just finished mActiveJobs.removeOne( job ); @@ -717,8 +736,6 @@ void QgsChunkedEntity::onActiveJobFinished() return; } - Q_ASSERT( mActiveJobs.contains( job ) ); - // this <==> root entity --> node <==> sub node/chunk loaded by this QgsChunkNode *node = job->chunk(); diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 3b3c9cd8961b..c1be4bf47c5f 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -420,19 +420,29 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) { Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); - if ( entity && !entity->hasReachedGpuMemoryLimit() ) - if ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) + if ( entity && + ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) ) + { + bool previousReachedGpu = entity->hasReachedGpuMemoryLimit(); + entity->handleSceneUpdate( buildSceneContext(), mMaxAvailableGpuMemory * 0.95 - mUsedGpuMemory ); + if ( previousReachedGpu != entity->hasReachedGpuMemoryLimit() ) { - entity->handleSceneUpdate( buildSceneContext(), mMaxAvailableGpuMemory * 0.95 - mUsedGpuMemory ); if ( entity->hasReachedGpuMemoryLimit() ) { QgsDebugMsgLevel( _logHeader( entity->layerName() ) - + QString( "has been disabled!" ), QGS_LOG_LVL_WARNING ); + + QString( "has been frozen!" ), QGS_LOG_LVL_WARNING ); emit gpuMemoryLimitReached(); setSceneState( Canceled ); break; } + else + { + QgsDebugMsgLevel( _logHeader( entity->layerName() ) + + QString( "has been un-frozen!" ), QGS_LOG_LVL_INFO ); + + } } + } } updateSceneState(); From ba6d79e678a5ac387b0a8940d3aae62fdf63400f Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 26/34] fix: gpu memory settings: add a check box for automatic limit --- python/3d/auto_generated/qgs3dmapscene.sip.in | 4 ++ src/3d/qgs3dmapscene.cpp | 61 ++++++++++--------- src/3d/qgs3dmapscene.h | 2 + src/app/3d/qgs3dmapcanvas.cpp | 3 + src/app/3d/qgs3doptions.cpp | 2 + src/ui/3d/qgs3doptionsbase.ui | 18 ++++-- 6 files changed, 56 insertions(+), 34 deletions(-) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index ee10c9082823..06905db77049 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -126,6 +126,10 @@ Returns the max available gpu memory for this 3D scene QList frozenLayers() const; %Docstring Returns frozen layer name list +%End + void readAvailableGpuMemory(); +%Docstring +Reads available gpu memory from settings or gpu card %End static QMap< QString, Qgs3DMapScene * > openScenes(); diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index c1be4bf47c5f..9532cf6d15e6 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -122,36 +122,6 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine addCameraRotationCenterEntity( mCameraController ); updateLights(); - // read defaut gpu memory in 3d map settings - { - const QgsSettings settings; - QString fromSetting; - double defaultGpuMemory = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); - if ( settings.value( QStringLiteral( "map3d/gpuMemoryLimitAuto" ), true, QgsSettings::App ).toBool() ) - { - int memoryAvailableKB = Qgs3DUtils::estimateGpuMemoryAvailable(); - if ( memoryAvailableKB == -1 ) - { - mMaxAvailableGpuMemory = defaultGpuMemory; - fromSetting = "user setting, auto failed"; - } - else - { - mMaxAvailableGpuMemory = 0.8 * static_cast( memoryAvailableKB ) / 1024.0; - fromSetting = "auto computed"; - } - } - else - { - mMaxAvailableGpuMemory = defaultGpuMemory; - fromSetting = "user setting"; - } - QgsDebugMsgLevel( QStringLiteral( "Gpu limit for '%1' is set to %2MB (%3)" ) - .arg( objectName() ) - .arg( mMaxAvailableGpuMemory ) - .arg( fromSetting ), QGS_LOG_LVL_INFO ); - } - // create terrain entity and other entities from other layers createTerrainDeferred(); @@ -240,6 +210,37 @@ Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine on3DAxisSettingsChanged(); } +void Qgs3DMapScene::readAvailableGpuMemory() +{ + const QgsSettings settings; + QString fromSetting; + double defaultGpuMemory = settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble(); + if ( settings.value( QStringLiteral( "map3d/readMemoryFromGpuCard" ), true, QgsSettings::App ).toBool() ) + { + int memoryAvailableKB = Qgs3DUtils::estimateGpuMemoryAvailable(); + if ( memoryAvailableKB == -1 ) + { + mMaxAvailableGpuMemory = defaultGpuMemory; + fromSetting = "user setting, auto failed"; + } + else + { + mMaxAvailableGpuMemory = 0.8 * static_cast( memoryAvailableKB ) / 1024.0; + fromSetting = "auto computed"; + } + } + else + { + mMaxAvailableGpuMemory = defaultGpuMemory; + fromSetting = "user setting"; + } + QgsDebugMsgLevel( QStringLiteral( "Gpu limit for '%1' is set to %2MB (%3)" ) + .arg( objectName() ) + .arg( mMaxAvailableGpuMemory ) + .arg( fromSetting ), QGS_LOG_LVL_INFO ); +} + + QgsTerrainEntity *Qgs3DMapScene::terrainEntity() const { if ( mTerrainLayer ) diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 7fc4dea43a4b..4dd547290f0c 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -193,6 +193,8 @@ class _3D_EXPORT Qgs3DMapScene : public QObject double maxAvailableGpuMemory() const { return mMaxAvailableGpuMemory;} //! Returns frozen layer name list QList frozenLayers() const; + //! Reads available gpu memory from settings or gpu card + void readAvailableGpuMemory(); /** * Returns a map of 3D map scenes (by name) open in the QGIS application. diff --git a/src/app/3d/qgs3dmapcanvas.cpp b/src/app/3d/qgs3dmapcanvas.cpp index 04ea7c9775db..1830b1324882 100644 --- a/src/app/3d/qgs3dmapcanvas.cpp +++ b/src/app/3d/qgs3dmapcanvas.cpp @@ -126,6 +126,9 @@ void Qgs3DMapCanvas::setMap( Qgs3DMapSettings *map ) mScene->setObjectName( parentWidget->canvasName() ); } + // read defaut gpu memory in 3d map settings + mScene->readAvailableGpuMemory(); + connect( mScene, &Qgs3DMapScene::fpsCountChanged, this, &Qgs3DMapCanvas::fpsCountChanged ); connect( mScene, &Qgs3DMapScene::fpsCounterEnabledChanged, this, &Qgs3DMapCanvas::fpsCounterEnabledChanged ); connect( mScene, &Qgs3DMapScene::viewed2DExtentFrom3DChanged, this, &Qgs3DMapCanvas::viewed2DExtentFrom3DChanged ); diff --git a/src/app/3d/qgs3doptions.cpp b/src/app/3d/qgs3doptions.cpp index e2e4097bdbaa..23a249812431 100644 --- a/src/app/3d/qgs3doptions.cpp +++ b/src/app/3d/qgs3doptions.cpp @@ -58,6 +58,7 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent ) mGpuMemoryLimit->setClearValue( 500 ); mGpuMemoryLimit->setValue( settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble() ); + mReadMemoryFromGpuCard->setChecked( settings.value( QStringLiteral( "map3d/readMemoryFromGpuCard" ), true, QgsSettings::App ).toBool() ); } QString Qgs3DOptionsWidget::helpKey() const @@ -76,6 +77,7 @@ void Qgs3DOptionsWidget::apply() settings.setValue( QStringLiteral( "map3d/defaultFieldOfView" ), spinCameraFieldOfView->value(), QgsSettings::App ); settings.setValue( QStringLiteral( "map3d/gpuMemoryLimit" ), mGpuMemoryLimit->value(), QgsSettings::App ); + settings.setValue( QStringLiteral( "map3d/readMemoryFromGpuCard" ), mReadMemoryFromGpuCard->isChecked(), QgsSettings::App ); } diff --git a/src/ui/3d/qgs3doptionsbase.ui b/src/ui/3d/qgs3doptionsbase.ui index ed5964d29772..54e2a470c646 100644 --- a/src/ui/3d/qgs3doptionsbase.ui +++ b/src/ui/3d/qgs3doptionsbase.ui @@ -158,14 +158,14 @@ Graphics Memory - - + + - Allowed memory per map layer + Allowed memory per 3D scene (fallback) - + MB @@ -184,6 +184,16 @@ + + + + Try to read available memory from GPU card? + + + true + + + From 2c00cf8241e4ebf19da243c3e55f36d6adebfe98 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Fri, 8 Dec 2023 16:09:17 +0100 Subject: [PATCH 27/34] minor fixes --- src/3d/chunks/qgschunkedentity_p.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index bca373d7df9c..51b9d4785921 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -144,7 +144,7 @@ void QgsChunkedEntity::setHasReachedGpuMemoryLimit( bool reached ) return; QgsDebugMsgLevel( _logHeader( mLayerName ) - + QStringLiteral( "has reached gpu memory limit. Cleaning up: active %1 | culled %2 | loading %3 loaded %4" ) + + QStringLiteral( "is now frozen. Cleaning up: active %1 | culled %2 | loading %3 loaded %4" ) .arg( mActiveNodes.count() ) .arg( mFrustumCulled ) .arg( mChunkLoaderQueue->count() ) @@ -294,12 +294,6 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext, doub int QgsChunkedEntity::unloadNodes() { double currentlyUsedGpuMemory = Qgs3DUtils::calculateEntityGpuMemorySize( this ); - if ( mUsedGpuMemory < currentlyUsedGpuMemory ) - QgsDebugMsgLevel( _logHeader( mLayerName ) - + QStringLiteral( "GPU memory usage increased! (now_using: %1MB, was: %2MB)" ) - .arg( currentlyUsedGpuMemory ) - .arg( mUsedGpuMemory ), QGS_LOG_LVL_DEBUG ); - double usableGpuMemory = mLastKnownAvailableGpuMemory + mUsedGpuMemory; if ( currentlyUsedGpuMemory <= usableGpuMemory ) { @@ -763,7 +757,13 @@ void QgsChunkedEntity::onActiveJobFinished() while ( ent->layerName().isEmpty() || ent->layerName() == "unknown" ) { if ( ent->parent() && dynamic_cast( ent->parent() ) ) + { ent = dynamic_cast( ent->parent() ); + QgsDebugMsgLevel( _logHeader( "ln???" ) + + QStringLiteral( "searching in parent of %1" ) + .arg( node->tileId().text() ) + , QGS_LOG_LVL_DEBUG ); + } else break; } From 6f2b9a1bcad919eb45bd6ef6a0ba42296d11b19b Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Thu, 14 Dec 2023 09:44:56 +0100 Subject: [PATCH 28/34] fix(qgs3dmapscene): apply clazy fixes about "allocating an unneeded temporary container" --- src/3d/qgs3dmapscene.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index 9532cf6d15e6..c3fc6c68c013 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -316,9 +316,9 @@ QVector Qgs3DMapScene::viewFrustum2DExtent() const int Qgs3DMapScene::totalPendingJobsCount() const { int count = 0; - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + Qgs3DMapSceneEntity *entity = dynamic_cast( it.value() ); if ( entity ) { count += entity->pendingJobsCount(); @@ -418,9 +418,9 @@ void Qgs3DMapScene::updateScene( bool forceUpdate ) if ( forceUpdate ) QgsEventTracing::addEvent( QgsEventTracing::Instant, QStringLiteral( "3D" ), QStringLiteral( "Update Scene" ) ); - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + Qgs3DMapSceneEntity *entity = dynamic_cast( it.value() ); if ( entity && ( forceUpdate || ( entity->isEnabled() && entity->needsUpdate() ) ) ) { @@ -470,9 +470,9 @@ bool Qgs3DMapScene::updateCameraNearFarPlanes() // Iterate all scene entities to make sure that they will not get // clipped by the near or far plane - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( qtEntity ); + Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( it.value() ); if ( sceneEntity ) { const QgsRange depthRange = sceneEntity->getNearFarPlaneRange( viewMatrix ); @@ -690,9 +690,9 @@ void Qgs3DMapScene::onLayersChanged() if ( layersBefore.size() != 0 ) // layers have been disabled, we try to reactivate frozen layers { - for ( Qt3DCore::QEntity *entity : mLayerEntities.values() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( entity ); + Qgs3DMapSceneEntity *sceneEntity = dynamic_cast( it.value() ); if ( sceneEntity && sceneEntity->hasReachedGpuMemoryLimit() ) { QgsDebugMsgLevel( _logHeader( sceneEntity->layerName() ) @@ -1044,9 +1044,9 @@ void Qgs3DMapScene::updateSceneState() } else { - for ( Qt3DCore::QEntity *qtEntity : mLayerEntities.values() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); + Qgs3DMapSceneEntity *entity = dynamic_cast( it.value() ); if ( entity && entity->isEnabled() && !entity->hasReachedGpuMemoryLimit() && entity->pendingJobsCount() > 0 ) { setSceneState( Updating ); @@ -1063,8 +1063,9 @@ void Qgs3DMapScene::updateSceneState() .arg( mUsedGpuMemory ) .arg( mMaxAvailableGpuMemory ), QGS_LOG_LVL_DEBUG ); - for ( QgsMapLayer *layer : mLayerEntities.keys() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { + QgsMapLayer *layer = it.key(); Qt3DCore::QEntity *qtEntity = mLayerEntities[layer]; Qgs3DMapSceneEntity *entity = dynamic_cast( qtEntity ); if ( entity && entity->isEnabled() ) @@ -1383,11 +1384,12 @@ void Qgs3DMapScene::on3DAxisSettingsChanged() QList Qgs3DMapScene::frozenLayers() const { QList out; - for ( QgsMapLayer *l : mLayerEntities.keys() ) + for ( auto it = mLayerEntities.begin(); it != mLayerEntities.end(); ++it ) { - Qgs3DMapSceneEntity *entity = dynamic_cast( mLayerEntities[l] ); + QgsMapLayer *layer = it.key(); + Qgs3DMapSceneEntity *entity = dynamic_cast( mLayerEntities[layer] ); if ( entity && entity->hasReachedGpuMemoryLimit() ) - out << l->name(); + out << layer->name(); } return out; } From 3f32f7952a6894f6e072455dbac01d94c767f72b Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Thu, 14 Dec 2023 10:04:02 +0100 Subject: [PATCH 29/34] fix(qgs3dmapscene): apply clazy fix about "lambda capture layer is not used" --- src/3d/qgs3dmapscene.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index c3fc6c68c013..defceb8910f0 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -802,6 +802,7 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer ) connect( sceneNewEntity, &Qgs3DMapSceneEntity::newEntityCreated, this, [this, layer]( Qt3DCore::QEntity * entity, double entityGpuMem ) { + Q_UNUSED( layer ); finalizeNewEntity( entity ); if ( std::isnan( entityGpuMem ) ) From 0535dcb8db1b384bd519fb8542fef82e706be219 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Thu, 14 Dec 2023 11:15:02 +0100 Subject: [PATCH 30/34] fix(qgschunkedentity): remove temporary #define --- src/3d/chunks/qgschunkedentity_p.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index 51b9d4785921..bfe34cac923e 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -28,8 +28,6 @@ #include -/// BDE TO REMOVE: -#define QGISDEBUG 1 ///@cond PRIVATE static QString _logHeader( const QString &layerName ) From 98bc8ced4f47f062572ad452fb18140581e64406 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Thu, 14 Dec 2023 16:21:09 +0100 Subject: [PATCH 31/34] fix(logger): clazy/clang errors --- src/3d/chunks/qgschunkedentity_p.cpp | 2 ++ src/3d/qgs3dmapscene.cpp | 2 ++ src/core/qgsmessagelog.cpp | 6 ++---- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/3d/chunks/qgschunkedentity_p.cpp b/src/3d/chunks/qgschunkedentity_p.cpp index bfe34cac923e..96e9dec156d3 100644 --- a/src/3d/chunks/qgschunkedentity_p.cpp +++ b/src/3d/chunks/qgschunkedentity_p.cpp @@ -30,12 +30,14 @@ ///@cond PRIVATE +#ifdef QGISDEBUG static QString _logHeader( const QString &layerName ) { if ( layerName.isEmpty() ) return QStringLiteral( "{layer:} " ); return QStringLiteral( "{layer:%1} " ).arg( layerName ); } +#endif static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneContext &sceneContext ) { diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index defceb8910f0..29e7c896f894 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -83,12 +83,14 @@ std::function< QMap< QString, Qgs3DMapScene * >() > Qgs3DMapScene::sOpenScenesFunction = [] { return QMap< QString, Qgs3DMapScene * >(); }; +#ifdef QGISDEBUG static QString _logHeader( const QString &layerName ) { if ( layerName.isEmpty() ) return QStringLiteral( "{layer:} " ); return QStringLiteral( "{layer:%1} " ).arg( layerName ); } +#endif Qgs3DMapScene::Qgs3DMapScene( Qgs3DMapSettings &map, QgsAbstract3DEngine *engine ) : mMap( map ) diff --git a/src/core/qgsmessagelog.cpp b/src/core/qgsmessagelog.cpp index 5e2a4d9b9dcb..37d597357fd9 100644 --- a/src/core/qgsmessagelog.cpp +++ b/src/core/qgsmessagelog.cpp @@ -49,12 +49,10 @@ void QgsMessageLog::logMessage( const QString &message, const QString &tag, Qgis } // TODO must use the QLoggingCategory - Q_UNUSED( loggerLevel ); - QgsDebugMsgLevel( QStringLiteral( "%1[%2] %3" ) + QgsLogger::debug( QStringLiteral( "%1[%2] %3" ) .arg( tag ) .arg( static_cast< int >( level ) ) - .arg( message ) - , loggerLevel ); + .arg( message ), loggerLevel, "logMessage_caller" ); QgsApplication::messageLog()->emitMessage( message, tag, level, notifyUser ); } From 6cc61fe50820a47e748d4bdc35f2e29604a9aa87 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Wed, 20 Dec 2023 13:59:35 +0100 Subject: [PATCH 32/34] fix(qgs3dmapscene): set default value for gpu limit to 500MB --- src/3d/qgs3dmapscene.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index 4dd547290f0c..19becf58a4ff 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -310,7 +310,7 @@ class _3D_EXPORT Qgs3DMapScene : public QObject Qgs3DAxis *m3DAxis = nullptr; //! max gpu memory available for this 3D scene (at scene creation) - double mMaxAvailableGpuMemory = 0.0; + double mMaxAvailableGpuMemory = 500.0; //! current gpu memory used by this 3D scene double mUsedGpuMemory = 0.0; }; From 461b09006495ce5b74803a4e50114416890f92b8 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Wed, 20 Dec 2023 14:00:29 +0100 Subject: [PATCH 33/34] to squash: qgsterrainentity_p disable useless logs --- src/3d/terrain/qgsterrainentity_p.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/3d/terrain/qgsterrainentity_p.cpp b/src/3d/terrain/qgsterrainentity_p.cpp index d56725c24331..d17ae9e02cc8 100644 --- a/src/3d/terrain/qgsterrainentity_p.cpp +++ b/src/3d/terrain/qgsterrainentity_p.cpp @@ -267,7 +267,7 @@ QgsTerrainLayer3DRenderer *QgsTerrainLayer3DRenderer::clone() const Qt3DCore::QEntity *QgsTerrainLayer3DRenderer::createEntity( const Qgs3DMapSettings &map3DSettings ) const { - qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity"; +// qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity"; QgsTerrainEntity *terrainEntity = nullptr; if ( map3DSettings.terrainRenderingEnabled() && map3DSettings.terrainGenerator() ) { @@ -276,14 +276,14 @@ Qt3DCore::QEntity *QgsTerrainLayer3DRenderer::createEntity( const Qgs3DMapSettin QgsAABB rootBbox = map3DSettings.terrainGenerator()->rootChunkBbox( map3DSettings ); float rootError = map3DSettings.terrainGenerator()->rootChunkError( map3DSettings ); const QgsAABB clippingBbox = Qgs3DUtils::mapToWorldExtent( map3DSettings.extent(), rootBbox.zMin, rootBbox.zMax, map3DSettings.origin() ); - qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will setup quadtree"; +// qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will setup quadtree"; map3DSettings.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel, clippingBbox ); - qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will create terrain"; +// qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity will create terrain"; terrainEntity = new QgsTerrainEntity( map3DSettings ); terrainEntity->setShowBoundingBoxes( map3DSettings.showTerrainBoundingBoxes() ); } - qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity done!"; +// qDebug() << "=============== QgsTerrainLayer3DRenderer::createEntity done!"; return terrainEntity; } /// @endcond From 9e6b86dbbfe47244f6bb03f56c7d431bb9ea5621 Mon Sep 17 00:00:00 2001 From: bdm-oslandia Date: Wed, 20 Dec 2023 14:01:28 +0100 Subject: [PATCH 34/34] testqgs3drendering only the segfault test --- tests/src/3d/testqgs3drendering.cpp | 2659 ++++++++++++++------------- 1 file changed, 1332 insertions(+), 1327 deletions(-) diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 01f742ee7118..734192c68209 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -66,36 +66,38 @@ class TestQgs3DRendering : public QgsTest private slots: void initTestCase();// will be called before the first testfunction is executed. void cleanupTestCase();// will be called after the last testfunction was executed. - void testFlatTerrain(); - void testDemTerrain(); - void testTerrainShading(); - void testEpsg4978LineRendering(); - void testExtrudedPolygons(); - void testExtrudedPolygonsDataDefined(); - void testExtrudedPolygonsGoochShading(); - void testPolygonsEdges(); - void testLineRendering(); - void testLineRenderingCurved(); - void testBufferedLineRendering(); - void testBufferedLineRenderingWidth(); - void testMapTheme(); - void testRuleBasedRenderer(); - void testAnimationExport(); - void testBillboardRendering(); - void testInstancedRendering(); - void testFilteredFlatTerrain(); - void testFilteredDemTerrain(); +// void testFlatTerrain(); +// void testDemTerrain(); +// void testTerrainShading(); +// void testEpsg4978LineRendering(); +// void testExtrudedPolygons(); + + // void testExtrudedPolygonsDataDefined(); + +// void testExtrudedPolygonsGoochShading(); +// void testPolygonsEdges(); +// void testLineRendering(); +// void testLineRenderingCurved(); +// void testBufferedLineRendering(); +// void testBufferedLineRenderingWidth(); +// void testMapTheme(); +// void testRuleBasedRenderer(); +// void testAnimationExport(); +// void testBillboardRendering(); +// void testInstancedRendering(); +// void testFilteredFlatTerrain(); +// void testFilteredDemTerrain(); void testFilteredExtrudedPolygons(); - void testDepthBuffer(); - void testAmbientOcclusion(); - void testDebugMap(); - void test3DSceneExporter(); +// void testDepthBuffer(); +// void testAmbientOcclusion(); +// void testDebugMap(); +// void test3DSceneExporter(); private: - QImage convertDepthImageToGray16Image( const QImage &depthImage ); +// QImage convertDepthImageToGray16Image( const QImage &depthImage ); - void do3DSceneExport( int zoomLevelsCount, int expectedObjectCount, int maxFaceCount, Qgs3DMapScene *scene, QgsPolygon3DSymbol *symbol3d, - QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine ); +// void do3DSceneExport( int zoomLevelsCount, int expectedObjectCount, int maxFaceCount, Qgs3DMapScene *scene, QgsPolygon3DSymbol *symbol3d, +// QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine ); std::unique_ptr mProject; QgsRasterLayer *mLayerDtm = nullptr; @@ -127,38 +129,38 @@ class QgsCameraController4Test : public QgsCameraController QgsCameraPose *cameraPose() { return &mCameraPose; } }; -QImage TestQgs3DRendering::convertDepthImageToGray16Image( const QImage &depthImage ) -{ - QImage grayImage( depthImage.width(), depthImage.height(), QImage::Format_Grayscale16 ); - - // compute image min/max depth values - double minV = 9999999.0; - double maxV = -9999999.0; - for ( int x = 0; x < grayImage.width(); x++ ) - { - for ( int y = 0; y < grayImage.height(); y++ ) - { - double d = Qgs3DUtils::decodeDepth( depthImage.pixel( x, y ) ); - if ( d > maxV ) maxV = d; - else if ( d < minV ) minV = d; - } - } - - // transform depth value to gray value - double factor = 65635.0 / ( maxV - minV ); - for ( int x = 0; x < grayImage.width(); x++ ) - { - for ( int y = 0; y < grayImage.height(); y++ ) - { - double d = Qgs3DUtils::decodeDepth( depthImage.pixel( x, y ) ); - unsigned short v = ( unsigned short )( factor * ( d - minV ) ); - QRgba64 col = QRgba64::fromRgba64( v, v, v, ( quint16 )65635 ); - grayImage.setPixelColor( x, y, QColor( col ) ); - } - } - - return grayImage; -} +//QImage TestQgs3DRendering::convertDepthImageToGray16Image( const QImage &depthImage ) +//{ +// QImage grayImage( depthImage.width(), depthImage.height(), QImage::Format_Grayscale16 ); + +// // compute image min/max depth values +// double minV = 9999999.0; +// double maxV = -9999999.0; +// for ( int x = 0; x < grayImage.width(); x++ ) +// { +// for ( int y = 0; y < grayImage.height(); y++ ) +// { +// double d = Qgs3DUtils::decodeDepth( depthImage.pixel( x, y ) ); +// if ( d > maxV ) maxV = d; +// else if ( d < minV ) minV = d; +// } +// } + +// // transform depth value to gray value +// double factor = 65635.0 / ( maxV - minV ); +// for ( int x = 0; x < grayImage.width(); x++ ) +// { +// for ( int y = 0; y < grayImage.height(); y++ ) +// { +// double d = Qgs3DUtils::decodeDepth( depthImage.pixel( x, y ) ); +// unsigned short v = ( unsigned short )( factor * ( d - minV ) ); +// QRgba64 col = QRgba64::fromRgba64( v, v, v, ( quint16 )65635 ); +// grayImage.setPixelColor( x, y, QColor( col ) ); +// } +// } + +// return grayImage; +//} // runs before all tests void TestQgs3DRendering::initTestCase() @@ -238,1355 +240,1358 @@ void TestQgs3DRendering::cleanupTestCase() QgsApplication::exitQgis(); } -void TestQgs3DRendering::testFlatTerrain() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); +//void TestQgs3DRendering::testFlatTerrain() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerRgb ); +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerRgb ); - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "flat_terrain_1", "flat_terrain_1", img, QString(), 40, QSize( 0, 0 ), 2 ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "flat_terrain_1", "flat_terrain_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - // tilted view (pitch = 60 degrees) - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 0 ); - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "flat_terrain_2", "flat_terrain_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +// // tilted view (pitch = 60 degrees) +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 0 ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "flat_terrain_2", "flat_terrain_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); - // also add horizontal rotation (yaw = 45 degrees) - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 45 ); - QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "flat_terrain_3", "flat_terrain_3", img3, QString(), 40, QSize( 0, 0 ), 2 ); +// // also add horizontal rotation (yaw = 45 degrees) +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 45 ); +// QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "flat_terrain_3", "flat_terrain_3", img3, QString(), 40, QSize( 0, 0 ), 2 ); - // change camera lens field of view - map->setFieldOfView( 85.0f ); - QImage img4 = Qgs3DUtils::captureSceneImage( engine, scene ); +// // change camera lens field of view +// map->setFieldOfView( 85.0f ); +// QImage img4 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "flat_terrain_4", "flat_terrain_4", img4, QString(), 40, QSize( 0, 0 ), 2 ); +//} - QGSVERIFYIMAGECHECK( "flat_terrain_4", "flat_terrain_4", img4, QString(), 40, QSize( 0, 0 ), 2 ); -} +//void TestQgs3DRendering::testDemTerrain() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerRgb ); -void TestQgs3DRendering::testDemTerrain() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; +// demTerrain->setLayer( mLayerDtm ); +// map->setTerrainGenerator( demTerrain ); +// map->setTerrainVerticalScale( 3 ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); + +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "dem_terrain_1", "dem_terrain_1", img3, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testTerrainShading() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// // no terrain layers set! + +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; +// demTerrain->setLayer( mLayerDtm ); +// map->setTerrainGenerator( demTerrain ); +// map->setTerrainVerticalScale( 3 ); + +// QgsPhongMaterialSettings terrainMaterial; +// terrainMaterial.setAmbient( QColor( 0, 0, 0 ) ); +// terrainMaterial.setDiffuse( QColor( 255, 255, 0 ) ); +// terrainMaterial.setSpecular( QColor( 255, 255, 255 ) ); +// map->setTerrainShadingMaterial( terrainMaterial ); +// map->setTerrainShadingEnabled( true ); + +// QgsPointLightSettings defaultLight; +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// defaultLight.setIntensity( 0.5 ); +// map->setLightSources( {defaultLight.clone() } ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "shaded_terrain_no_layers", "shaded_terrain_no_layers", img3, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testExtrudedPolygons() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerBuildings << mLayerRgb ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( {defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + +// QGSVERIFYIMAGECHECK( "polygon3d_extrusion", "polygon3d_extrusion", img, QString(), 40, QSize( 0, 0 ), 2 ); + +// // change opacity +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setAmbient( Qt::lightGray ); +// materialSettings.setOpacity( 0.5f ); +// QgsPolygon3DSymbol *symbol3dOpacity = new QgsPolygon3DSymbol; +// symbol3dOpacity->setMaterialSettings( materialSettings.clone() ); +// symbol3dOpacity->setExtrusionHeight( 10.f ); +// QgsVectorLayer3DRenderer *renderer3dOpacity = new QgsVectorLayer3DRenderer( symbol3dOpacity ); +// mLayerBuildings->setRenderer3D( renderer3dOpacity ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "polygon3d_extrusion_opacity", "polygon3d_extrusion_opacity", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testExtrudedPolygonsDataDefined() +//{ +// QgsPropertyCollection propertyColection; +// QgsProperty diffuseColor; +// QgsProperty ambientColor; +// QgsProperty specularColor; +// diffuseColor.setExpressionString( QStringLiteral( "color_rgb( 120*(\"ogc_fid\"%3),125,0)" ) ); +// ambientColor.setExpressionString( QStringLiteral( "color_rgb( 120,(\"ogc_fid\"%2)*255,0)" ) ); +// specularColor.setExpressionString( QStringLiteral( "'yellow'" ) ); +// propertyColection.setProperty( QgsAbstractMaterialSettings::Diffuse, diffuseColor ); +// propertyColection.setProperty( QgsAbstractMaterialSettings::Ambient, ambientColor ); +// propertyColection.setProperty( QgsAbstractMaterialSettings::Specular, specularColor ); +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setDataDefinedProperties( propertyColection ); +// materialSettings.setAmbient( Qt::red ); +// QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; +// symbol3d->setMaterialSettings( materialSettings.clone() ); +// symbol3d->setExtrusionHeight( 10.f ); +// QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d ); +// mLayerBuildings->setRenderer3D( renderer3d ); + +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerBuildings << mLayerRgb ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( { defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + +// delete scene; +// delete map; +// QGSVERIFYIMAGECHECK( "polygon3d_extrusion_data_defined", "polygon3d_extrusion_data_defined", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testExtrudedPolygonsGoochShading() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( {defaultLight.clone() } ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerBuildings << mLayerRgb ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// QgsGoochMaterialSettings materialSettings; +// materialSettings.setWarm( QColor( 224, 224, 17 ) ); +// materialSettings.setCool( QColor( 21, 187, 235 ) ); +// materialSettings.setAlpha( 0.2f ); +// materialSettings.setBeta( 0.6f ); +// QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; +// symbol3d->setMaterialSettings( materialSettings.clone() ); +// symbol3d->setExtrusionHeight( 10.f ); +// QgsVectorLayer3DRenderer *renderer3dOpacity = new QgsVectorLayer3DRenderer( symbol3d ); +// mLayerBuildings->setRenderer3D( renderer3dOpacity ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "polygon3d_extrusion_gooch_shading", "polygon3d_extrusion_gooch_shading", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testPolygonsEdges() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); + +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setAmbient( Qt::lightGray ); +// QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; +// symbol3d->setMaterialSettings( materialSettings.clone() ); +// symbol3d->setExtrusionHeight( 10.f ); +// symbol3d->setOffset( 20.f ); +// symbol3d->setEdgesEnabled( true ); +// symbol3d->setEdgeWidth( 8 ); +// symbol3d->setEdgeColor( QColor( 255, 0, 0 ) ); + +// std::unique_ptr< QgsVectorLayer > layer( mLayerBuildings->clone() ); + +// std::unique_ptr< QgsSimpleFillSymbolLayer > simpleFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 0, 0, 0 ), Qt::SolidPattern, QColor( 0, 0, 0 ), Qt::NoPen ); +// std::unique_ptr< QgsFillSymbol > fillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << simpleFill.release() ); +// layer->setRenderer( new QgsSingleSymbolRenderer( fillSymbol.release() ) ); + +// QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d ); +// layer->setRenderer3D( renderer3d ); + +// map->setLayers( QList() << layer.get() ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( { defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 100, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "polygon_edges_height", "polygon_edges_height", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testLineRendering() +//{ +// const QgsRectangle fullExtent( 0, 0, 1000, 1000 ); + +// QgsVectorLayer *layerLines = new QgsVectorLayer( "LineString?crs=EPSG:27700", "lines", "memory" ); + +// QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; +// lineSymbol->setRenderAsSimpleLines( true ); +// lineSymbol->setWidth( 10 ); +// QgsSimpleLineMaterialSettings matSettings; +// matSettings.setAmbient( Qt::red ); +// lineSymbol->setMaterialSettings( matSettings.clone() ); +// layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); + +// QVector pts; +// pts << QgsPoint( 0, 0, 10 ) << QgsPoint( 0, 1000, 10 ) << QgsPoint( 1000, 1000, 10 ) << QgsPoint( 1000, 0, 10 ); +// pts << QgsPoint( 1000, 0, 500 ) << QgsPoint( 1000, 1000, 500 ) << QgsPoint( 0, 1000, 500 ) << QgsPoint( 0, 0, 500 ); +// QgsFeature f1( layerLines->fields() ); +// f1.setGeometry( QgsGeometry( new QgsLineString( pts ) ) ); +// QgsFeatureList flist; +// flist << f1; +// layerLines->dataProvider()->addFeatures( flist ); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerLines ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "line_rendering_1", "line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); + +// // more perspective look +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 45 ); + +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// delete layerLines; +// QGSVERIFYIMAGECHECK( "line_rendering_2", "line_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testLineRenderingCurved() +//{ +// // test rendering of compound curve lines works +// const QgsRectangle fullExtent( 0, 0, 1000, 1000 ); + +// QgsVectorLayer *layerLines = new QgsVectorLayer( "CompoundCurve?crs=EPSG:27700", "lines", "memory" ); + +// QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; +// lineSymbol->setRenderAsSimpleLines( true ); +// lineSymbol->setWidth( 10 ); +// QgsSimpleLineMaterialSettings matSettings; +// matSettings.setAmbient( Qt::red ); +// lineSymbol->setMaterialSettings( matSettings.clone() ); +// layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); + +// QVector pts; +// pts << QgsPoint( 0, 0, 10 ) << QgsPoint( 0, 1000, 10 ) << QgsPoint( 1000, 1000, 10 ) << QgsPoint( 1000, 0, 10 ); +// std::unique_ptr< QgsCompoundCurve > curve = std::make_unique< QgsCompoundCurve >(); +// curve->addCurve( new QgsLineString( pts ) ); +// pts.clear(); +// pts << QgsPoint( 1000, 0, 10 ) << QgsPoint( 1000, 0, 500 ) << QgsPoint( 1000, 1000, 500 ) << QgsPoint( 0, 1000, 500 ) << QgsPoint( 0, 0, 500 ); +// curve->addCurve( new QgsLineString( pts ) ); + +// QgsFeature f1( layerLines->fields() ); +// f1.setGeometry( QgsGeometry( std::move( curve ) ) ); +// QgsFeatureList flist; +// flist << f1; +// layerLines->dataProvider()->addFeatures( flist ); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerLines ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// delete layerLines; +// QGSVERIFYIMAGECHECK( "line_rendering_1", "line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testBufferedLineRendering() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/lines.shp" ), "lines", "ogr" ); +// QVERIFY( layerLines->isValid() ); + +// QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; +// lineSymbol->setWidth( 10 ); +// lineSymbol->setExtrusionHeight( 30 ); +// QgsPhongMaterialSettings matSettings; +// matSettings.setAmbient( Qt::red ); +// lineSymbol->setMaterialSettings( matSettings.clone() ); +// layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerLines ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( { defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 300, 0, 250 ), 500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// delete layerLines; + +// QGSVERIFYIMAGECHECK( "buffered_lines", "buffered_lines", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testBufferedLineRenderingWidth() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/lines.shp" ), "lines", "ogr" ); +// QVERIFY( layerLines->isValid() ); + +// QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; +// lineSymbol->setWidth( 20 ); +// lineSymbol->setExtrusionHeight( 30 ); +// lineSymbol->setOffset( 10 ); +// QgsPhongMaterialSettings matSettings; +// matSettings.setAmbient( Qt::red ); +// lineSymbol->setMaterialSettings( matSettings.clone() ); +// layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerLines ); +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( { defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 300, 0, 250 ), 500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// delete layerLines; +// QGSVERIFYIMAGECHECK( "buffered_lines_width", "buffered_lines_width", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testMapTheme() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerDtm ); + +// // set theme - this should override what we set in setLayers() +// map->setMapThemeCollection( mProject->mapThemeCollection() ); +// map->setTerrainMapTheme( "theme_dtm" ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// QGSVERIFYIMAGECHECK( "terrain_theme", "terrain_theme", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testRuleBasedRenderer() +//{ +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setAmbient( Qt::lightGray ); +// QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; +// symbol3d->setMaterialSettings( materialSettings.clone() ); +// symbol3d->setExtrusionHeight( 10.f ); + +// QgsPhongMaterialSettings materialSettings2; +// materialSettings2.setAmbient( Qt::red ); +// QgsPolygon3DSymbol *symbol3d2 = new QgsPolygon3DSymbol; +// symbol3d2->setMaterialSettings( materialSettings2.clone() ); +// symbol3d2->setExtrusionHeight( 10.f ); + +// QgsRuleBased3DRenderer::Rule *root = new QgsRuleBased3DRenderer::Rule( nullptr ); +// QgsRuleBased3DRenderer::Rule *rule1 = new QgsRuleBased3DRenderer::Rule( symbol3d, "ogc_fid < 29069", "rule 1" ); +// QgsRuleBased3DRenderer::Rule *rule2 = new QgsRuleBased3DRenderer::Rule( symbol3d2, "ogc_fid >= 29069", "rule 2" ); +// root->appendChild( rule1 ); +// root->appendChild( rule2 ); +// QgsRuleBased3DRenderer *renderer3d = new QgsRuleBased3DRenderer( root ); +// mLayerBuildings->setRenderer3D( renderer3d ); + +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerBuildings ); + +// QgsPointLightSettings defaultLight; +// defaultLight.setIntensity( 0.5 ); +// defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// map->setLightSources( { defaultLight.clone() } ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); + +// // When running the test, it would sometimes return partially rendered image. +// // It is probably based on how fast qt3d manages to upload the data to GPU... +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + +// delete scene; +// delete map; + +// QGSVERIFYIMAGECHECK( "rulebased", "rulebased", img, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testAnimationExport() +//{ +// const QgsRectangle fullExtent = mLayerDtm->extent(); + +// Qgs3DMapSettings map; +// map.setCrs( mProject->crs() ); +// map.setExtent( fullExtent ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map.crs() ); +// map.setTerrainGenerator( flatTerrain ); + +// Qgs3DAnimationSettings animSettings; +// Qgs3DAnimationSettings::Keyframes keyframes; +// Qgs3DAnimationSettings::Keyframe kf1; +// kf1.dist = 2500; +// Qgs3DAnimationSettings::Keyframe kf2; +// kf2.time = 2; +// kf2.dist = 3000; +// keyframes << kf1; +// keyframes << kf2; +// animSettings.setKeyframes( keyframes ); + +// const QString dir = QDir::temp().path(); +// QString error; + +// const bool success = Qgs3DUtils::exportAnimation( +// animSettings, +// map, +// 1, +// dir, +// "test3danimation###.png", +// QSize( 600, 400 ), +// error, +// nullptr ); + +// QVERIFY( success ); +// QVERIFY( QFileInfo::exists( ( QDir( dir ).filePath( QStringLiteral( "test3danimation001.png" ) ) ) ) ); +//} + +//void TestQgs3DRendering::testInstancedRendering() +//{ +// const QgsRectangle fullExtent( 1000, 1000, 2000, 2000 ); + +// std::unique_ptr layerPointsZ( new QgsVectorLayer( "PointZ?crs=EPSG:27700", "points Z", "memory" ) ); + +// QgsPoint *p1 = new QgsPoint( 1000, 1000, 50 ); +// QgsPoint *p2 = new QgsPoint( 1000, 2000, 100 ); +// QgsPoint *p3 = new QgsPoint( 2000, 2000, 200 ); + +// QgsFeature f1( layerPointsZ->fields() ); +// QgsFeature f2( layerPointsZ->fields() ); +// QgsFeature f3( layerPointsZ->fields() ); + +// f1.setGeometry( QgsGeometry( p1 ) ); +// f2.setGeometry( QgsGeometry( p2 ) ); +// f3.setGeometry( QgsGeometry( p3 ) ); + +// QgsFeatureList featureList; +// featureList << f1 << f2 << f3; +// layerPointsZ->dataProvider()->addFeatures( featureList ); + +// QgsPoint3DSymbol *sphere3DSymbol = new QgsPoint3DSymbol(); +// sphere3DSymbol->setShape( QgsPoint3DSymbol::Sphere ); +// QVariantMap vmSphere; +// vmSphere[QStringLiteral( "radius" )] = 80.0f; +// sphere3DSymbol->setShapeProperties( vmSphere ); +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setAmbient( Qt::gray ); +// sphere3DSymbol->setMaterialSettings( materialSettings.clone() ); + +// layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( sphere3DSymbol ) ); + +// Qgs3DMapSettings *mapSettings = new Qgs3DMapSettings; +// mapSettings->setCrs( mProject->crs() ); +// mapSettings->setExtent( fullExtent ); +// mapSettings->setLayers( QList() << layerPointsZ.get() ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( mapSettings->crs() ); +// mapSettings->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *mapSettings, &engine ); +// engine.setRootEntity( scene ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); + +// QImage imgSphere = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "sphere_rendering", "sphere_rendering", imgSphere, QString(), 40, QSize( 0, 0 ), 2 ); + +// QgsPoint3DSymbol *cylinder3DSymbol = new QgsPoint3DSymbol(); +// cylinder3DSymbol->setShape( QgsPoint3DSymbol::Cylinder ); +// QVariantMap vmCylinder; +// vmCylinder[QStringLiteral( "radius" )] = 20.0f; +// vmCylinder[QStringLiteral( "length" )] = 200.0f; +// cylinder3DSymbol->setShapeProperties( vmCylinder ); +// cylinder3DSymbol->setMaterialSettings( materialSettings.clone() ); + +// layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( cylinder3DSymbol ) ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 0 ); + +// QImage imgCylinder = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete mapSettings; +// QGSVERIFYIMAGECHECK( "cylinder_rendering", "cylinder_rendering", imgCylinder, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +//void TestQgs3DRendering::testBillboardRendering() +//{ +// const QgsRectangle fullExtent( 1000, 1000, 2000, 2000 ); + +// std::unique_ptr layerPointsZ( new QgsVectorLayer( "PointZ?crs=EPSG:27700", "points Z", "memory" ) ); + +// QgsPoint *p1 = new QgsPoint( 1000, 1000, 50 ); +// QgsPoint *p2 = new QgsPoint( 1000, 2000, 100 ); +// QgsPoint *p3 = new QgsPoint( 2000, 2000, 200 ); + +// QgsFeature f1( layerPointsZ->fields() ); +// QgsFeature f2( layerPointsZ->fields() ); +// QgsFeature f3( layerPointsZ->fields() ); + +// f1.setGeometry( QgsGeometry( p1 ) ); +// f2.setGeometry( QgsGeometry( p2 ) ); +// f3.setGeometry( QgsGeometry( p3 ) ); - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerRgb ); +// QgsFeatureList featureList; +// featureList << f1 << f2 << f3; +// layerPointsZ->dataProvider()->addFeatures( featureList ); - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; - demTerrain->setLayer( mLayerDtm ); - map->setTerrainGenerator( demTerrain ); - map->setTerrainVerticalScale( 3 ); +// QgsMarkerSymbol *markerSymbol = static_cast( QgsSymbol::defaultSymbol( Qgis::GeometryType::Point ) ); +// markerSymbol->setColor( QColor( 255, 0, 0 ) ); +// markerSymbol->setSize( 4 ); +// QgsSimpleMarkerSymbolLayer *sl = static_cast( markerSymbol->symbolLayer( 0 ) ) ; +// sl->setStrokeColor( QColor( 0, 0, 255 ) ); +// sl->setStrokeWidth( 2 ); +// QgsPoint3DSymbol *point3DSymbol = new QgsPoint3DSymbol(); +// point3DSymbol->setBillboardSymbol( markerSymbol ); +// point3DSymbol->setShape( QgsPoint3DSymbol::Billboard ); + +// layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( point3DSymbol ) ); + +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerPointsZ.get() ); + +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); + +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "billboard_rendering_1", "billboard_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 0 ); +// // more perspective look +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 45 ); - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; - QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "billboard_rendering_2", "billboard_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} - delete scene; - delete map; +//void TestQgs3DRendering::testEpsg4978LineRendering() +//{ +// QgsProject p; - QGSVERIFYIMAGECHECK( "dem_terrain_1", "dem_terrain_1", img3, QString(), 40, QSize( 0, 0 ), 2 ); -} +// QgsCoordinateReferenceSystem newCrs( QStringLiteral( "EPSG:4978" ) ); +// p.setCrs( newCrs ); + +// QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/earth_size_sphere_4978.gpkg" ), "lines", "ogr" ); + +// QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; +// lineSymbol->setRenderAsSimpleLines( true ); +// lineSymbol->setWidth( 2 ); +// QgsSimpleLineMaterialSettings matSettings; +// matSettings.setAmbient( Qt::red ); +// lineSymbol->setMaterialSettings( matSettings.clone() ); +// layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); + +// const QgsRectangle fullExtent = layerLines->extent(); -void TestQgs3DRendering::testTerrainShading() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( p.crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << layerLines ); - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - // no terrain layers set! +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); + +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 1.5e7, 0, 0 ); + +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; - demTerrain->setLayer( mLayerDtm ); - map->setTerrainGenerator( demTerrain ); - map->setTerrainVerticalScale( 3 ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "4978_line_rendering_1", "4978_line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); + +// // more perspective look +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 1.5e7, 45, 45 ); - QgsPhongMaterialSettings terrainMaterial; - terrainMaterial.setAmbient( QColor( 0, 0, 0 ) ); - terrainMaterial.setDiffuse( QColor( 255, 255, 0 ) ); - terrainMaterial.setSpecular( QColor( 255, 255, 255 ) ); - map->setTerrainShadingMaterial( terrainMaterial ); - map->setTerrainShadingEnabled( true ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; +// delete layerLines; - QgsPointLightSettings defaultLight; - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - defaultLight.setIntensity( 0.5 ); - map->setLightSources( {defaultLight.clone() } ); +// QGSVERIFYIMAGECHECK( "4978_line_rendering_2", "4978_line_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); +//void TestQgs3DRendering::testFilteredFlatTerrain() +//{ +// QgsRectangle fullExtent = mLayerDtm->extent(); +// // Set the extent to have width > height +// fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 ); +// fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 ); - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 0 ); +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerRgb ); - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); +// QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; +// flatTerrain->setCrs( map->crs() ); +// map->setTerrainGenerator( flatTerrain ); - QImage img3 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); - QGSVERIFYIMAGECHECK( "shaded_terrain_no_layers", "shaded_terrain_no_layers", img3, QString(), 40, QSize( 0, 0 ), 2 ); -} +// // look from the top +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 0, 0 ); -void TestQgs3DRendering::testExtrudedPolygons() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerBuildings << mLayerRgb ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( {defaultLight.clone() } ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "flat_terrain_filtered_1", "flat_terrain_filtered_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); +// // Now set the extent to have height > width and redo +// fullExtent = mLayerDtm->extent(); +// fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 ); +// fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 ); +// map->setExtent( fullExtent ); - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); +// delete scene; +// delete map; - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "flat_terrain_filtered_2", "flat_terrain_filtered_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} - QGSVERIFYIMAGECHECK( "polygon3d_extrusion", "polygon3d_extrusion", img, QString(), 40, QSize( 0, 0 ), 2 ); +//void TestQgs3DRendering::testFilteredDemTerrain() +//{ +// QgsRectangle fullExtent = mLayerDtm->extent(); +// // Set the extent to have width > height +// fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 ); +// fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 ); - // change opacity - QgsPhongMaterialSettings materialSettings; - materialSettings.setAmbient( Qt::lightGray ); - materialSettings.setOpacity( 0.5f ); - QgsPolygon3DSymbol *symbol3dOpacity = new QgsPolygon3DSymbol; - symbol3dOpacity->setMaterialSettings( materialSettings.clone() ); - symbol3dOpacity->setExtrusionHeight( 10.f ); - QgsVectorLayer3DRenderer *renderer3dOpacity = new QgsVectorLayer3DRenderer( symbol3dOpacity ); - mLayerBuildings->setRenderer3D( renderer3dOpacity ); - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "polygon3d_extrusion_opacity", "polygon3d_extrusion_opacity", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} +// Qgs3DMapSettings *map = new Qgs3DMapSettings; +// map->setCrs( mProject->crs() ); +// map->setExtent( fullExtent ); +// map->setLayers( QList() << mLayerRgb ); -void TestQgs3DRendering::testExtrudedPolygonsDataDefined() -{ - QgsPropertyCollection propertyColection; - QgsProperty diffuseColor; - QgsProperty ambientColor; - QgsProperty specularColor; - diffuseColor.setExpressionString( QStringLiteral( "color_rgb( 120*(\"ogc_fid\"%3),125,0)" ) ); - ambientColor.setExpressionString( QStringLiteral( "color_rgb( 120,(\"ogc_fid\"%2)*255,0)" ) ); - specularColor.setExpressionString( QStringLiteral( "'yellow'" ) ); - propertyColection.setProperty( QgsAbstractMaterialSettings::Diffuse, diffuseColor ); - propertyColection.setProperty( QgsAbstractMaterialSettings::Ambient, ambientColor ); - propertyColection.setProperty( QgsAbstractMaterialSettings::Specular, specularColor ); - QgsPhongMaterialSettings materialSettings; - materialSettings.setDataDefinedProperties( propertyColection ); - materialSettings.setAmbient( Qt::red ); - QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; - symbol3d->setMaterialSettings( materialSettings.clone() ); - symbol3d->setExtrusionHeight( 10.f ); - QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d ); - mLayerBuildings->setRenderer3D( renderer3d ); +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; +// demTerrain->setLayer( mLayerDtm ); +// map->setTerrainGenerator( demTerrain ); +// map->setTerrainVerticalScale( 3 ); - const QgsRectangle fullExtent = mLayerDtm->extent(); +// QgsOffscreen3DEngine engine; +// Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); +// engine.setRootEntity( scene ); - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerBuildings << mLayerRgb ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( { defaultLight.clone() } ); +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 225 ); - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); +// // When running the test on Travis, it would initially return empty rendered image. +// // Capturing the initial image and throwing it away fixes that. Hopefully we will +// // find a better fix in the future. +// Qgs3DUtils::captureSceneImage( engine, scene ); - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); +// QImage img1 = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "dem_terrain_filtered_1", "dem_terrain_filtered_1", img1, QString(), 40, QSize( 0, 0 ), 2 ); - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); +// // Now set the extent to have height > width and redo +// fullExtent = mLayerDtm->extent(); +// fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 ); +// fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 ); +// map->setExtent( fullExtent ); +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 45 ); - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - QGSVERIFYIMAGECHECK( "polygon3d_extrusion_data_defined", "polygon3d_extrusion_data_defined", img, QString(), 40, QSize( 0, 0 ), 2 ); -} +// delete scene; +// delete map; -void TestQgs3DRendering::testExtrudedPolygonsGoochShading() +// QGSVERIFYIMAGECHECK( "dem_terrain_filtered_2", "dem_terrain_filtered_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); +//} + +void TestQgs3DRendering::testFilteredExtrudedPolygons() { - const QgsRectangle fullExtent = mLayerDtm->extent(); + const QgsRectangle fullExtent = QgsRectangle( 321720, 129190, 322560, 130060 ); Qgs3DMapSettings *map = new Qgs3DMapSettings; map->setCrs( mProject->crs() ); + map->setExtent( fullExtent ); + map->setLayers( QList() << mLayerBuildings << mLayerRgb ); QgsPointLightSettings defaultLight; defaultLight.setIntensity( 0.5 ); defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); map->setLightSources( {defaultLight.clone() } ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerBuildings << mLayerRgb ); QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; flatTerrain->setCrs( map->crs() ); map->setTerrainGenerator( flatTerrain ); + QPoint winSize = QPoint( 640, 480 ); // default window size + QgsOffscreen3DEngine engine; + engine.setSize( QSize( winSize.x(), winSize.y() ) ); Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); engine.setRootEntity( scene ); - QgsGoochMaterialSettings materialSettings; - materialSettings.setWarm( QColor( 224, 224, 17 ) ); - materialSettings.setCool( QColor( 21, 187, 235 ) ); - materialSettings.setAlpha( 0.2f ); - materialSettings.setBeta( 0.6f ); - QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; - symbol3d->setMaterialSettings( materialSettings.clone() ); - symbol3d->setExtrusionHeight( 10.f ); - QgsVectorLayer3DRenderer *renderer3dOpacity = new QgsVectorLayer3DRenderer( symbol3d ); - mLayerBuildings->setRenderer3D( renderer3dOpacity ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "polygon3d_extrusion_gooch_shading", "polygon3d_extrusion_gooch_shading", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testPolygonsEdges() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); + scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 1500, 45, 0 ); QgsPhongMaterialSettings materialSettings; materialSettings.setAmbient( Qt::lightGray ); QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; symbol3d->setMaterialSettings( materialSettings.clone() ); symbol3d->setExtrusionHeight( 10.f ); - symbol3d->setOffset( 20.f ); - symbol3d->setEdgesEnabled( true ); - symbol3d->setEdgeWidth( 8 ); - symbol3d->setEdgeColor( QColor( 255, 0, 0 ) ); - - std::unique_ptr< QgsVectorLayer > layer( mLayerBuildings->clone() ); - - std::unique_ptr< QgsSimpleFillSymbolLayer > simpleFill = std::make_unique< QgsSimpleFillSymbolLayer >( QColor( 0, 0, 0 ), Qt::SolidPattern, QColor( 0, 0, 0 ), Qt::NoPen ); - std::unique_ptr< QgsFillSymbol > fillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << simpleFill.release() ); - layer->setRenderer( new QgsSingleSymbolRenderer( fillSymbol.release() ) ); - QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d ); - layer->setRenderer3D( renderer3d ); - - map->setLayers( QList() << layer.get() ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( { defaultLight.clone() } ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 100, 45, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "polygon_edges_height", "polygon_edges_height", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testLineRendering() -{ - const QgsRectangle fullExtent( 0, 0, 1000, 1000 ); - - QgsVectorLayer *layerLines = new QgsVectorLayer( "LineString?crs=EPSG:27700", "lines", "memory" ); - - QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; - lineSymbol->setRenderAsSimpleLines( true ); - lineSymbol->setWidth( 10 ); - QgsSimpleLineMaterialSettings matSettings; - matSettings.setAmbient( Qt::red ); - lineSymbol->setMaterialSettings( matSettings.clone() ); - layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); - - QVector pts; - pts << QgsPoint( 0, 0, 10 ) << QgsPoint( 0, 1000, 10 ) << QgsPoint( 1000, 1000, 10 ) << QgsPoint( 1000, 0, 10 ); - pts << QgsPoint( 1000, 0, 500 ) << QgsPoint( 1000, 1000, 500 ) << QgsPoint( 0, 1000, 500 ) << QgsPoint( 0, 0, 500 ); - QgsFeature f1( layerLines->fields() ); - f1.setGeometry( QgsGeometry( new QgsLineString( pts ) ) ); - QgsFeatureList flist; - flist << f1; - layerLines->dataProvider()->addFeatures( flist ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerLines ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "line_rendering_1", "line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - - // more perspective look - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 45 ); - - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - delete layerLines; - QGSVERIFYIMAGECHECK( "line_rendering_2", "line_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testLineRenderingCurved() -{ - // test rendering of compound curve lines works - const QgsRectangle fullExtent( 0, 0, 1000, 1000 ); - - QgsVectorLayer *layerLines = new QgsVectorLayer( "CompoundCurve?crs=EPSG:27700", "lines", "memory" ); - - QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; - lineSymbol->setRenderAsSimpleLines( true ); - lineSymbol->setWidth( 10 ); - QgsSimpleLineMaterialSettings matSettings; - matSettings.setAmbient( Qt::red ); - lineSymbol->setMaterialSettings( matSettings.clone() ); - layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); - - QVector pts; - pts << QgsPoint( 0, 0, 10 ) << QgsPoint( 0, 1000, 10 ) << QgsPoint( 1000, 1000, 10 ) << QgsPoint( 1000, 0, 10 ); - std::unique_ptr< QgsCompoundCurve > curve = std::make_unique< QgsCompoundCurve >(); - curve->addCurve( new QgsLineString( pts ) ); - pts.clear(); - pts << QgsPoint( 1000, 0, 10 ) << QgsPoint( 1000, 0, 500 ) << QgsPoint( 1000, 1000, 500 ) << QgsPoint( 0, 1000, 500 ) << QgsPoint( 0, 0, 500 ); - curve->addCurve( new QgsLineString( pts ) ); - - QgsFeature f1( layerLines->fields() ); - f1.setGeometry( QgsGeometry( std::move( curve ) ) ); - QgsFeatureList flist; - flist << f1; - layerLines->dataProvider()->addFeatures( flist ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerLines ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - delete layerLines; - QGSVERIFYIMAGECHECK( "line_rendering_1", "line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testBufferedLineRendering() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); - - QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/lines.shp" ), "lines", "ogr" ); - QVERIFY( layerLines->isValid() ); - - QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; - lineSymbol->setWidth( 10 ); - lineSymbol->setExtrusionHeight( 30 ); - QgsPhongMaterialSettings matSettings; - matSettings.setAmbient( Qt::red ); - lineSymbol->setMaterialSettings( matSettings.clone() ); - layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerLines ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( { defaultLight.clone() } ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 300, 0, 250 ), 500, 45, 0 ); + mLayerBuildings->setRenderer3D( renderer3d ); // When running the test on Travis, it would initially return empty rendered image. // Capturing the initial image and throwing it away fixes that. Hopefully we will // find a better fix in the future. Qgs3DUtils::captureSceneImage( engine, scene ); +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - delete layerLines; - - QGSVERIFYIMAGECHECK( "buffered_lines", "buffered_lines", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testBufferedLineRenderingWidth() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); - - QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/lines.shp" ), "lines", "ogr" ); - QVERIFY( layerLines->isValid() ); - - QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; - lineSymbol->setWidth( 20 ); - lineSymbol->setExtrusionHeight( 30 ); - lineSymbol->setOffset( 10 ); - QgsPhongMaterialSettings matSettings; - matSettings.setAmbient( Qt::red ); - lineSymbol->setMaterialSettings( matSettings.clone() ); - layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerLines ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( { defaultLight.clone() } ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 300, 0, 250 ), 500, 45, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); +// delete scene; +// delete map; - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - delete layerLines; - QGSVERIFYIMAGECHECK( "buffered_lines_width", "buffered_lines_width", img, QString(), 40, QSize( 0, 0 ), 2 ); +// QGSVERIFYIMAGECHECK( "polygon3d_extrusion_filtered", "polygon3d_extrusion_filtered", img, QString(), 40, QSize( 0, 0 ), 2 ); } -void TestQgs3DRendering::testMapTheme() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerDtm ); - - // set theme - this should override what we set in setLayers() - map->setMapThemeCollection( mProject->mapThemeCollection() ); - map->setTerrainMapTheme( "theme_dtm" ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - QGSVERIFYIMAGECHECK( "terrain_theme", "terrain_theme", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testRuleBasedRenderer() -{ - QgsPhongMaterialSettings materialSettings; - materialSettings.setAmbient( Qt::lightGray ); - QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; - symbol3d->setMaterialSettings( materialSettings.clone() ); - symbol3d->setExtrusionHeight( 10.f ); - - QgsPhongMaterialSettings materialSettings2; - materialSettings2.setAmbient( Qt::red ); - QgsPolygon3DSymbol *symbol3d2 = new QgsPolygon3DSymbol; - symbol3d2->setMaterialSettings( materialSettings2.clone() ); - symbol3d2->setExtrusionHeight( 10.f ); - - QgsRuleBased3DRenderer::Rule *root = new QgsRuleBased3DRenderer::Rule( nullptr ); - QgsRuleBased3DRenderer::Rule *rule1 = new QgsRuleBased3DRenderer::Rule( symbol3d, "ogc_fid < 29069", "rule 1" ); - QgsRuleBased3DRenderer::Rule *rule2 = new QgsRuleBased3DRenderer::Rule( symbol3d2, "ogc_fid >= 29069", "rule 2" ); - root->appendChild( rule1 ); - root->appendChild( rule2 ); - QgsRuleBased3DRenderer *renderer3d = new QgsRuleBased3DRenderer( root ); - mLayerBuildings->setRenderer3D( renderer3d ); - - const QgsRectangle fullExtent = mLayerDtm->extent(); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerBuildings ); - - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( { defaultLight.clone() } ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 500, 45, 0 ); - - // When running the test, it would sometimes return partially rendered image. - // It is probably based on how fast qt3d manages to upload the data to GPU... - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "rulebased", "rulebased", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testAnimationExport() -{ - const QgsRectangle fullExtent = mLayerDtm->extent(); - - Qgs3DMapSettings map; - map.setCrs( mProject->crs() ); - map.setExtent( fullExtent ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map.crs() ); - map.setTerrainGenerator( flatTerrain ); - - Qgs3DAnimationSettings animSettings; - Qgs3DAnimationSettings::Keyframes keyframes; - Qgs3DAnimationSettings::Keyframe kf1; - kf1.dist = 2500; - Qgs3DAnimationSettings::Keyframe kf2; - kf2.time = 2; - kf2.dist = 3000; - keyframes << kf1; - keyframes << kf2; - animSettings.setKeyframes( keyframes ); - - const QString dir = QDir::temp().path(); - QString error; - - const bool success = Qgs3DUtils::exportAnimation( - animSettings, - map, - 1, - dir, - "test3danimation###.png", - QSize( 600, 400 ), - error, - nullptr ); - - QVERIFY( success ); - QVERIFY( QFileInfo::exists( ( QDir( dir ).filePath( QStringLiteral( "test3danimation001.png" ) ) ) ) ); -} - -void TestQgs3DRendering::testInstancedRendering() -{ - const QgsRectangle fullExtent( 1000, 1000, 2000, 2000 ); - - std::unique_ptr layerPointsZ( new QgsVectorLayer( "PointZ?crs=EPSG:27700", "points Z", "memory" ) ); - - QgsPoint *p1 = new QgsPoint( 1000, 1000, 50 ); - QgsPoint *p2 = new QgsPoint( 1000, 2000, 100 ); - QgsPoint *p3 = new QgsPoint( 2000, 2000, 200 ); - - QgsFeature f1( layerPointsZ->fields() ); - QgsFeature f2( layerPointsZ->fields() ); - QgsFeature f3( layerPointsZ->fields() ); - - f1.setGeometry( QgsGeometry( p1 ) ); - f2.setGeometry( QgsGeometry( p2 ) ); - f3.setGeometry( QgsGeometry( p3 ) ); - - QgsFeatureList featureList; - featureList << f1 << f2 << f3; - layerPointsZ->dataProvider()->addFeatures( featureList ); - - QgsPoint3DSymbol *sphere3DSymbol = new QgsPoint3DSymbol(); - sphere3DSymbol->setShape( QgsPoint3DSymbol::Sphere ); - QVariantMap vmSphere; - vmSphere[QStringLiteral( "radius" )] = 80.0f; - sphere3DSymbol->setShapeProperties( vmSphere ); - QgsPhongMaterialSettings materialSettings; - materialSettings.setAmbient( Qt::gray ); - sphere3DSymbol->setMaterialSettings( materialSettings.clone() ); - - layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( sphere3DSymbol ) ); - - Qgs3DMapSettings *mapSettings = new Qgs3DMapSettings; - mapSettings->setCrs( mProject->crs() ); - mapSettings->setExtent( fullExtent ); - mapSettings->setLayers( QList() << layerPointsZ.get() ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( mapSettings->crs() ); - mapSettings->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *mapSettings, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage imgSphere = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "sphere_rendering", "sphere_rendering", imgSphere, QString(), 40, QSize( 0, 0 ), 2 ); - - QgsPoint3DSymbol *cylinder3DSymbol = new QgsPoint3DSymbol(); - cylinder3DSymbol->setShape( QgsPoint3DSymbol::Cylinder ); - QVariantMap vmCylinder; - vmCylinder[QStringLiteral( "radius" )] = 20.0f; - vmCylinder[QStringLiteral( "length" )] = 200.0f; - cylinder3DSymbol->setShapeProperties( vmCylinder ); - cylinder3DSymbol->setMaterialSettings( materialSettings.clone() ); - - layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( cylinder3DSymbol ) ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 60, 0 ); - - QImage imgCylinder = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete mapSettings; - QGSVERIFYIMAGECHECK( "cylinder_rendering", "cylinder_rendering", imgCylinder, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testBillboardRendering() -{ - const QgsRectangle fullExtent( 1000, 1000, 2000, 2000 ); - - std::unique_ptr layerPointsZ( new QgsVectorLayer( "PointZ?crs=EPSG:27700", "points Z", "memory" ) ); - - QgsPoint *p1 = new QgsPoint( 1000, 1000, 50 ); - QgsPoint *p2 = new QgsPoint( 1000, 2000, 100 ); - QgsPoint *p3 = new QgsPoint( 2000, 2000, 200 ); - - QgsFeature f1( layerPointsZ->fields() ); - QgsFeature f2( layerPointsZ->fields() ); - QgsFeature f3( layerPointsZ->fields() ); - - f1.setGeometry( QgsGeometry( p1 ) ); - f2.setGeometry( QgsGeometry( p2 ) ); - f3.setGeometry( QgsGeometry( p3 ) ); - - QgsFeatureList featureList; - featureList << f1 << f2 << f3; - layerPointsZ->dataProvider()->addFeatures( featureList ); - - QgsMarkerSymbol *markerSymbol = static_cast( QgsSymbol::defaultSymbol( Qgis::GeometryType::Point ) ); - markerSymbol->setColor( QColor( 255, 0, 0 ) ); - markerSymbol->setSize( 4 ); - QgsSimpleMarkerSymbolLayer *sl = static_cast( markerSymbol->symbolLayer( 0 ) ) ; - sl->setStrokeColor( QColor( 0, 0, 255 ) ); - sl->setStrokeWidth( 2 ); - QgsPoint3DSymbol *point3DSymbol = new QgsPoint3DSymbol(); - point3DSymbol->setBillboardSymbol( markerSymbol ); - point3DSymbol->setShape( QgsPoint3DSymbol::Billboard ); - - layerPointsZ->setRenderer3D( new QgsVectorLayer3DRenderer( point3DSymbol ) ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerPointsZ.get() ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "billboard_rendering_1", "billboard_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - - // more perspective look - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2500, 45, 45 ); - - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "billboard_rendering_2", "billboard_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testEpsg4978LineRendering() -{ - QgsProject p; - - QgsCoordinateReferenceSystem newCrs( QStringLiteral( "EPSG:4978" ) ); - p.setCrs( newCrs ); - - QgsVectorLayer *layerLines = new QgsVectorLayer( testDataPath( "/3d/earth_size_sphere_4978.gpkg" ), "lines", "ogr" ); - - QgsLine3DSymbol *lineSymbol = new QgsLine3DSymbol; - lineSymbol->setRenderAsSimpleLines( true ); - lineSymbol->setWidth( 2 ); - QgsSimpleLineMaterialSettings matSettings; - matSettings.setAmbient( Qt::red ); - lineSymbol->setMaterialSettings( matSettings.clone() ); - layerLines->setRenderer3D( new QgsVectorLayer3DRenderer( lineSymbol ) ); - - const QgsRectangle fullExtent = layerLines->extent(); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( p.crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << layerLines ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 1.5e7, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "4978_line_rendering_1", "4978_line_rendering_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - - // more perspective look - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 1.5e7, 45, 45 ); - - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - delete scene; - delete map; - delete layerLines; - - QGSVERIFYIMAGECHECK( "4978_line_rendering_2", "4978_line_rendering_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testFilteredFlatTerrain() -{ - QgsRectangle fullExtent = mLayerDtm->extent(); - // Set the extent to have width > height - fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 ); - fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerRgb ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - // look from the top - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 0, 0 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "flat_terrain_filtered_1", "flat_terrain_filtered_1", img, QString(), 40, QSize( 0, 0 ), 2 ); - - // Now set the extent to have height > width and redo - fullExtent = mLayerDtm->extent(); - fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 ); - fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 ); - map->setExtent( fullExtent ); - - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "flat_terrain_filtered_2", "flat_terrain_filtered_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testFilteredDemTerrain() -{ - QgsRectangle fullExtent = mLayerDtm->extent(); - // Set the extent to have width > height - fullExtent.setYMaximum( fullExtent.yMaximum() - fullExtent.height() / 3 ); - fullExtent.setYMinimum( fullExtent.yMinimum() + fullExtent.height() / 3 ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerRgb ); - - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; - demTerrain->setLayer( mLayerDtm ); - map->setTerrainGenerator( demTerrain ); - map->setTerrainVerticalScale( 3 ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 225 ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - - QImage img1 = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "dem_terrain_filtered_1", "dem_terrain_filtered_1", img1, QString(), 40, QSize( 0, 0 ), 2 ); - - // Now set the extent to have height > width and redo - fullExtent = mLayerDtm->extent(); - fullExtent.setXMaximum( fullExtent.xMaximum() - fullExtent.width() / 3 ); - fullExtent.setXMinimum( fullExtent.xMinimum() + fullExtent.width() / 3 ); - map->setExtent( fullExtent ); - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 2000, 60, 45 ); - - QImage img2 = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "dem_terrain_filtered_2", "dem_terrain_filtered_2", img2, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testFilteredExtrudedPolygons() -{ - const QgsRectangle fullExtent = QgsRectangle( 321720, 129190, 322560, 130060 ); - - Qgs3DMapSettings *map = new Qgs3DMapSettings; - map->setCrs( mProject->crs() ); - map->setExtent( fullExtent ); - map->setLayers( QList() << mLayerBuildings << mLayerRgb ); - QgsPointLightSettings defaultLight; - defaultLight.setIntensity( 0.5 ); - defaultLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - map->setLightSources( {defaultLight.clone() } ); - - QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; - flatTerrain->setCrs( map->crs() ); - map->setTerrainGenerator( flatTerrain ); - - QgsOffscreen3DEngine engine; - Qgs3DMapScene *scene = new Qgs3DMapScene( *map, &engine ); - engine.setRootEntity( scene ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 250 ), 1500, 45, 0 ); - - QgsPhongMaterialSettings materialSettings; - materialSettings.setAmbient( Qt::lightGray ); - QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; - symbol3d->setMaterialSettings( materialSettings.clone() ); - symbol3d->setExtrusionHeight( 10.f ); - QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d ); - mLayerBuildings->setRenderer3D( renderer3d ); - - // When running the test on Travis, it would initially return empty rendered image. - // Capturing the initial image and throwing it away fixes that. Hopefully we will - // find a better fix in the future. - Qgs3DUtils::captureSceneImage( engine, scene ); - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - - delete scene; - delete map; - - QGSVERIFYIMAGECHECK( "polygon3d_extrusion_filtered", "polygon3d_extrusion_filtered", img, QString(), 40, QSize( 0, 0 ), 2 ); -} - -void TestQgs3DRendering::testAmbientOcclusion() -{ - // ============================================= - // =========== creating Qgs3DMapSettings - QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); - QVERIFY( layerDtm->isValid() ); - - const QgsRectangle fullExtent = layerDtm->extent(); - - QgsProject project; - project.addMapLayer( layerDtm ); - - Qgs3DMapSettings mapSettings; - mapSettings.setCrs( project.crs() ); - mapSettings.setExtent( fullExtent ); - mapSettings.setLayers( {layerDtm, mLayerBuildings} ); - - mapSettings.setTransformContext( project.transformContext() ); - mapSettings.setPathResolver( project.pathResolver() ); - mapSettings.setMapThemeCollection( project.mapThemeCollection() ); - - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; - demTerrain->setLayer( layerDtm ); - mapSettings.setTerrainGenerator( demTerrain ); - mapSettings.setTerrainVerticalScale( 3 ); - - QgsPointLightSettings defaultPointLight; - defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) ); - defaultPointLight.setConstantAttenuation( 0 ); - mapSettings.setLightSources( {defaultPointLight.clone() } ); - mapSettings.setOutputDpi( 92 ); - - // =========== creating Qgs3DMapScene - QPoint winSize = QPoint( 640, 480 ); // default window size - - QgsOffscreen3DEngine engine; - engine.setSize( QSize( winSize.x(), winSize.y() ) ); - Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); - engine.setRootEntity( scene ); - - // =========== set camera position - scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 400, 50, 10 ); - - QgsAmbientOcclusionSettings aoSettings = mapSettings.ambientOcclusionSettings(); - aoSettings.setEnabled( false ); - mapSettings.setAmbientOcclusionSettings( aoSettings ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "ambient_occlusion_1", "ambient_occlusion_1", img, QString(), 40, QSize( 0, 0 ), 15 ); - - aoSettings.setEnabled( true ); - mapSettings.setAmbientOcclusionSettings( aoSettings ); - - img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "ambient_occlusion_2", "ambient_occlusion_2", img, QString(), 40, QSize( 0, 0 ), 15 ); - - delete scene; - mapSettings.setLayers( {} ); - demTerrain->deleteLater(); -} - -void TestQgs3DRendering::testDepthBuffer() -{ - // ============================================= - // =========== creating Qgs3DMapSettings - QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); - QVERIFY( layerDtm->isValid() ); - QgsProject project; - project.addMapLayer( layerDtm ); - - const QgsRectangle fullExtent = layerDtm->extent(); - - Qgs3DMapSettings mapSettings; - mapSettings.setCrs( project.crs() ); - mapSettings.setExtent( fullExtent ); - mapSettings.setLayers( {layerDtm} ); - - mapSettings.setTransformContext( project.transformContext() ); - mapSettings.setPathResolver( project.pathResolver() ); - mapSettings.setMapThemeCollection( project.mapThemeCollection() ); - - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; - demTerrain->setLayer( layerDtm ); - mapSettings.setTerrainGenerator( demTerrain ); - mapSettings.setTerrainVerticalScale( 3 ); - - QgsPointLightSettings defaultPointLight; - defaultPointLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); - defaultPointLight.setConstantAttenuation( 0 ); - mapSettings.setLightSources( {defaultPointLight.clone() } ); - mapSettings.setOutputDpi( 92 ); - - // =========== creating Qgs3DMapScene - QPoint winSize = QPoint( 640, 480 ); // default window size - QPoint midPos = winSize / 2; - - QgsOffscreen3DEngine engine; - engine.setSize( QSize( winSize.x(), winSize.y() ) ); - Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); - engine.setRootEntity( scene ); - - // =========== set camera position - scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 ); - - // ============================================= - // =========== TEST DEPTH - QgsCameraController4Test *testCam = static_cast( scene->cameraController() ); - - // retrieve 3D depth image - QImage depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); - QImage grayImage = convertDepthImageToGray16Image( depthImage ); - QGSVERIFYIMAGECHECK( "depth_retrieve_image", "depth_retrieve_image", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); - - // =========== TEST WHEEL ZOOM - QVector3D startPos = scene->cameraController()->camera()->position(); - // set cameraController mouse pos to middle screen - QMouseEvent mouseEvent( QEvent::MouseButtonPress, QPointF( midPos.x(), midPos.y() ), - Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier ); - testCam->superOnMousePressed( new Qt3DInput::QMouseEvent( mouseEvent ) ); - - // Check first wheel action - QWheelEvent wheelEvent( midPos, midPos, QPoint(), QPoint( 0, 120 ), - Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, - false, Qt::MouseEventSynthesizedByApplication ); - testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent ) ); - QCOMPARE( testCam->cumulatedWheelY(), wheelEvent.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); - - depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); - grayImage = convertDepthImageToGray16Image( depthImage ); - QGSVERIFYIMAGECHECK( "depth_wheel_action_1", "depth_wheel_action_1", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); - - scene->cameraController()->depthBufferCaptured( depthImage ); - - QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.7, 224.6, 185.5 ), 1.0 ); - QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.7, 224.6, 185.5 ), 1.0 ); - QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 955.4, 1.0 ); - QCOMPARE( testCam->cumulatedWheelY(), 0 ); - - // Checking second wheel action - QWheelEvent wheelEvent2( midPos, midPos, QPoint(), QPoint( 0, 120 ), - Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, - false, Qt::MouseEventSynthesizedByApplication ); - testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent2 ) ); - QCOMPARE( testCam->cumulatedWheelY(), wheelEvent2.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); - - depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); - grayImage = convertDepthImageToGray16Image( depthImage ); - QGSVERIFYIMAGECHECK( "depth_wheel_action_2", "depth_wheel_action_2", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); - - scene->cameraController()->depthBufferCaptured( depthImage ); - - QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.5, 223.5, 184.7 ), 1.0 ); - QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.5, 223.5, 184.7 ), 1.0 ); - QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 757.4, 1.0 ); - QCOMPARE( testCam->cumulatedWheelY(), 0 ); - - // Checking third wheel action - QWheelEvent wheelEvent3( midPos, midPos, QPoint( ), QPoint( 0, 480 ), - Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, - false, Qt::MouseEventSynthesizedByApplication ); - testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent3 ) ); - QCOMPARE( testCam->cumulatedWheelY(), wheelEvent3.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); - - depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); - grayImage = convertDepthImageToGray16Image( depthImage ); - QGSVERIFYIMAGECHECK( "depth_wheel_action_3", "depth_wheel_action_3", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); - - scene->cameraController()->depthBufferCaptured( depthImage ); - - QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.4, 222.8, 184.1 ), 1.0 ); - QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.4, 222.8, 184.1 ), 1.0 ); - QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 126.4, 0.1 ); - QCOMPARE( testCam->cumulatedWheelY(), 0 ); - - // Checking fourth wheel action - QWheelEvent wheelEvent4( midPos, midPos, QPoint(), QPoint( 0, 120 ), - Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, - false, Qt::MouseEventSynthesizedByApplication ); - testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent4 ) ); - QCOMPARE( testCam->cumulatedWheelY(), wheelEvent4.angleDelta().y() * 5 ); - QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); - - depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); - grayImage = convertDepthImageToGray16Image( depthImage ); - QGSVERIFYIMAGECHECK( "depth_wheel_action_4", "depth_wheel_action_4", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); - - scene->cameraController()->depthBufferCaptured( depthImage ); - - QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.3, 221.7, 183.2 ), 1.0 ); - QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.3, 221.7, 183.2 ), 1.0 ); - QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 101.1, 0.1 ); - QCOMPARE( testCam->cumulatedWheelY(), 0 ); - - // Checking camera position - QVector3D diff = scene->cameraController()->camera()->position() - startPos; - QGSCOMPARENEARVECTOR3D( diff, QVector3D( 125, -850, -700 ), 3.0 ); - - delete scene; - mapSettings.setLayers( {} ); - demTerrain->deleteLater(); -} - -void TestQgs3DRendering::testDebugMap() -{ - // ============================================= - // =========== creating Qgs3DMapSettings - QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); - QVERIFY( layerDtm->isValid() ); - - const QgsRectangle fullExtent = layerDtm->extent(); - - QgsProject project; - project.addMapLayer( layerDtm ); - - Qgs3DMapSettings mapSettings; - mapSettings.setCrs( project.crs() ); - mapSettings.setExtent( fullExtent ); - mapSettings.setLayers( {layerDtm, mLayerBuildings} ); - - mapSettings.setTransformContext( project.transformContext() ); - mapSettings.setPathResolver( project.pathResolver() ); - mapSettings.setMapThemeCollection( project.mapThemeCollection() ); - - QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator(); - demTerrain->setLayer( layerDtm ); - mapSettings.setTerrainGenerator( demTerrain ); - mapSettings.setTerrainVerticalScale( 3 ); - - QgsPointLightSettings defaultPointLight; - defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) ); - defaultPointLight.setConstantAttenuation( 0 ); - mapSettings.setLightSources( {defaultPointLight.clone() } ); - mapSettings.setOutputDpi( 92 ); - - // =========== creating Qgs3DMapScene - QPoint winSize = QPoint( 640, 480 ); // default window size - - QgsOffscreen3DEngine engine; - engine.setSize( QSize( winSize.x(), winSize.y() ) ); - Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); - engine.setRootEntity( scene ); - - // =========== set camera position - scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 ); - - // =========== activate debug depth map - mapSettings.setDebugDepthMapSettings( true, Qt::Corner::BottomRightCorner, 0.5 ); - - QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "debug_map_1", "debug_map_1", img, QString(), 100, QSize( 0, 0 ), 15 ); - - // =========== activate debug shadow map - mapSettings.setDebugShadowMapSettings( true, Qt::Corner::TopLeftCorner, 0.5 ); - - img = Qgs3DUtils::captureSceneImage( engine, scene ); - QGSVERIFYIMAGECHECK( "debug_map_2", "debug_map_2", img, QString(), 100, QSize( 0, 0 ), 15 ); - - delete scene; - mapSettings.setLayers( {} ); - demTerrain->deleteLater(); -} - -void TestQgs3DRendering::do3DSceneExport( int zoomLevelsCount, int expectedObjectCount, int maxFaceCount, Qgs3DMapScene *scene, QgsPolygon3DSymbol *symbol3d, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine ) -{ - QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d->clone() ); - QgsVectorLayer3DTilingSettings tilingSettings; - tilingSettings.setZoomLevelsCount( zoomLevelsCount ); - tilingSettings.setShowBoundingBoxes( true ); - renderer3d->setTilingSettings( tilingSettings ); - layerPoly->setRenderer3D( renderer3d ); - - Qgs3DUtils::captureSceneImage( *engine, scene ); - - Qgs3DSceneExporter exporter; - exporter.setTerrainResolution( 128 ); - exporter.setSmoothEdges( false ); - exporter.setExportNormals( true ); - exporter.setExportTextures( false ); - exporter.setTerrainTextureResolution( 512 ); - exporter.setScale( 1.0 ); - - QVERIFY( exporter.parseVectorLayerEntity( scene->layerEntity( layerPoly ), layerPoly ) ); - exporter.save( QString( "test3DSceneExporter-%1" ).arg( zoomLevelsCount ), "/tmp/" ); - - int sum = 0; - for ( auto o : exporter.mObjects ) - { - QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() ); - sum += o->indexes().size(); - } - - QCOMPARE( sum, maxFaceCount ); - QCOMPARE( exporter.mExportedFeatureIds.size(), 3 ); - QCOMPARE( exporter.mObjects.size(), expectedObjectCount ); -} - -void TestQgs3DRendering::test3DSceneExporter() -{ - // ============================================= - // =========== creating Qgs3DMapSettings - QgsVectorLayer *layerPoly = new QgsVectorLayer( testDataPath( "/3d/polygons.gpkg.gz" ), "polygons", "ogr" ); - QVERIFY( layerPoly->isValid() ); - - const QgsRectangle fullExtent = layerPoly->extent(); - - // =========== create polygon 3D renderer - QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; - symbol3d->setExtrusionHeight( 10.f ); - - QgsPhongMaterialSettings materialSettings; - materialSettings.setAmbient( Qt::lightGray ); - symbol3d->setMaterialSettings( materialSettings.clone() ); - - QgsProject project; - project.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 3857 ) ); - project.addMapLayer( layerPoly ); - - // =========== create scene 3D settings - Qgs3DMapSettings mapSettings; - mapSettings.setCrs( project.crs() ); - mapSettings.setExtent( fullExtent ); - mapSettings.setLayers( {layerPoly} ); - mapSettings.setTerrainGenerator( new QgsFlatTerrainGenerator ); - - mapSettings.setTransformContext( project.transformContext() ); - mapSettings.setPathResolver( project.pathResolver() ); - mapSettings.setMapThemeCollection( project.mapThemeCollection() ); - mapSettings.setOutputDpi( 92 ); - - // =========== creating Qgs3DMapScene - QPoint winSize = QPoint( 640, 480 ); // default window size - - QgsOffscreen3DEngine engine; - engine.setSize( QSize( winSize.x(), winSize.y() ) ); - Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); - - scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 7000, 20.0, -10.0 ); - engine.setRootEntity( scene ); - - // =========== check with 1 big tile ==> 1 exported object - do3DSceneExport( 1, 1, 165, scene, symbol3d, layerPoly, &engine ); - // =========== check with 4 tiles ==> 3 exported objects - do3DSceneExport( 2, 1, 165, scene, symbol3d, layerPoly, &engine ); - // =========== check with 9 tiles ==> 3 exported objects - do3DSceneExport( 3, 3, 165, scene, symbol3d, layerPoly, &engine ); - // =========== check with 16 tiles ==> 3 exported objects - do3DSceneExport( 4, 3, 165, scene, symbol3d, layerPoly, &engine ); - // =========== check with 25 tiles ==> 3 exported objects - do3DSceneExport( 5, 3, 165, scene, symbol3d, layerPoly, &engine ); - - delete scene; - mapSettings.setLayers( {} ); -} +//void TestQgs3DRendering::testAmbientOcclusion() +//{ +// // ============================================= +// // =========== creating Qgs3DMapSettings +// QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); +// QVERIFY( layerDtm->isValid() ); + +// const QgsRectangle fullExtent = layerDtm->extent(); + +// QgsProject project; +// project.addMapLayer( layerDtm ); + +// Qgs3DMapSettings mapSettings; +// mapSettings.setCrs( project.crs() ); +// mapSettings.setExtent( fullExtent ); +// mapSettings.setLayers( {layerDtm, mLayerBuildings} ); + +// mapSettings.setTransformContext( project.transformContext() ); +// mapSettings.setPathResolver( project.pathResolver() ); +// mapSettings.setMapThemeCollection( project.mapThemeCollection() ); + +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; +// demTerrain->setLayer( layerDtm ); +// mapSettings.setTerrainGenerator( demTerrain ); +// mapSettings.setTerrainVerticalScale( 3 ); + +// QgsPointLightSettings defaultPointLight; +// defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) ); +// defaultPointLight.setConstantAttenuation( 0 ); +// mapSettings.setLightSources( {defaultPointLight.clone() } ); +// mapSettings.setOutputDpi( 92 ); + +// // =========== creating Qgs3DMapScene +// QPoint winSize = QPoint( 640, 480 ); // default window size + +// QgsOffscreen3DEngine engine; +// engine.setSize( QSize( winSize.x(), winSize.y() ) ); +// Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); +// engine.setRootEntity( scene ); + +// // =========== set camera position +// scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 400, 50, 10 ); + +// QgsAmbientOcclusionSettings aoSettings = mapSettings.ambientOcclusionSettings(); +// aoSettings.setEnabled( false ); +// mapSettings.setAmbientOcclusionSettings( aoSettings ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "ambient_occlusion_1", "ambient_occlusion_1", img, QString(), 40, QSize( 0, 0 ), 15 ); + +// aoSettings.setEnabled( true ); +// mapSettings.setAmbientOcclusionSettings( aoSettings ); + +// img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "ambient_occlusion_2", "ambient_occlusion_2", img, QString(), 40, QSize( 0, 0 ), 15 ); + +// delete scene; +// mapSettings.setLayers( {} ); +// demTerrain->deleteLater(); +//} + +//void TestQgs3DRendering::testDepthBuffer() +//{ +// // ============================================= +// // =========== creating Qgs3DMapSettings +// QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); +// QVERIFY( layerDtm->isValid() ); +// QgsProject project; +// project.addMapLayer( layerDtm ); + +// const QgsRectangle fullExtent = layerDtm->extent(); + +// Qgs3DMapSettings mapSettings; +// mapSettings.setCrs( project.crs() ); +// mapSettings.setExtent( fullExtent ); +// mapSettings.setLayers( {layerDtm} ); + +// mapSettings.setTransformContext( project.transformContext() ); +// mapSettings.setPathResolver( project.pathResolver() ); +// mapSettings.setMapThemeCollection( project.mapThemeCollection() ); + +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator; +// demTerrain->setLayer( layerDtm ); +// mapSettings.setTerrainGenerator( demTerrain ); +// mapSettings.setTerrainVerticalScale( 3 ); + +// QgsPointLightSettings defaultPointLight; +// defaultPointLight.setPosition( QgsVector3D( 0, 1000, 0 ) ); +// defaultPointLight.setConstantAttenuation( 0 ); +// mapSettings.setLightSources( {defaultPointLight.clone() } ); +// mapSettings.setOutputDpi( 92 ); + +// // =========== creating Qgs3DMapScene +// QPoint winSize = QPoint( 640, 480 ); // default window size +// QPoint midPos = winSize / 2; + +// QgsOffscreen3DEngine engine; +// engine.setSize( QSize( winSize.x(), winSize.y() ) ); +// Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); +// engine.setRootEntity( scene ); + +// // =========== set camera position +// scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 ); + +// // ============================================= +// // =========== TEST DEPTH +// QgsCameraController4Test *testCam = static_cast( scene->cameraController() ); + +// // retrieve 3D depth image +// QImage depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); +// QImage grayImage = convertDepthImageToGray16Image( depthImage ); +// QGSVERIFYIMAGECHECK( "depth_retrieve_image", "depth_retrieve_image", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); + +// // =========== TEST WHEEL ZOOM +// QVector3D startPos = scene->cameraController()->camera()->position(); +// // set cameraController mouse pos to middle screen +// QMouseEvent mouseEvent( QEvent::MouseButtonPress, QPointF( midPos.x(), midPos.y() ), +// Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier ); +// testCam->superOnMousePressed( new Qt3DInput::QMouseEvent( mouseEvent ) ); + +// // Check first wheel action +// QWheelEvent wheelEvent( midPos, midPos, QPoint(), QPoint( 0, 120 ), +// Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, +// false, Qt::MouseEventSynthesizedByApplication ); +// testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent ) ); +// QCOMPARE( testCam->cumulatedWheelY(), wheelEvent.angleDelta().y() * 5 ); +// QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + +// depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); +// grayImage = convertDepthImageToGray16Image( depthImage ); +// QGSVERIFYIMAGECHECK( "depth_wheel_action_1", "depth_wheel_action_1", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); + +// scene->cameraController()->depthBufferCaptured( depthImage ); + +// QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.7, 224.6, 185.5 ), 1.0 ); +// QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.7, 224.6, 185.5 ), 1.0 ); +// QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 955.4, 1.0 ); +// QCOMPARE( testCam->cumulatedWheelY(), 0 ); + +// // Checking second wheel action +// QWheelEvent wheelEvent2( midPos, midPos, QPoint(), QPoint( 0, 120 ), +// Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, +// false, Qt::MouseEventSynthesizedByApplication ); +// testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent2 ) ); +// QCOMPARE( testCam->cumulatedWheelY(), wheelEvent2.angleDelta().y() * 5 ); +// QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + +// depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); +// grayImage = convertDepthImageToGray16Image( depthImage ); +// QGSVERIFYIMAGECHECK( "depth_wheel_action_2", "depth_wheel_action_2", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); + +// scene->cameraController()->depthBufferCaptured( depthImage ); + +// QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.5, 223.5, 184.7 ), 1.0 ); +// QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.5, 223.5, 184.7 ), 1.0 ); +// QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 757.4, 1.0 ); +// QCOMPARE( testCam->cumulatedWheelY(), 0 ); + +// // Checking third wheel action +// QWheelEvent wheelEvent3( midPos, midPos, QPoint( ), QPoint( 0, 480 ), +// Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, +// false, Qt::MouseEventSynthesizedByApplication ); +// testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent3 ) ); +// QCOMPARE( testCam->cumulatedWheelY(), wheelEvent3.angleDelta().y() * 5 ); +// QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + +// depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); +// grayImage = convertDepthImageToGray16Image( depthImage ); +// QGSVERIFYIMAGECHECK( "depth_wheel_action_3", "depth_wheel_action_3", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); + +// scene->cameraController()->depthBufferCaptured( depthImage ); + +// QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.4, 222.8, 184.1 ), 1.0 ); +// QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.4, 222.8, 184.1 ), 1.0 ); +// QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 126.4, 0.1 ); +// QCOMPARE( testCam->cumulatedWheelY(), 0 ); + +// // Checking fourth wheel action +// QWheelEvent wheelEvent4( midPos, midPos, QPoint(), QPoint( 0, 120 ), +// Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, +// false, Qt::MouseEventSynthesizedByApplication ); +// testCam->superOnWheel( new Qt3DInput::QWheelEvent( wheelEvent4 ) ); +// QCOMPARE( testCam->cumulatedWheelY(), wheelEvent4.angleDelta().y() * 5 ); +// QCOMPARE( testCam->cameraBefore()->viewCenter(), testCam->cameraPose()->centerPoint().toVector3D() ); + +// depthImage = Qgs3DUtils::captureSceneDepthBuffer( engine, scene ); +// grayImage = convertDepthImageToGray16Image( depthImage ); +// QGSVERIFYIMAGECHECK( "depth_wheel_action_4", "depth_wheel_action_4", grayImage, QString(), 550, QSize( 0, 0 ), 2 ); + +// scene->cameraController()->depthBufferCaptured( depthImage ); + +// QGSCOMPARENEARVECTOR3D( testCam->zoomPoint(), QVector3D( -32.3, 221.7, 183.2 ), 1.0 ); +// QGSCOMPARENEARVECTOR3D( testCam->cameraPose()->centerPoint(), QVector3D( -32.3, 221.7, 183.2 ), 1.0 ); +// QGSCOMPARENEAR( testCam->cameraPose()->distanceFromCenterPoint(), 101.1, 0.1 ); +// QCOMPARE( testCam->cumulatedWheelY(), 0 ); + +// // Checking camera position +// QVector3D diff = scene->cameraController()->camera()->position() - startPos; +// QGSCOMPARENEARVECTOR3D( diff, QVector3D( 125, -850, -700 ), 3.0 ); + +// delete scene; +// mapSettings.setLayers( {} ); +// demTerrain->deleteLater(); +//} + +//void TestQgs3DRendering::testDebugMap() +//{ +// // ============================================= +// // =========== creating Qgs3DMapSettings +// QgsRasterLayer *layerDtm = new QgsRasterLayer( testDataPath( "/3d/dtm.tif" ), "dtm", "gdal" ); +// QVERIFY( layerDtm->isValid() ); + +// const QgsRectangle fullExtent = layerDtm->extent(); + +// QgsProject project; +// project.addMapLayer( layerDtm ); + +// Qgs3DMapSettings mapSettings; +// mapSettings.setCrs( project.crs() ); +// mapSettings.setExtent( fullExtent ); +// mapSettings.setLayers( {layerDtm, mLayerBuildings} ); + +// mapSettings.setTransformContext( project.transformContext() ); +// mapSettings.setPathResolver( project.pathResolver() ); +// mapSettings.setMapThemeCollection( project.mapThemeCollection() ); + +// QgsDemTerrainGenerator *demTerrain = new QgsDemTerrainGenerator(); +// demTerrain->setLayer( layerDtm ); +// mapSettings.setTerrainGenerator( demTerrain ); +// mapSettings.setTerrainVerticalScale( 3 ); + +// QgsPointLightSettings defaultPointLight; +// defaultPointLight.setPosition( QgsVector3D( 0, 400, 0 ) ); +// defaultPointLight.setConstantAttenuation( 0 ); +// mapSettings.setLightSources( {defaultPointLight.clone() } ); +// mapSettings.setOutputDpi( 92 ); + +// // =========== creating Qgs3DMapScene +// QPoint winSize = QPoint( 640, 480 ); // default window size + +// QgsOffscreen3DEngine engine; +// engine.setSize( QSize( winSize.x(), winSize.y() ) ); +// Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); +// engine.setRootEntity( scene ); + +// // =========== set camera position +// scene->cameraController()->setLookingAtPoint( QVector3D( 0, 0, 0 ), 1500, 40.0, -10.0 ); + +// // =========== activate debug depth map +// mapSettings.setDebugDepthMapSettings( true, Qt::Corner::BottomRightCorner, 0.5 ); + +// QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "debug_map_1", "debug_map_1", img, QString(), 100, QSize( 0, 0 ), 15 ); + +// // =========== activate debug shadow map +// mapSettings.setDebugShadowMapSettings( true, Qt::Corner::TopLeftCorner, 0.5 ); + +// img = Qgs3DUtils::captureSceneImage( engine, scene ); +// QGSVERIFYIMAGECHECK( "debug_map_2", "debug_map_2", img, QString(), 100, QSize( 0, 0 ), 15 ); + +// delete scene; +// mapSettings.setLayers( {} ); +// demTerrain->deleteLater(); +//} + +//void TestQgs3DRendering::do3DSceneExport( int zoomLevelsCount, int expectedObjectCount, int maxFaceCount, Qgs3DMapScene *scene, QgsPolygon3DSymbol *symbol3d, QgsVectorLayer *layerPoly, QgsOffscreen3DEngine *engine ) +//{ +// QgsVectorLayer3DRenderer *renderer3d = new QgsVectorLayer3DRenderer( symbol3d->clone() ); +// QgsVectorLayer3DTilingSettings tilingSettings; +// tilingSettings.setZoomLevelsCount( zoomLevelsCount ); +// tilingSettings.setShowBoundingBoxes( true ); +// renderer3d->setTilingSettings( tilingSettings ); +// layerPoly->setRenderer3D( renderer3d ); + +// Qgs3DUtils::captureSceneImage( *engine, scene ); + +// Qgs3DSceneExporter exporter; +// exporter.setTerrainResolution( 128 ); +// exporter.setSmoothEdges( false ); +// exporter.setExportNormals( true ); +// exporter.setExportTextures( false ); +// exporter.setTerrainTextureResolution( 512 ); +// exporter.setScale( 1.0 ); + +// QVERIFY( exporter.parseVectorLayerEntity( scene->layerEntity( layerPoly ), layerPoly ) ); +// exporter.save( QString( "test3DSceneExporter-%1" ).arg( zoomLevelsCount ), "/tmp/" ); + +// int sum = 0; +// for ( auto o : exporter.mObjects ) +// { +// QVERIFY( o->indexes().size() * 3 <= o->vertexPosition().size() ); +// sum += o->indexes().size(); +// } + +// QCOMPARE( sum, maxFaceCount ); +// QCOMPARE( exporter.mExportedFeatureIds.size(), 3 ); +// QCOMPARE( exporter.mObjects.size(), expectedObjectCount ); +//} + +//void TestQgs3DRendering::test3DSceneExporter() +//{ +// // ============================================= +// // =========== creating Qgs3DMapSettings +// QgsVectorLayer *layerPoly = new QgsVectorLayer( testDataPath( "/3d/polygons.gpkg.gz" ), "polygons", "ogr" ); +// QVERIFY( layerPoly->isValid() ); + +// const QgsRectangle fullExtent = layerPoly->extent(); + +// // =========== create polygon 3D renderer +// QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; +// symbol3d->setExtrusionHeight( 10.f ); + +// QgsPhongMaterialSettings materialSettings; +// materialSettings.setAmbient( Qt::lightGray ); +// symbol3d->setMaterialSettings( materialSettings.clone() ); + +// QgsProject project; +// project.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 3857 ) ); +// project.addMapLayer( layerPoly ); + +// // =========== create scene 3D settings +// Qgs3DMapSettings mapSettings; +// mapSettings.setCrs( project.crs() ); +// mapSettings.setExtent( fullExtent ); +// mapSettings.setLayers( {layerPoly} ); +// mapSettings.setTerrainGenerator( new QgsFlatTerrainGenerator ); + +// mapSettings.setTransformContext( project.transformContext() ); +// mapSettings.setPathResolver( project.pathResolver() ); +// mapSettings.setMapThemeCollection( project.mapThemeCollection() ); +// mapSettings.setOutputDpi( 92 ); + +// // =========== creating Qgs3DMapScene +// QPoint winSize = QPoint( 640, 480 ); // default window size + +// QgsOffscreen3DEngine engine; +// engine.setSize( QSize( winSize.x(), winSize.y() ) ); +// Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); + +// scene->cameraController()->setLookingAtPoint( QgsVector3D( 0, 0, 0 ), 7000, 20.0, -10.0 ); +// engine.setRootEntity( scene ); + +// // =========== check with 1 big tile ==> 1 exported object +// do3DSceneExport( 1, 1, 165, scene, symbol3d, layerPoly, &engine ); +// // =========== check with 4 tiles ==> 3 exported objects +// do3DSceneExport( 2, 1, 165, scene, symbol3d, layerPoly, &engine ); +// // =========== check with 9 tiles ==> 3 exported objects +// do3DSceneExport( 3, 3, 165, scene, symbol3d, layerPoly, &engine ); +// // =========== check with 16 tiles ==> 3 exported objects +// do3DSceneExport( 4, 3, 165, scene, symbol3d, layerPoly, &engine ); +// // =========== check with 25 tiles ==> 3 exported objects +// do3DSceneExport( 5, 3, 165, scene, symbol3d, layerPoly, &engine ); + +// delete scene; +// mapSettings.setLayers( {} ); +//} QGSTEST_MAIN( TestQgs3DRendering )