on xenial help sign in
Optimize Wallpaper loading to reduce shell lag
840777fDalton Durst
qml/Components/Wallpaper.qml+13−3
@@ -38,9 +38,7 @@ Item {
38 anchors.fill: parent
38 anchors.fill: parent
39 fillMode: Image.PreserveAspectCrop
39 fillMode: Image.PreserveAspectCrop
40
40
41− // Setting this to true stops the UI from pausing when the shell's
42− // dimensions change but causes the image to flash on screen.
43− asynchronous: false
41+ asynchronous: true
44
42
45 property real oldSourceSize: 0.0
43 property real oldSourceSize: 0.0
46 property real intermediarySourceSize: {
44 property real intermediarySourceSize: {
@@ -73,6 +71,18 @@ Item {
73 source: intermediarySourceSize !== -1.0 ? root.source : ""
71 source: intermediarySourceSize !== -1.0 ? root.source : ""
74 }
72 }
75
73
74+ Rectangle {
75+ id: wallpaperFadeRectangle
76+ objectName: "wallpaperFadeRectangle"
77+ color: theme.palette.normal.background
78+ anchors.fill: parent
79+ opacity: image.status === Image.Ready ? 0: 1
80+ visible: opacity !== 0
81+ Behavior on opacity {
82+ UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
83+ }
84+ }
85+
76 Timer {
86 Timer {
77 id: developerSanityTimer
87 id: developerSanityTimer
78 interval: 2000
88 interval: 2000
qml/Components/WallpaperResolver.qml+14−4
@@ -24,13 +24,21 @@ Item {
24 // Provide a list of wallpapers to resolve here, preferred ones first
24 // Provide a list of wallpapers to resolve here, preferred ones first
25 property var candidates: []
25 property var candidates: []
26
26
27+ property bool cache: true
28+
27 readonly property url background: {
29 readonly property url background: {
28 for (var i = 0; i < repeater.count; i++) {
30 for (var i = 0; i < repeater.count; i++) {
31+ var image = repeater.itemAt(i);
32+ var expectedImageSource = Qt.resolvedUrl(candidates[i]);
33+ if (image.source != expectedImageSource)
34+ return "";
29− if (repeater.itemAt(i).status === Image.Ready)
35+ if (image.status === Image.Ready)
30 return candidates[i];
36 return candidates[i];
37+ if (image.status === Image.Loading)
38+ return "";
31 }
39 }
32− if (i > 0) {
40+ if (candidates.length > 0) {
33− return candidates[i - 1]; // last item is last resort
41+ return candidates.slice(-1)[0]; // last item is last resort
34 } else {
42 } else {
35 return "";
43 return "";
36 }
44 }
@@ -38,9 +46,11 @@ Item {
38
46
39 Repeater {
47 Repeater {
40 id: repeater
48 id: repeater
41− model: root.candidates
49+ model: root.candidates.slice(0, -1)
42 delegate: Image {
50 delegate: Image {
43 source: modelData
51 source: modelData
52+ asynchronous: true
53+ cache: root.cache
44 height: 0
54 height: 0
45 width: 0
55 width: 0
46 sourceSize.height: 1
56 sourceSize.height: 1
tests/data/unity/backgrounds/black.pngnew file

Binary file not shown.

tests/qmltests/Components/tst_Wallpaper.qml+14
@@ -54,6 +54,7 @@ Item {
54
54
55 function init() {
55 function init() {
56 root.sourceSize = 0;
56 root.sourceSize = 0;
57+ root.defaultBackground = "../../data/unity/backgrounds/20x30.png";
57 }
58 }
58
59
59 // Ensures that the Wallpaper's sourceSize causes the image to be
60 // Ensures that the Wallpaper's sourceSize causes the image to be
@@ -100,5 +101,18 @@ Item {
100 // forever.
101 // forever.
101 wait(3000);
102 wait(3000);
102 }
103 }
104+
105+ // Ensures there is a blocker over the wallpaper before it has loaded
106+ // which goes away once loading finishes
107+ function test_wallpaperBlock() {
108+ root.sourceSize = 1;
109+ root.defaultBackground = "";
110+ var wallpaper = createTemporaryObject(wallpaperComponent, root);
111+ var wallpaperFadeRectangle = findChild(wallpaper, "wallpaperFadeRectangle");
112+ compare(wallpaperFadeRectangle.opacity, 1);
113+ root.defaultBackground = "../../data/unity/backgrounds/red.png";
114+ tryCompare(wallpaperFadeRectangle, "opacity", 0);
115+ compare(wallpaperFadeRectangle.visible, false);
116+ }
103 }
117 }
104}
118}
tests/qmltests/Components/tst_WallpaperResolver.qml+104−18
@@ -1,5 +1,6 @@
1/*
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
2 * Copyright (C) 2015 Canonical, Ltd.
3+ * Copyright (C) 2021 UBports Foundation
3 *
4 *
4 * This program is free software; you can redistribute it and/or modify
5 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * it under the terms of the GNU General Public License as published by
@@ -16,20 +17,34 @@
16
17
17import QtQuick 2.4
18import QtQuick 2.4
18import QtTest 1.0
19import QtTest 1.0
19−import Ubuntu.Components 1.3
20import Unity.Test 0.1
20import Unity.Test 0.1
21
21
22import "../../../qml/Components"
22import "../../../qml/Components"
23
23
24
24
25Image {
25Image {
26+ id: root
26− width: units.gu(70)
27+ width: 70
27− height: units.gu(70)
28+ height: 70
28
29
29 source: wallpaperResolver.background
30 source: wallpaperResolver.background
30
31
32+ readonly property url blue: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")
33+ readonly property url red: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")
34+ readonly property url big: Qt.resolvedUrl("../../graphics/applicationIcons/dash.png")
35+
31 WallpaperResolver {
36 WallpaperResolver {
32 id: wallpaperResolver
37 id: wallpaperResolver
38+
39+ property var pastBackgrounds: []
40+
41+ onBackgroundChanged: pastBackgrounds.push(background)
42+ }
43+
44+ SignalSpy {
45+ id: spy
46+ signalName: "backgroundChanged"
47+ target: wallpaperResolver
33 }
48 }
34
49
35 UnityTestCase {
50 UnityTestCase {
@@ -39,37 +54,108 @@ Image {
39
54
40 function test_background_data() {
55 function test_background_data() {
41 return [
56 return [
57+ {tag: "empty-candidates",
58+ list: [],
59+ output: ""},
60+
61+ {tag: "blank-candidate",
62+ list: [""],
63+ output: ""},
64+
65+ {tag: "blank-urls",
66+ list: [Qt.resolvedUrl(""), Qt.resolvedUrl(""), root.blue],
67+ output: root.blue},
68+
69+ {tag: "invalid-urls",
70+ list: [Qt.resolvedUrl("/first"), Qt.resolvedUrl("/middle"), root.blue],
71+ output: root.blue},
72+
73+ {tag: "valid-after-blanks",
74+ list: ["", "", root.red],
75+ output: root.red},
76+
77+ // Ensure that the WallpaperResolver doesn't get stuck if it
78+ // sees the same invalid wallpaper multiple times in a row
79+ {tag: "valid-after-the-same-invalid",
80+ list: ["/first", "/first", "/first", root.red],
81+ output: root.red},
82+
83+ {tag: "naughty",
84+ list: [null, undefined, "", NaN, 1.0, 1, root.red, null],
85+ output: root.red},
86+
42 {tag: "none-valid",
87 {tag: "none-valid",
43 list: ["/first", "/middle", "/last"],
88 list: ["/first", "/middle", "/last"],
44− output: "file:///last"},
89+ output: Qt.resolvedUrl("/last")},
45
90
46 {tag: "first-valid",
91 {tag: "first-valid",
47− list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
48− "/middle", "/last"],
92+ list: [root.blue, "/middle", "/last"],
49− output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
93+ output: root.blue},
50
94
51 {tag: "middle-valid",
95 {tag: "middle-valid",
52− list: ["/first",
96+ list: ["/first", root.red, "/last"],
53− Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
97+ output: root.red},
54− "/last"],
55− output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
56
98
57 {tag: "last-valid",
99 {tag: "last-valid",
58− list: ["/first",
100+ list: ["/first", "/middle", root.red],
59− "/middle",
101+ output: root.red},
60− Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
61− output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
62
102
63 {tag: "multiple-valid",
103 {tag: "multiple-valid",
64− list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
104+ list: [root.blue, root.red],
65− Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
105+ output: root.blue},
66− output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
106+
107+ {tag: "multiple-valid-after-multiple-invalid",
108+ list: ["/first", "/middle", "/last", root.blue, root.red],
109+ output: root.blue},
110+
67 ]
111 ]
68 }
112 }
69
113
114+ function init() {
115+ // Make sure we don't have our next test compare() to the results
116+ // of the last test by exercising the resolver
117+ wallpaperResolver.cache = true;
118+ wallpaperResolver.candidates = [];
119+ tryCompare(wallpaperResolver, "background", "");
120+ wallpaperResolver.pastBackgrounds = [];
121+ wallpaperResolver.candidates = [root.blue, root.blue];
122+ tryCompare(wallpaperResolver, "background", root.blue);
123+ wallpaperResolver.candidates = [];
124+ tryCompare(wallpaperResolver, "background", "");
125+ tryCompare(wallpaperResolver, "pastBackgrounds", [root.blue, Qt.resolvedUrl("")])
126+ wallpaperResolver.pastBackgrounds = [];
127+ }
128+
70 function test_background(data) {
129 function test_background(data) {
71 wallpaperResolver.candidates = data.list;
130 wallpaperResolver.candidates = data.list;
72 tryCompare(wallpaperResolver, "background", data.output);
131 tryCompare(wallpaperResolver, "background", data.output);
73 }
132 }
133+
134+ function test_reload_with_blanks() {
135+ wallpaperResolver.candidates = ["", "", root.red];
136+ tryCompare(wallpaperResolver, "background", root.red);
137+ wallpaperResolver.candidates = ["", "", root.blue];
138+ tryCompare(wallpaperResolver, "background", root.blue);
139+ }
140+
141+ // WallpaperResolver loads images asynchronously. It's important to make
142+ // sure that it never returns the wrong image just because they loaded
143+ // in the wrong order. So we set its images 100 times.
144+ function test_images_changing() {
145+ wallpaperResolver.cache = false;
146+ for (var i = 0; i < 50; i++) {
147+ // We don't spy on the first one because the wallpaper doesn't
148+ // transition from a set url to "" the first time.
149+ wallpaperResolver.candidates = [root.red, root.big, root.blue];
150+ tryCompare(wallpaperResolver, "background", root.red);
151+
152+ spy.clear();
153+ wallpaperResolver.pastBackgrounds = [];
154+ wallpaperResolver.candidates = [root.big, root.blue, root.red];
155+ tryCompare(wallpaperResolver, "background", root.big);
156+ compare(wallpaperResolver.pastBackgrounds, ["", root.big])
157+ compare(spy.count, 4);
158+ }
159+ }
74 }
160 }
75}
161}
tests/qmltests/tst_Shell.qml+1−1
@@ -1307,7 +1307,7 @@ Rectangle {
1307
1307
1308 AccountsService.backgroundFile = Qt.resolvedUrl("../graphics/applicationIcons/dash.png");
1308 AccountsService.backgroundFile = Qt.resolvedUrl("../graphics/applicationIcons/dash.png");
1309 tryCompare(greeter, "hasCustomBackground", true);
1309 tryCompare(greeter, "hasCustomBackground", true);
1310− compare(wallpaperResolver.background, AccountsService.backgroundFile);
1310+ tryCompare(wallpaperResolver, "background", AccountsService.backgroundFile);
1311 }
1311 }
1312
1312
1313 function test_tapOnRightEdgeReachesApplicationSurface() {
1313 function test_tapOnRightEdgeReachesApplicationSurface() {