Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QQuickView>
#include <private/qquick3dviewport_p.h>
#include <ssg/qssgrendercontextcore.h>
#include <private/qssgrenderbuffermanager_p.h>
#include <private/qquick3dresourceloader_p.h>
#if QT_CONFIG(vulkan)
#include <QVulkanInstance>
#endif
#include <QThread>
#include "../shared/util.h"
static inline void renderNextFrame(QQuick3DTestOffscreenRenderer *renderer, bool *readCompleted, QRhiReadbackResult *readResult, QImage *result)
{
renderer->qmlEngine->collectGarbage();
QGuiApplication::processEvents();
renderer->renderControl->polishItems();
renderer->renderControl->beginFrame();
renderer->renderControl->sync();
renderer->renderControl->render();
renderer->enqueueReadback(readCompleted, readResult, result);
renderer->renderControl->endFrame();
}
class tst_BufferManager : public QQuick3DDataTest
{
Q_OBJECT
private slots:
void initTestCase() override;
void staticScene_data();
void staticScene();
void dynamicScene();
private:
bool initRenderer(QQuick3DTestOffscreenRenderer *renderer, const QString &filename);
#if QT_CONFIG(vulkan)
QVulkanInstance vulkanInstance;
#endif
};
void tst_BufferManager::initTestCase()
{
QQuick3DDataTest::initTestCase();
if (!initialized())
return;
#if QT_CONFIG(vulkan)
vulkanInstance.setLayers({ "VK_LAYER_LUNARG_standard_validation" });
vulkanInstance.create(); // may fail, which is fine is Vulkan is not used in the first place
#endif
}
void tst_BufferManager::staticScene_data()
{
QTest::addColumn<QString>("qmlSource");
QTest::addColumn<int>("pathTextureCount");
QTest::addColumn<int>("sgTextureCount");
QTest::addColumn<int>("customTextureCount");
QTest::addColumn<int>("meshCount");
QTest::addColumn<int>("customMeshCount");
QTest::newRow("empty scene") << "empty.qml" << 0 << 0 << 0 << 0 << 0;
// Path Meshes
QTest::newRow("unshared path meshes") << "pathedMeshesUnshared.qml" << 0 << 0 << 0 << 4 << 0;
QTest::newRow("shared path meshes") << "pathedMeshesShared.qml" << 0 << 0 << 0 << 4 << 0;
// Custom Meshes
QTest::newRow("unused Custom Geometry") << "customGeometryUnused.qml" << 0 << 0 << 0 << 0 << 0;
QTest::newRow("unshared Custom Geometry") << "customGeometryUnshared.qml" << 0 << 0 << 0 << 0 << 3;
QTest::newRow("shared Custom Geometry") << "customGeometryShared.qml" << 0 << 0 << 0 << 0 << 3;
// Path Textures
QTest::newRow("unused path textures") << "pathedTexturesUnused.qml" << 0 << 0 << 0 << 0 << 0;
QTest::newRow("unshared path textures") << "pathedTexturesUnshared.qml" << 5 << 0 << 0 << 1 << 0;
QTest::newRow("shared path textures") << "pathedTexturesShared.qml" << 3 << 0 << 0 << 1 << 0;
QTest::newRow("mipmodes path textures") << "pathedTexturesMipModes.qml" << 3 << 0 << 0 << 1 << 0;
// SG (Quick) Textures
QTest::newRow("unused SG textures") << "sgTexturesUnused.qml" << 0 << 0 << 0 << 0 << 0;
QTest::newRow("unshared SG textures") << "sgTexturesUnshared.qml" << 0 << 4 << 0 << 1 << 0;
QTest::newRow("shared SG textures") << "sgTexturesShared.qml" << 0 << 3 << 0 << 1 << 0;
// Custom Textures
QTest::newRow("unused Custom Textures") << "customTexturesUnused.qml" << 0 << 0 << 0 << 0 << 0;
QTest::newRow("unshared Custom Textures") << "customTexturesUnshared.qml" << 0 << 0 << 3 << 1 << 0;
QTest::newRow("unshared Custom Textures") << "customTexturesShared.qml" << 0 << 0 << 2 << 1 << 0;
// ResourceLoader
QTest::newRow("only ResourceLoader") << "resourceLoaderOnly.qml" << 3 << 1 << 1 << 4 << 3;
QTest::newRow("mixed ResourceLoader") << "resourceLoaderMixed.qml" << 5 << 1 << 2 << 5 << 4;
QTest::newRow("multiple ResourceLoader") << "resourceLoaderMultiple.qml" << 3 << 1 << 2 << 5 << 4;
}
void tst_BufferManager::staticScene()
{
QFETCH(QString, qmlSource);
QFETCH(int, pathTextureCount);
QFETCH(int, sgTextureCount);
QFETCH(int, customTextureCount);
QFETCH(int, meshCount);
QFETCH(int, customMeshCount);
QQuick3DTestOffscreenRenderer renderer;
QVERIFY(initRenderer(&renderer, qmlSource));
if (renderer.quickWindow->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) {
#ifdef Q_OS_MACOS
QSKIP("Skipping test due to sofware OpenGL renderer problems on macOS");
#endif
}
bool readCompleted = false;
QRhiReadbackResult readResult;
QImage result;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
const auto &context = QQuick3DSceneManager::getOrSetWindowAttachment(*renderer.quickWindow)->rci();
QVERIFY(context);
const auto &bufferManager = context->bufferManager();
QCOMPARE(bufferManager->getImageMap().size(), pathTextureCount);
QCOMPARE(bufferManager->getSGImageMap().size(), sgTextureCount);
QCOMPARE(bufferManager->getCustomTextureMap().size(), customTextureCount);
QCOMPARE(bufferManager->getMeshMap().size(), meshCount);
QCOMPARE(bufferManager->getCustomMeshMap().size(), customMeshCount);
}
void tst_BufferManager::dynamicScene()
{
QQuick3DTestOffscreenRenderer renderer;
QVERIFY(initRenderer(&renderer, QString("dynamic.qml")));
if (renderer.quickWindow->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) {
#ifdef Q_OS_MACOS
QSKIP("Skipping test due to sofware OpenGL renderer problems on macOS");
#endif
}
bool readCompleted = false;
QRhiReadbackResult readResult;
QImage result;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
const auto &context = QQuick3DSceneManager::getOrSetWindowAttachment(*renderer.quickWindow)->rci();
QVERIFY(context);
const auto &bufferManager = context->bufferManager();
// Check for the initial state (1 static path model)
QCOMPARE(bufferManager->getImageMap().size(), 0);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 0);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 0);
// Get the test controller object
const auto controller = renderer.rootItem->property("controller").value<QQuick3DNode*>();
QVERIFY(controller);
auto addModel = [controller](const QString &path) -> QQuick3DModel* {
QQuick3DModel *model = nullptr;
QMetaObject::invokeMethod(controller, "addModel", Q_RETURN_ARG(QQuick3DModel*, model), Q_ARG(QString, path));
return model;
};
auto addDynamicModel = [controller]() -> QQuick3DModel* {
QQuick3DModel *model = nullptr;
QMetaObject::invokeMethod(controller, "addDynamicModel", Q_RETURN_ARG(QQuick3DModel*, model));
return model;
};
auto addTexture = [controller](const QString &path) -> QQuick3DTexture* {
QQuick3DTexture *texture = nullptr;
QMetaObject::invokeMethod(controller, "addTexture", Q_RETURN_ARG(QQuick3DTexture*, texture), Q_ARG(QString, path));
return texture;
};
auto addDynamicTexture = [controller]() -> QQuick3DTexture* {
QQuick3DTexture *texture = nullptr;
QMetaObject::invokeMethod(controller, "addDynamicTexture", Q_RETURN_ARG(QQuick3DTexture*, texture));
return texture;
};
auto addQmlTexture = [controller]() -> QQuick3DModel* {
QQuick3DModel *model = nullptr;
QMetaObject::invokeMethod(controller, "addQmlTexture", Q_RETURN_ARG(QQuick3DModel*, model));
return model;
};
auto addQmlSharedTexture = [controller]() -> QQuick3DModel* {
QQuick3DModel *model = nullptr;
QMetaObject::invokeMethod(controller, "addQmlSharedTexture", Q_RETURN_ARG(QQuick3DModel*, model));
return model;
};
auto addResourceLoader = [controller]() -> QQuick3DResourceLoader* {
QQuick3DResourceLoader *loader = nullptr;
QMetaObject::invokeMethod(controller, "addResourceLoader", Q_RETURN_ARG(QQuick3DResourceLoader*, loader));
return loader;
};
// models (path)
// add another sphere (count should stay 1)
auto sphereModel = addModel("#Sphere");
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
// add a cube (count == 2)
QQuick3DModel *model = addModel("#Cube");
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getMeshMap().size(), 2);
// remove the cube model (count == 1)
QMetaObject::invokeMethod(controller, "removeModel", Qt::DirectConnection);
delete model;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeModel");
delete sphereModel;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
// models (customGeometry)
auto dynamicModel = addDynamicModel();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 1);
auto dynamicModel2 = addDynamicModel();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 2);
QMetaObject::invokeMethod(controller, "removeDynamicModel");
delete dynamicModel2;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeDynamicModel");
delete dynamicModel;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 0);
// textures (path)
QCOMPARE(bufferManager->getImageMap().size(), 0);
auto texture = addTexture("noise1.jpg");
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getImageMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeTexture");
delete texture;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getImageMap().size(), 0);
// textures (custom)
QCOMPARE(bufferManager->getCustomTextureMap().size(), 0);
auto dynamicTexture = addDynamicTexture();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeDynamicTexture");
delete dynamicTexture;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 0);
// textures (sg)
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
auto qmlTexture = addQmlTexture();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
auto qmlTexture2 = addQmlTexture();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 2);
QMetaObject::invokeMethod(controller, "removeQmlTexture");
delete qmlTexture2;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeQmlTexture");
delete qmlTexture;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
auto qmlSharedTexture = addQmlSharedTexture();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
auto qmlSharedTexture2 = addQmlSharedTexture();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeQmlSharedTexture");
delete qmlSharedTexture2;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
QMetaObject::invokeMethod(controller, "removeQmlSharedTexture");
delete qmlSharedTexture;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
// Make sure scene is in its initial state
QCOMPARE(bufferManager->getImageMap().size(), 0);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 0);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 0);
// Add a ResourceLoader
auto resourceLoader = addResourceLoader();
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getImageMap().size(), 3);
QCOMPARE(bufferManager->getSGImageMap().size(), 1);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 1);
QCOMPARE(bufferManager->getMeshMap().size(), 5);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 1);
// Remove the ResourceLoader
delete resourceLoader;
renderNextFrame(&renderer, &readCompleted, &readResult, &result);
QCOMPARE(bufferManager->getImageMap().size(), 0);
QCOMPARE(bufferManager->getSGImageMap().size(), 0);
QCOMPARE(bufferManager->getCustomTextureMap().size(), 0);
QCOMPARE(bufferManager->getMeshMap().size(), 1);
QCOMPARE(bufferManager->getCustomMeshMap().size(), 0);
}
bool tst_BufferManager::initRenderer(QQuick3DTestOffscreenRenderer *renderer, const QString &filename)
{
const bool initSuccess = renderer->init(testFileUrl(filename),
#if QT_CONFIG(vulkan)
&vulkanInstance
#else
nullptr
#endif
);
return initSuccess;
}
QTEST_MAIN(tst_BufferManager)
#include "tst_buffermanager.moc"