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:
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" );
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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.";
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user