Add layout diagnosis: font metrics in the log, --snapshot option

On the Steam Frame, the fixed 1200x800 QML layout overflows: about the
bottom quarter is cut off and the navigation icons overlap their text.
Forcing 96 DPI and switching the Quick Controls style didn't help, so the
cause has to be measured rather than guessed.

- SetWidget() logs the application font (family, pt, px), what 20 pt
  (MyText) is in px, the screen's DPI and the device pixel ratio.
- --snapshot <file> writes the rendered overlay (RenderBackend::readback)
  as PNG at most once per second while it is visible. A time interval,
  because OVRAS only renders when the scene changes. Written to a
  temporary file and renamed, so readers never see a partial file.
- run.sh passes arguments after the graphics API to the program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 00:06:09 +02:00
co-authored by Claude Opus 5.5
parent bbb3d1ba0e
commit d664f86945
9 changed files with 122 additions and 13 deletions
+8 -4
View File
@@ -67,12 +67,16 @@ EOF
cat > "$out/run.sh" <<EOF cat > "$out/run.sh" <<EOF
#!/bin/sh #!/bin/sh
# Restarts OVR Advanced Settings with the given graphics API, for testing: # Restarts OVR Advanced Settings with the given graphics API, for testing.
# ./run.sh [opengl|vulkan] (default: opengl) # Further arguments go to the program:
# ./run.sh [opengl|vulkan] [options] (default: opengl)
# e.g. ./run.sh opengl --snapshot /tmp/ovras.png
cd "\$(dirname "\$0")" || exit 1 cd "\$(dirname "\$0")" || exit 1
api="\${1:-opengl}"
[ \$# -gt 0 ] && shift
pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1 pkill -f "/$exe( |\\\$)" 2>/dev/null && sleep 1
DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\${1:-opengl}" >/dev/null 2>&1 & DISPLAY="\${DISPLAY:-:0}" nohup ./$exe --graphics-api "\$api" "\$@" >/dev/null 2>&1 &
echo "Started with \${1:-opengl}; log: \$(pwd)/AdvancedSettings.log" echo "Started with \$api; log: \$(pwd)/AdvancedSettings.log"
EOF EOF
chmod +x "$out/install.sh" "$out/uninstall.sh" "$out/run.sh" chmod +x "$out/install.sh" "$out/uninstall.sh" "$out/run.sh"
+21
View File
@@ -260,6 +260,27 @@ existing code:
private API. private API.
- RPATH is only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`): the build tree gets packaged. - RPATH is only `$ORIGIN` (`BUILD_WITH_INSTALL_RPATH`): the build tree gets packaged.
## 10. Layout diagnosis: font log and `--snapshot` (approved 2026-10-08)
**Why:** on the Frame the fixed 1200×800 layout overflows: the bottom quarter is cut off
(left: Statistics, Bindings, Settings; right: Microphone, Push-to-Talk), and the navigation
icons overlap their text. `QT_FONT_DPI=96`, `QT_QUICK_CONTROLS_STYLE=Basic` and the Material
"Dense" variant didn't fix it. Font metrics (Noto Sans instead of Segoe UI) are the main
suspect; measuring needs the unwarped rendered image.
**Changes (platform-neutral):**
- `OverlayController::SetWidget()`: logs once the application font (family, pt, px), what
20 pt (`MyText`) is in px, the screen's logical/physical DPI and the window's device pixel
ratio.
- Option `--snapshot <file>` (`setup.*`, `main.cpp`, `overlaycontroller.*`): while the
overlay is visible and rendered, `renderOverlay()` writes `RenderBackend::readback()` as
PNG to `<file>`, at most once per second (OVRAS only renders when the scene changes, so a
frame count could never trigger on a still page). Written to `<file>.tmp` and renamed.
`main.cpp` passes it through a setter, not a new constructor parameter, to keep the diff
small. Without the option nothing changes.
- `run.sh`: arguments after the graphics API go to the program
(`./run.sh opengl --snapshot /tmp/ovras.png`).
## Tests ## Tests
- **x86_64 locally:** the build succeeds; `objdump` check (R1). - **x86_64 locally:** the build succeeds; `objdump` check (R1).
+22
View File
@@ -266,6 +266,28 @@ URL: "steam://launch/1009850/openvroverlay" auto-launch: true`.
- **Open for R6/T10:** each tick makes three synchronous PulseAudio queries (server info, - **Open for R6/T10:** each tick makes three synchronous PulseAudio queries (server info,
all sinks, all sources) for the mute state. Not changed now. all sinks, all sources) for the mute state. Not changed now.
### Layout cut off on the Frame; diagnosis tools (2026-10-08)
- **Observed (headset photo):** the dashboard tab appears, navigation and laser work. The
fixed 1200×800 layout overflows: about the bottom quarter is cut off (left: Statistics,
Bindings, Settings; right: Microphone half, Push-to-Talk). Navigation icons overlap their
text. The right edge is complete.
- **Ruled out by experiment on the Frame:** `QT_FONT_DPI=96` (verified in the process
environment): no change. `QT_QUICK_CONTROLS_STYLE=Basic` (Qt 5's "Default" style; `qt6`
sets no style, so Qt 6 uses Fusion on Linux; only `mainwidget.qml` imports Material): no
visible change. `QT_QUICK_CONTROLS_MATERIAL_VARIANT=Dense`: slightly better, still cut
off.
- **Facts:** Frame X screen 1920×1080, `Xft.dpi: 144`; default font Noto Sans
(`fc-match sans-serif`). Template renders 1600×1000.
- **Also seen:** after removing the Steam license, Steam's `steamapps.vrmanifest` still
lists `steam.overlay.1009850` (not yet regenerated); the debug-output fix works (0 `[D]`
lines).
- **Diagnosis tools (DESIGN.md section 10):** font/screen metrics in the log, option
`--snapshot <file>`, `run.sh` passes further options.
- **Result:** cross build and package succeed, `Qt_6_PRIVATE_API` without `QVulkan*`: 0,
generated `run.sh` checked with `sh -n`. No new warnings in the changed lines.
- **Not tested yet:** on the Frame (log line "Font: …", PNG written).
### Test checklist on the Frame (step 5) ### Test checklist on the Frame (step 5)
Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR running). Deploy: `build_scripts/steamframe/deploy-to-frame.sh <user>@<frame>` (SteamVR running).
+3 -2
View File
@@ -124,11 +124,12 @@ docker run --rm -v "$PWD:$PWD" -w "$PWD" ovras-steamframe-cross-arm64 \
| Command | Effect | | Command | Effect |
|---|---| |---|---|
| `./install.sh` | Registers with SteamVR (auto-start) and starts | | `./install.sh` | Registers with SteamVR (auto-start) and starts |
| `./run.sh opengl\|vulkan` | Restarts with that graphics API | | `./run.sh opengl\|vulkan [options]` | Restarts with that graphics API; further options go to the program |
| `./uninstall.sh` | Unregisters | | `./uninstall.sh` | Unregisters |
| `AdvancedSettings.log` | Log (in the working directory, i.e. this folder) | | `AdvancedSettings.log` | Log (in the working directory, i.e. this folder) |
Program options (new): `--graphics-api opengl|vulkan` (default `opengl`). Existing ones Program options (new): `--graphics-api opengl|vulkan` (default `opengl`); `--snapshot <file>`
(diagnosis: writes the rendered overlay as PNG at most once per second while visible). Existing ones
include `--force-install-manifest` and `--force-remove-manifest`; see `--help`. include `--force-install-manifest` and `--force-remove-manifest`; see `--help`.
## Steam Frame facts the code relies on ## Steam Frame facts the code relies on
+1
View File
@@ -97,6 +97,7 @@ int main( int argc, char* argv[] )
commandLineArgs.forceNoSound, commandLineArgs.forceNoSound,
qmlEngine, qmlEngine,
std::move( renderBackend ) ) ); std::move( renderBackend ) ) );
controller->setSnapshotPath( commandLineArgs.snapshotPath );
//const auto url //const auto url
// = QUrl( ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" ); // = QUrl( ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" );
QQmlComponent component( &qmlEngine, ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" ); QQmlComponent component( &qmlEngine, ":/qt/qml/AdvancedSettings/src/res/qml/common/mainwidget.qml" );
+34
View File
@@ -16,6 +16,9 @@
#include <QCursor> #include <QCursor>
#include <QProcess> #include <QProcess>
#include <QMessageBox> #include <QMessageBox>
#include <QFile>
#include <QFontInfo>
#include <QScreen>
#include <QtLogging> #include <QtLogging>
#include <QtDebug> #include <QtDebug>
#include <iostream> #include <iostream>
@@ -456,6 +459,20 @@ void OverlayController::SetWidget( QQuickItem* quickItem,
static_cast<int>( quickItem->width() ), static_cast<int>( quickItem->width() ),
static_cast<int>( quickItem->height() ) ); static_cast<int>( quickItem->height() ) );
// Font and screen metrics that decide how big the fixed-size
// (1200x800) QML layout comes out, e.g. on the Steam Frame (Noto Sans,
// Xft.dpi 144) compared to Windows (Segoe UI, 96 DPI).
const QFont font = QGuiApplication::font();
const QFontInfo fontInfo( font );
qInfo() << "Font:" << fontInfo.family() << fontInfo.pointSizeF()
<< "pt =" << fontInfo.pixelSize() << "px (requested"
<< font.family() << font.pointSizeF() << "pt); 20 pt ="
<< QFontInfo( QFont( font.family(), 20 ) ).pixelSize()
<< "px; screen" << m_window.screen()->name() << "logical DPI"
<< m_window.screen()->logicalDotsPerInch() << "physical DPI"
<< m_window.screen()->physicalDotsPerInch()
<< "device pixel ratio" << m_window.effectiveDevicePixelRatio();
vr::HmdVector2_t vecWindowSize vr::HmdVector2_t vecWindowSize
= { static_cast<float>( quickItem->width() ), = { static_cast<float>( quickItem->width() ),
static_cast<float>( quickItem->height() ) }; static_cast<float>( quickItem->height() ) };
@@ -572,6 +589,23 @@ void OverlayController::renderOverlay()
m_renderControl.endFrame(); m_renderControl.endFrame();
m_renderBackend->finishFrame(); m_renderBackend->finishFrame();
// --snapshot: writes the rendered overlay to a PNG at most once per
// second while it is visible, so the layout can be checked without a
// headset photo. Written to a temporary file and renamed, so readers
// never see a partial file.
if ( !m_snapshotPath.isEmpty()
&& ( !m_snapshotTimer.isValid()
|| m_snapshotTimer.elapsed() >= 1000 ) )
{
m_snapshotTimer.start();
const QString tmp = m_snapshotPath + ".tmp";
if ( m_renderBackend->readback().save( tmp, "PNG" ) )
{
QFile::remove( m_snapshotPath );
QFile::rename( tmp, m_snapshotPath );
}
}
vr::VRTextureBounds_t bounds = m_renderBackend->vrTextureBounds(); vr::VRTextureBounds_t bounds = m_renderBackend->vrTextureBounds();
vr::VROverlay()->SetOverlayTextureBounds( m_ulOverlayHandle, &bounds ); vr::VROverlay()->SetOverlayTextureBounds( m_ulOverlayHandle, &bounds );
vr::Texture_t texture = m_renderBackend->vrTexture(); vr::Texture_t texture = m_renderBackend->vrTexture();
+10
View File
@@ -23,6 +23,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QQuickRenderTarget> #include <QQuickRenderTarget>
#include <QElapsedTimer>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "render/RenderBackend.h" #include "render/RenderBackend.h"
@@ -117,6 +118,9 @@ private:
QQuickWindow m_window{ &m_renderControl }; QQuickWindow m_window{ &m_renderControl };
std::optional<vr::ETextureType> m_cached_vr_texture_type; std::optional<vr::ETextureType> m_cached_vr_texture_type;
// --snapshot (see renderOverlay()): target file and time of the last one.
QString m_snapshotPath;
QElapsedTimer m_snapshotTimer;
// Set by Shutdown(), which must run only once (see there). // Set by Shutdown(), which must run only once (see there).
bool m_shutdownDone = false; bool m_shutdownDone = false;
@@ -210,6 +214,12 @@ public:
std::unique_ptr<RenderBackend> renderBackend ); std::unique_ptr<RenderBackend> renderBackend );
virtual ~OverlayController(); virtual ~OverlayController();
// Diagnosis (--snapshot): see renderOverlay(). Empty: off.
void setSnapshotPath( const QString& path )
{
m_snapshotPath = path;
}
void Shutdown(); void Shutdown();
Q_INVOKABLE void exitApp(); Q_INVOKABLE void exitApp();
Q_INVOKABLE void setAutoChapProfileName( int index ); Q_INVOKABLE void setAutoChapProfileName( int index );
+14 -7
View File
@@ -65,6 +65,10 @@ CommandLineOptions returnCommandLineParser( const MyQApplication& application )
k_graphicsApiDefault ); k_graphicsApiDefault );
parser.addOption( graphicsApi ); parser.addOption( graphicsApi );
QCommandLineOption snapshot(
k_snapshot, k_snapshotDescription, k_snapshotValueName );
parser.addOption( snapshot );
parser.process( application ); parser.process( application );
const bool desktopModeEnabled = parser.isSet( desktopMode ); const bool desktopModeEnabled = parser.isSet( desktopMode );
@@ -94,13 +98,16 @@ CommandLineOptions returnCommandLineParser( const MyQApplication& application )
const QString graphicsApiValue = parser.value( graphicsApi ); const QString graphicsApiValue = parser.value( graphicsApi );
qInfo() << "Graphics API:" << graphicsApiValue; qInfo() << "Graphics API:" << graphicsApiValue;
const CommandLineOptions commandLineArgs{ desktopModeEnabled, const QString snapshotValue = parser.value( snapshot );
forceNoSoundEnabled, if ( !snapshotValue.isEmpty() )
forceNoManifestEnabled, qInfo() << "Overlay snapshots to:" << snapshotValue;
forceInstallManifestEnabled,
forceRemoveManifestEnabled, const CommandLineOptions commandLineArgs{
resetSettingsEnabled, desktopModeEnabled, forceNoSoundEnabled,
graphicsApiValue }; forceNoManifestEnabled, forceInstallManifestEnabled,
forceRemoveManifestEnabled, resetSettingsEnabled,
graphicsApiValue, snapshotValue
};
qInfo() << "Command line arguments processed."; qInfo() << "Command line arguments processed.";
+9
View File
@@ -30,6 +30,8 @@ struct CommandLineOptions
const bool resetSettings = false; const bool resetSettings = false;
// Value of --graphics-api; checked in main() with parseGraphicsApi(). // Value of --graphics-api; checked in main() with parseGraphicsApi().
const QString graphicsApi = "opengl"; const QString graphicsApi = "opengl";
// Value of --snapshot; empty: no snapshots.
const QString snapshotPath;
}; };
// Manages the programs control flow and main settings. // Manages the programs control flow and main settings.
@@ -98,6 +100,13 @@ constexpr auto k_graphicsApiDescription
= "Graphics API for rendering the overlay: opengl (default) or vulkan."; = "Graphics API for rendering the overlay: opengl (default) or vulkan.";
constexpr auto k_graphicsApiValueName = "api"; constexpr auto k_graphicsApiValueName = "api";
constexpr auto k_graphicsApiDefault = "opengl"; constexpr auto k_graphicsApiDefault = "opengl";
// Diagnosis: writes the rendered overlay to a PNG while it is visible, to check
// the layout without a headset photo (OverlayController::renderOverlay()).
constexpr auto k_snapshot = "snapshot";
constexpr auto k_snapshotDescription
= "Writes the rendered overlay to <file> (PNG) at most once per second "
"while it is visible.";
constexpr auto k_snapshotValueName = "file";
constexpr auto k_resetSettings = "reset-steamvr-settings"; constexpr auto k_resetSettings = "reset-steamvr-settings";
constexpr auto k_resetSettingsDescription constexpr auto k_resetSettingsDescription