Template stripped to the overlay platform (examples, demo UI and Frame test package removed), renamed to FrameAssistant, and working rules for the project recorded in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.1 KiB
C++
119 lines
4.1 KiB
C++
#include <QQmlEngine>
|
|
#include <QQuickWindow>
|
|
#include <QtTest>
|
|
|
|
#include "render/OffscreenQuickRenderer.h"
|
|
#include "render/RenderBackend.h"
|
|
|
|
// Renders a known scene through the real offscreen pipeline (backend +
|
|
// QQuickRenderControl) and checks the texture content via readback. The backend
|
|
// comes from VRDASH_TEST_API (opengl / vulkan): the graphics API is
|
|
// process-global, so CTest runs one process each.
|
|
class RenderTest : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void initTestCase() {
|
|
GraphicsApi api = GraphicsApi::OpenGL;
|
|
const QString apiName =
|
|
qEnvironmentVariable("VRDASH_TEST_API", QStringLiteral("opengl"));
|
|
QVERIFY2(parseGraphicsApi(apiName, &api), qPrintable(apiName));
|
|
m_backend = createRenderBackend(api);
|
|
if (!m_backend)
|
|
QSKIP(qPrintable(apiName + QStringLiteral(" not compiled in")));
|
|
QQuickWindow::setGraphicsApi(m_backend->graphicsApi());
|
|
QString error;
|
|
if (!m_backend->initialize({}, &error))
|
|
QSKIP(qPrintable(
|
|
QStringLiteral("No %1 context: %2").arg(m_backend->name(), error)));
|
|
}
|
|
|
|
void rendersSceneUpright() {
|
|
QQmlEngine engine;
|
|
const QSize size(64, 32);
|
|
OffscreenQuickRenderer renderer(*m_backend, size);
|
|
QString error;
|
|
QVERIFY2(renderer.initialize(engine, "RenderTest", "TwoTone", &error),
|
|
qPrintable(error));
|
|
|
|
int sinkCalls = 0;
|
|
vr::Texture_t submitted{};
|
|
renderer.setFrameSink(
|
|
[&](const vr::Texture_t& texture, const vr::VRTextureBounds_t&) {
|
|
++sinkCalls;
|
|
submitted = texture;
|
|
});
|
|
renderer.renderNow();
|
|
|
|
QCOMPARE(sinkCalls, 1);
|
|
QVERIFY(submitted.handle != nullptr);
|
|
const auto expectedType =
|
|
m_backend->graphicsApi() == QSGRendererInterface::Vulkan
|
|
? vr::TextureType_Vulkan
|
|
: vr::TextureType_OpenGL;
|
|
QCOMPARE(submitted.eType, expectedType);
|
|
if (submitted.eType == vr::TextureType_Vulkan) {
|
|
const auto* vk =
|
|
static_cast<const vr::VRVulkanTextureData_t*>(submitted.handle);
|
|
QVERIFY(vk->m_nImage != 0 && vk->m_pDevice && vk->m_pQueue &&
|
|
vk->m_pInstance);
|
|
QCOMPARE(vk->m_nWidth, uint32_t(size.width()));
|
|
}
|
|
QVERIFY(!m_backend->deviceName().isEmpty());
|
|
|
|
const QImage image = m_backend->readback();
|
|
QCOMPARE(image.size(), size);
|
|
QCOMPARE(image.pixelColor(size.width() / 2, 4), QColor(255, 0, 0));
|
|
QCOMPARE(image.pixelColor(size.width() / 2, size.height() - 4),
|
|
QColor(0, 0, 255));
|
|
}
|
|
|
|
// Several frames in a row exercise the per-frame state handling (e.g.
|
|
// Vulkan layout transitions between Qt's frames and the SteamVR
|
|
// hand-off).
|
|
void rendersRepeatedly() {
|
|
QQmlEngine engine;
|
|
OffscreenQuickRenderer renderer(*m_backend, {32, 32});
|
|
QString error;
|
|
QVERIFY2(renderer.initialize(engine, "RenderTest", "TwoTone", &error),
|
|
qPrintable(error));
|
|
for (int i = 0; i < 5; ++i) renderer.renderNow();
|
|
QCOMPARE(m_backend->readback().pixelColor(16, 2), QColor(255, 0, 0));
|
|
}
|
|
|
|
// An animated scene must render at the fps cap even when each frame
|
|
// takes noticeable time: pacing is measured from frame start, not added
|
|
// after the frame.
|
|
void pacingHoldsFrameRateWithSlowFrames() {
|
|
QQmlEngine engine;
|
|
OffscreenQuickRenderer renderer(*m_backend, {32, 32});
|
|
QString error;
|
|
QVERIFY2(renderer.initialize(engine, "RenderTest", "Spinner", &error),
|
|
qPrintable(error));
|
|
int frames = 0;
|
|
renderer.setFrameSink(
|
|
[&](const vr::Texture_t&, const vr::VRTextureBounds_t&) {
|
|
++frames;
|
|
QThread::usleep(3000); // e.g. a slow SteamVR hand-off
|
|
});
|
|
renderer.setMaxFps(60);
|
|
renderer.setActive(true);
|
|
QTest::qWait(300); // warm-up
|
|
frames = 0;
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
QTest::qWait(2000);
|
|
const double fps = frames * 1000.0 / timer.elapsed();
|
|
renderer.setActive(false);
|
|
qInfo("measured %.1f fps", fps);
|
|
QVERIFY2(fps > 57.0 && fps < 63.0, qPrintable(QString::number(fps)));
|
|
}
|
|
|
|
void cleanupTestCase() { m_backend.reset(); }
|
|
|
|
private:
|
|
std::unique_ptr<RenderBackend> m_backend;
|
|
};
|
|
|
|
QTEST_MAIN(RenderTest)
|
|
#include "tst_render.moc"
|