- build_scripts/steamframe/: Debian trixie (Qt 6.8) containers for the aarch64 cross build, native build and tests; toolchain; scripts to build, test (Vulkan validation layers), package (system Qt, ~3 MB) and deploy over SSH. Adapted from the vrdashboard template. - test/render/: render test for the OpenGL and Vulkan backends, driven like OverlayController (option OVRAS_BUILD_TESTS). - RPATH only $ORIGIN, without the build machine's link directories. - Ignore dist/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
184 lines
6.3 KiB
C++
184 lines
6.3 KiB
C++
// Render test for the render backends (src/render/), adapted from the
|
|
// vrdashboard template's tst_render. It drives a backend and
|
|
// QQuickRenderControl in the same order as OverlayController::SetWidget() and
|
|
// renderOverlay(), then checks the texture handed to SteamVR and its content
|
|
// (via readback).
|
|
//
|
|
// The backend comes from OVRAS_TEST_API (opengl / vulkan): the graphics API is
|
|
// process-wide, so CTest runs one process per API. Without a GPU context the
|
|
// test skips itself. With OVRAS_VULKAN_VALIDATION=1 the Vulkan validation
|
|
// layers are enabled (build_scripts/steamframe/container-test.sh).
|
|
|
|
#include <QQmlComponent>
|
|
#include <QQmlEngine>
|
|
#include <QQuickItem>
|
|
#include <QQuickRenderControl>
|
|
#include <QQuickWindow>
|
|
#include <QtTest>
|
|
#include <memory>
|
|
|
|
#include "render/RenderBackend.h"
|
|
|
|
class RenderTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
void initTestCase()
|
|
{
|
|
GraphicsApi api = GraphicsApi::OpenGL;
|
|
const QString apiName
|
|
= qEnvironmentVariable( "OVRAS_TEST_API", "opengl" );
|
|
QVERIFY2( parseGraphicsApi( apiName, &api ), qPrintable( apiName ) );
|
|
m_backend = createRenderBackend( api );
|
|
if ( !m_backend )
|
|
{
|
|
QSKIP( qPrintable( apiName + " not compiled in" ) );
|
|
}
|
|
// Before any QQuickWindow exists, as in main().
|
|
QQuickWindow::setGraphicsApi( m_backend->graphicsApi() );
|
|
QString error;
|
|
// No SteamVR here: empty interop, the backend picks defaults.
|
|
if ( !m_backend->initialize( {}, &error ) )
|
|
{
|
|
QSKIP( qPrintable( QString( "No %1 context: %2" )
|
|
.arg( m_backend->name(), error ) ) );
|
|
}
|
|
}
|
|
|
|
// One frame of a known scene (top red, bottom blue): texture type and
|
|
// description for SteamVR, content and orientation.
|
|
void rendersSceneUpright()
|
|
{
|
|
const QSize size( 64, 32 );
|
|
Scene scene( *m_backend, size );
|
|
QVERIFY2( scene.error.isEmpty(), qPrintable( scene.error ) );
|
|
|
|
const vr::Texture_t texture = scene.renderFrame();
|
|
QVERIFY( texture.handle != nullptr );
|
|
const auto expectedType
|
|
= m_backend->graphicsApi() == QSGRendererInterface::Vulkan
|
|
? vr::TextureType_Vulkan
|
|
: vr::TextureType_OpenGL;
|
|
QCOMPARE( texture.eType, expectedType );
|
|
if ( texture.eType == vr::TextureType_Vulkan )
|
|
{
|
|
const auto* vk = static_cast<const vr::VRVulkanTextureData_t*>(
|
|
texture.handle );
|
|
QVERIFY( vk->m_nImage != 0 && vk->m_pDevice && vk->m_pQueue
|
|
&& vk->m_pInstance );
|
|
QCOMPARE( vk->m_nWidth, static_cast<uint32_t>( size.width() ) );
|
|
// VK_FORMAT_R8G8B8A8_UNORM, the format SteamVR gets told.
|
|
QCOMPARE( vk->m_nFormat, 37u );
|
|
}
|
|
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.
|
|
// the Vulkan layout transitions between Qt's frames and the SteamVR
|
|
// hand-off).
|
|
void rendersRepeatedly()
|
|
{
|
|
Scene scene( *m_backend, { 32, 32 } );
|
|
QVERIFY2( scene.error.isEmpty(), qPrintable( scene.error ) );
|
|
for ( int i = 0; i < 5; ++i )
|
|
{
|
|
scene.renderFrame();
|
|
}
|
|
QCOMPARE( m_backend->readback().pixelColor( 16, 2 ),
|
|
QColor( 255, 0, 0 ) );
|
|
}
|
|
|
|
void cleanupTestCase()
|
|
{
|
|
m_backend.reset();
|
|
}
|
|
|
|
private:
|
|
// The TwoTone scene in an offscreen window, set up like
|
|
// OverlayController::SetWidget(). Tears down like
|
|
// OverlayController::Shutdown() (minus VR_Shutdown).
|
|
struct Scene
|
|
{
|
|
Scene( RenderBackend& backend, QSize size ) : backend( backend )
|
|
{
|
|
window = std::make_unique<QQuickWindow>( &renderControl );
|
|
window->setGeometry( 0, 0, size.width(), size.height() );
|
|
QQmlComponent component( &engine );
|
|
component.loadFromModule( "RenderTest", "TwoTone" );
|
|
root.reset( qobject_cast<QQuickItem*>( component.create() ) );
|
|
if ( !root )
|
|
{
|
|
error = component.errorString();
|
|
return;
|
|
}
|
|
root->setSize( size );
|
|
root->setParentItem( window->contentItem() );
|
|
|
|
backend.configureWindow( window.get() );
|
|
if ( !backend.makeCurrent() || !renderControl.initialize() )
|
|
{
|
|
error = "QQuickRenderControl::initialize failed";
|
|
return;
|
|
}
|
|
if ( !backend.attachWindow( window.get(), &error ) )
|
|
{
|
|
return;
|
|
}
|
|
const QQuickRenderTarget target
|
|
= backend.createRenderTarget( size );
|
|
if ( target.isNull() )
|
|
{
|
|
error = "createRenderTarget failed";
|
|
return;
|
|
}
|
|
window->setRenderTarget( target );
|
|
}
|
|
|
|
~Scene()
|
|
{
|
|
backend.makeCurrent();
|
|
root.reset();
|
|
backend.releaseRenderTarget();
|
|
backend.detachWindow();
|
|
window.reset();
|
|
}
|
|
|
|
// One frame as in OverlayController::renderOverlay(); returns what
|
|
// would be handed to IVROverlay::SetOverlayTexture.
|
|
vr::Texture_t renderFrame()
|
|
{
|
|
backend.makeCurrent();
|
|
backend.beginFrame();
|
|
renderControl.polishItems();
|
|
renderControl.beginFrame();
|
|
renderControl.sync();
|
|
renderControl.render();
|
|
renderControl.endFrame();
|
|
backend.finishFrame();
|
|
return backend.vrTexture();
|
|
}
|
|
|
|
RenderBackend& backend;
|
|
QQmlEngine engine;
|
|
// Destroyed after the window (declared before it): the window
|
|
// belongs to this render control.
|
|
QQuickRenderControl renderControl;
|
|
std::unique_ptr<QQuickWindow> window;
|
|
std::unique_ptr<QQuickItem> root;
|
|
QString error;
|
|
};
|
|
|
|
std::unique_ptr<RenderBackend> m_backend;
|
|
};
|
|
|
|
QTEST_MAIN( RenderTest )
|
|
#include "tst_render.moc"
|