-
Notifications
You must be signed in to change notification settings - Fork 77
Add Variable Refresh Rate (VRR) Test (New) #2464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3bb7ec6
93eab0f
7e144e6
9ee3237
a138db9
e3e7120
1819cc8
81fe257
17b4488
d44836a
10e3c85
91bcc24
5e5cf26
b9b6333
f53df3d
c52b87b
ab5a953
3fb90f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| /* This file is part of Checkbox. | ||
|
|
||
| Copyright 2026 Canonical Ltd. | ||
| Written by: | ||
| Zhongning Li <zhongning.li@canonical.com> | ||
|
|
||
| Checkbox is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU General Public License version 3, | ||
| as published by the Free Software Foundation. | ||
|
|
||
| Checkbox is distributed in the hope that it will be useful, | ||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU General Public License for more details. | ||
|
|
||
| You should have received a copy of the GNU General Public License | ||
| along with Checkbox. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| import QtQuick 2.0 | ||
| import QtQuick.Window 2.0 | ||
| import QtQuick.Controls 1.4 | ||
| import QtQuick.Layouts 1.2 | ||
|
|
||
| Window { | ||
| id: root | ||
| width: 1280 | ||
| height: 720 | ||
| visible: true | ||
| title: "Dynamic Refresh Rate Demo" | ||
| color: "#1a1a1a" | ||
|
|
||
| property int targetFps: 60 | ||
| property int rectCount: 5 | ||
|
|
||
| property int minFps: 10 | ||
| property int maxFps: 200 | ||
|
|
||
| property int minRectCount: 1 | ||
| property int maxRectCount: 10 | ||
|
|
||
| // old QT workaround, there's no 'Shortcuts' property on Window | ||
| // in new QTs don't do this | ||
| Item { | ||
| anchors.fill: parent | ||
| focus: true | ||
| Keys.onPressed: { | ||
| if (event.key === Qt.Key_Escape) { | ||
| root.close() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Timer { | ||
| interval: 1000 / targetFps | ||
| running: true | ||
| repeat: true | ||
| onTriggered: { | ||
| // manually update the positions | ||
| // this also implicitly changes the framerate to the requested one | ||
| for (var i = 0; i < rectContainer.children.length; i++) { | ||
| // must use 'var' here, not let/const like modern js | ||
| // otherwise older QT won't understand it | ||
| var rect = rectContainer.children[i]; | ||
| // this QT version doesn't support optional chaining | ||
| // don't use rect?.updatePosition?.() | ||
| // method existence also must be checked | ||
| // since it doesn't exist on the very 1st frame | ||
| if (rect.updatePosition) { | ||
| rect.updatePosition(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Container for the rectangles | ||
| Item { | ||
| id: rectContainer | ||
| anchors.fill: parent | ||
|
|
||
| Repeater { | ||
| model: rectCount | ||
| Rectangle { | ||
| width: Screen.width * 0.05 | ||
| height: Screen.width * 0.05 | ||
| color: Qt.hsla(Math.random(), 0.6, 0.6, 0.9) | ||
| radius: 4 | ||
|
|
||
| // velocity x and y | ||
| property real vx: (Math.random() - 0.5) * 500 | ||
| property real vy: (Math.random() - 0.5) * 500 | ||
|
|
||
| Component.onCompleted: { | ||
| x = Math.random() * (root.width - width); | ||
| y = Math.random() * (root.height - height); | ||
| } | ||
|
|
||
| // Custom function called by the Timer | ||
| function updatePosition() { | ||
| // normalize position change w.r.t. fps | ||
| x += vx / targetFps; | ||
| y += vy / targetFps; | ||
|
|
||
| // bounce at the walls | ||
| if (x <= 0 || x >= root.width - width) { | ||
| vx *= -1; | ||
| } | ||
| if (y <= 0 || y >= root.height - height) { | ||
| vy *= -1 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Control Panel | ||
| Rectangle { | ||
| id: panel | ||
| anchors.bottom: parent.bottom | ||
| anchors.horizontalCenter: parent.horizontalCenter | ||
| width: contentColumn.implicitWidth + 50 | ||
| height: contentColumn.implicitHeight + 50 | ||
| color: "#cc000000" | ||
| radius: 12 | ||
| border.color: "white" | ||
| anchors.margins: 10 | ||
|
|
||
| Column { | ||
| id: contentColumn | ||
| anchors.centerIn: parent | ||
| spacing: 5 | ||
|
|
||
| Text { | ||
| text: "Press Esc to quit" | ||
| color: "white" | ||
| font.bold: true | ||
| anchors.left: parent.left | ||
| } | ||
|
tomli380576 marked this conversation as resolved.
|
||
|
|
||
| Text { | ||
| text: "This test should be run at fullscreen" | ||
| color: "grey" | ||
| anchors.left: parent.left | ||
| } | ||
|
tomli380576 marked this conversation as resolved.
|
||
|
|
||
| Text { | ||
| text: "Set GALLIUM_HUD=fps vblank_mode=3 MESA_VK_WSI_PRESENT_MODE=relaxed" | ||
| color: "grey" | ||
| anchors.left: parent.left | ||
| } | ||
|
|
||
| Text { | ||
| text: "Look for tearing ONLY. Any stutter or fps mismatch is OK." | ||
| color: "red" | ||
| font.bold: true | ||
| anchors.left: parent.left | ||
| } | ||
|
|
||
| Text { | ||
| text: "Requested Refresh Rate: " + targetFps + " FPS" | ||
| color: "white" | ||
| font.bold: true | ||
| anchors.horizontalCenter: parent.horizontalCenter | ||
| } | ||
|
|
||
| RowLayout { | ||
| Layout.minimumHeight: 10 | ||
| Layout.fillWidth: true | ||
| anchors.horizontalCenter: parent.horizontalCenter | ||
| Button { | ||
| text: '-' | ||
| Layout.fillHeight: true | ||
| Layout.fillWidth: true | ||
| onClicked: { | ||
| targetFps = Math.max(minFps, targetFps - 1) | ||
| } | ||
| } | ||
| Slider { | ||
| minimumValue: minFps | ||
| maximumValue: maxFps | ||
| value: targetFps | ||
| onValueChanged: { | ||
| targetFps = Math.floor(value) | ||
| } | ||
| } | ||
| Button { | ||
| text: '+' | ||
| Layout.fillHeight: true | ||
| Layout.fillWidth: true | ||
| onClicked: { | ||
| targetFps = Math.min(maxFps, targetFps + 1) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Text { | ||
| text: "Number of rectangles: " + rectCount | ||
| color: "white" | ||
| font.bold: true | ||
| anchors.horizontalCenter: parent.horizontalCenter | ||
| } | ||
|
|
||
| RowLayout { | ||
| Layout.minimumHeight: 10 | ||
| Layout.fillWidth: true | ||
| anchors.horizontalCenter: parent.horizontalCenter | ||
| Button { | ||
| text: '-' | ||
| Layout.fillHeight: true | ||
| Layout.fillWidth: true | ||
| onClicked: { | ||
| rectCount = Math.max(minRectCount, rectCount - 1) | ||
| } | ||
| } | ||
| Slider { | ||
| minimumValue: 1 | ||
| maximumValue: 10 | ||
| value: rectCount | ||
| onValueChanged: { | ||
| rectCount = Math.floor(value) | ||
| } | ||
| } | ||
| Button { | ||
| text: '+' | ||
| Layout.fillHeight: true | ||
| Layout.fillWidth: true | ||
| onClicked: { | ||
| rectCount = Math.min(maxRectCount, rectCount + 1) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -409,3 +409,9 @@ include: | |
| bootstrap_include: | ||
| graphics_card | ||
| executable | ||
|
|
||
| id: vrr-test-plan | ||
| unit: test plan | ||
| _name: Variable-Refresh-Rate Test Plan | ||
| include: | ||
| graphics/variable-refresh-rate.* | ||
|
Comment on lines
+413
to
+417
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| unit: job | ||
| plugin: user-interact-verify | ||
| category_id: com.canonical.plainbox::graphics | ||
| id: graphics/variable-refresh-rate-manual | ||
| flags: | ||
| - also-after-suspend | ||
| imports: | ||
| - from com.canonical.plainbox import manifest | ||
| requires: | ||
| - executable.name == 'qmlscene' | ||
| - manifest.has_vrr_support == 'True' | ||
| - dmi.sane_product == 'portable' # don't run this if there's no built-in screen | ||
| # the hud shows an fps graph over time | ||
| # vblank_mode=3 forces vsync for opengl | ||
| # https://dri.freedesktop.org/wiki/ConfigurationOptions/ | ||
| # MESA_VK_WSI_PRESENT_MODE=relaxed is the relaxed vsync for vulkan | ||
| # https://docs.mesa3d.org/envvars.html#envvar-MESA_VK_WSI_PRESENT_MODE | ||
| # vblank is ignored by vulkan and the MESA_VK var is ignored by opengl => they don't conflict each other | ||
| command: >- | ||
| GALLIUM_HUD=fps | ||
| MESA_VK_WSI_PRESENT_MODE=relaxed | ||
| vblank_mode=3 | ||
| qmlscene "$PLAINBOX_PROVIDER_DATA"/vrr_rectangles_test.qml --fullscreen | ||
|
tomli380576 marked this conversation as resolved.
|
||
| estimated_duration: "20s" | ||
| summary: This test checks if Variable-Refresh-Rate (VRR) is working on the built-in display | ||
| steps: | | ||
| 1. Press Enter to start the test program | ||
| 2. Inside the test program, you will see multiple rectangles bouncing inside the window. | ||
| Adjust the FPS slider and look for screen tearing. | ||
| It's OK for the FPS meter to not match the requested FPS. | ||
| 3. If the DUT appears to be struggling to render all the rectangles, | ||
| use the bottom slider to adjust the amount. | ||
| 4. Press Esc to quit the program | ||
| 5. Mark as pass if no tearing was observed, fail otherwise. | ||
| --- | ||
| unit: manifest entry | ||
| id: has_vrr_support | ||
| name: Does the device have a built-in display with Variable Refresh Rate (VRR)? | ||
|
tomli380576 marked this conversation as resolved.
|
||
| value-type: bool | ||
|
tomli380576 marked this conversation as resolved.
|
||
Uh oh!
There was an error while loading. Please reload this page.