Revision control
Copy as Markdown
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest>
#include <QtQuick3D/private/qquick3dobject_p.h>
#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
#include <QtQuick3DRuntimeRender/private/qssglayerrenderdata_p.h>
#include <QtQuick3DUtils/private/qssgbounds3_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderclippingfrustum_p.h>
#include <QtGui/qquaternion.h>
class BenchFrustumCulling : public QObject
{
Q_OBJECT
public:
BenchFrustumCulling();
~BenchFrustumCulling();
private slots:
void initTestCase();
void cleanupTestCase();
void test_frustumCulling();
void bench_inline();
private:
struct ObjectData
{
QVector3D worldCenterPt;
QMatrix4x4 globalTransform;
QSSGBounds3 bounds;
};
static ObjectData createRenderableData(QVector3D position, QQuaternion rotation, QSSGBounds3 bounds)
{
auto transform = QSSGRenderNode::calculateTransformMatrix(position, {1.0f, 1.0f, 1.0f}, {}, rotation);
return ObjectData{ {}, transform, bounds};
}
static void populateRenderableList(const QList<ObjectData> &objects, QList<QSSGRenderableObject *> &renderableObjects)
{
renderableObjects.clear();
renderableObjects.reserve(objects.size());
for (const auto &od : std::as_const(objects)) {
QSSGBounds3 globalBounds = od.bounds;
globalBounds.transform(od.globalTransform);
// NOTE: We pass in the global bounds, as that's the only thing we care about here.
// In practice we never create the base type QSSGRenderableObject in the engine.
renderableObjects.push_back(new QSSGRenderableObject{ QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset, QSSGRenderableObjectFlags(), od.worldCenterPt, globalBounds, 0.0f });
}
}
QQuick3DPerspectiveCamera camera;
QScopedPointer<QSSGRenderCamera> cameraNode;
QSSGClippingFrustum clipFrustum;
};
BenchFrustumCulling::BenchFrustumCulling()
{
}
BenchFrustumCulling::~BenchFrustumCulling()
{
}
void BenchFrustumCulling::initTestCase()
{
const QRect viewport = { 0, 0, 100, 100 };
camera.setPosition({0, 0, 100});
camera.setClipNear(95.0f);
camera.setClipFar(105.0f);
cameraNode.reset(static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::updateSpatialNode(&camera, nullptr)));
QSSGRenderCamera::calculateProjectionInternal(*cameraNode, viewport);
QMatrix4x4 viewProjectionMatrix = QMatrix4x4(Qt::Uninitialized);
QSSGClipPlane nearPlane;
{
auto &camera = *cameraNode;
camera.calculateViewProjectionMatrix(camera.localTransform, viewProjectionMatrix);
QMatrix3x3 theUpper33(camera.localTransform.normalMatrix());
QVector3D dir(QSSGUtils::mat33::transform(theUpper33, QVector3D(0, 0, -1)));
dir.normalize();
nearPlane.normal = dir;
QVector3D theGlobalPos = QSSGRenderNode::getGlobalPos(camera.localTransform) + camera.clipPlanes.clipNear() * dir;
nearPlane.d = -(QVector3D::dotProduct(dir, theGlobalPos));
}
// the near plane's bbox edges are calculated in the clipping frustum's
// constructor.
clipFrustum = QSSGClippingFrustum(viewProjectionMatrix, nearPlane);
}
void BenchFrustumCulling::cleanupTestCase()
{
}
void BenchFrustumCulling::test_frustumCulling()
{
}
constexpr auto Identity = QQuaternion{1, 0, 0, 0};
void BenchFrustumCulling::bench_inline()
{
// bounds 10x10x10 all in world coordinates
constexpr float widthAndHeight = 10.0f;
constexpr QSSGBounds3 bounds { { -widthAndHeight / 2.0f, -widthAndHeight / 2.0f, -widthAndHeight / 2.0f }, { widthAndHeight / 2.0f, widthAndHeight / 2.0f, widthAndHeight / 2.0f } };
// For simplicity we only do put "cullable" object in front or behind the frustum for now.
const float frustumNearBorder = camera.position().z() - camera.clipNear() + widthAndHeight;
const float frustumFarBorder = camera.position().z() - camera.clipFar() - widthAndHeight;
const quint32 objectCount = 10000;
const quint32 nonCulledItemCount = 3;
QSet<quint32> replaceIndexes;
while (replaceIndexes.size() < nonCulledItemCount)
replaceIndexes.insert(QRandomGenerator::global()->bounded(objectCount));
QList<ObjectData> objects;
objects.reserve(objectCount);
// Fill the list with object data that should be culled
for (quint32 i = 0, end = objectCount; i != end; ++i) {
if (i % 2)
objects.push_back(createRenderableData({0.0f, 0.0f, frustumNearBorder }, Identity, bounds));
else
objects.push_back(createRenderableData({0.0f, 0.0f, frustumFarBorder }, Identity, bounds));
}
// Insert items at random positions in the list that should not be culled
for (auto v : std::as_const(replaceIndexes))
objects.replace(v, createRenderableData({0.0f, 0.0f, 0.0f}, Identity, bounds));
QCOMPARE(objects.size(), objectCount);
QList<QSSGRenderableObject *> renderableObjects;
// List of renderables
populateRenderableList(objects, renderableObjects);
// Renderable object handle class...
QSSGRenderableObjectList renderables;
renderables.reserve(objects.size());
for (auto *ro : renderableObjects)
renderables.push_back({ ro, 0.0f, {} });
QVERIFY(!cameraNode->isDirty(QSSGRenderCamera::DirtyFlag::CameraDirty));
qsizetype ret;
QBENCHMARK {
ret = QSSGLayerRenderData::frustumCullingInline(clipFrustum, renderables);
}
QCOMPARE(ret, nonCulledItemCount);
}
QTEST_APPLESS_MAIN(BenchFrustumCulling)
#include "tst_benchfrustumculling.moc"