Initial FrameAssistant baseline from the vrdashboard template
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>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
#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"
|
||||
Reference in New Issue
Block a user