letfixit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +33 -0
- package/README.md +226 -0
- package/adapters/android/build.gradle +28 -0
- package/adapters/android/src/main/kotlin/dev/perfanalyzer/ChoreographerAdapter.kt +43 -0
- package/adapters/android/src/main/kotlin/dev/perfanalyzer/LogcatReader.kt +64 -0
- package/adapters/android/src/main/kotlin/dev/perfanalyzer/MemoryObserver.kt +46 -0
- package/adapters/android/src/main/kotlin/dev/perfanalyzer/OkHttpInterceptor.kt +53 -0
- package/adapters/android/src/main/kotlin/dev/perfanalyzer/PerfAnalyzer.kt +100 -0
- package/adapters/flutter/lib/perf_analyzer.dart +73 -0
- package/adapters/flutter/lib/src/frame_observer.dart +45 -0
- package/adapters/flutter/lib/src/network_observer.dart +72 -0
- package/adapters/flutter/lib/src/rebuild_observer.dart +57 -0
- package/adapters/flutter/lib/src/universal_event.dart +50 -0
- package/adapters/flutter/lib/src/vm_service_adapter.dart +81 -0
- package/adapters/flutter/pubspec.lock +184 -0
- package/adapters/flutter/pubspec.yaml +13 -0
- package/config/model.manifest.example.json +108 -0
- package/config/model.manifest.json +108 -0
- package/core/schema/universal_event.dart +63 -0
- package/dashboard/dist/assets/index-D5TCSsvB.js +107 -0
- package/dashboard/dist/index.html +118 -0
- package/package.json +62 -0
- package/scripts/encrypt-model.js +86 -0
- package/scripts/fill-manifest.js +67 -0
- package/scripts/load-env.js +20 -0
- package/scripts/model_crypto.js +14 -0
- package/scripts/setup-model.js +269 -0
- package/server/adb_bridge.js +201 -0
- package/server/adb_score.js +77 -0
- package/server/ai_proxy.js +373 -0
- package/server/analysis_engine.js +222 -0
- package/server/android_bridge.js +124 -0
- package/server/android_live.js +167 -0
- package/server/data/device_gpu_db.json +465 -0
- package/server/device_db.js +94 -0
- package/server/device_review.js +146 -0
- package/server/finding_generator.js +56 -0
- package/server/flutter_bridge.js +414 -0
- package/server/index.js +619 -0
- package/server/ios_bridge.js +181 -0
- package/server/model_config.js +54 -0
- package/server/report_template.js +169 -0
- package/server/session_manager.js +370 -0
- package/server/widget_gpu_classifier.js +77 -0
- package/server/widget_triage.js +78 -0
- package/setup.sh +160 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"updated": "2025-07",
|
|
5
|
+
"tiers": {
|
|
6
|
+
"flagship": "Top-end GPU. Handles all Flutter widgets including heavy shaders, BackdropFilter, Impeller.",
|
|
7
|
+
"high": "Strong GPU. Most heavy widgets fine. Complex shader stacks may drop frames at 120Hz.",
|
|
8
|
+
"mid": "Moderate GPU. BackdropFilter, ShaderMask, heavy Opacity layers will cause frame drops.",
|
|
9
|
+
"low": "Weak GPU. Any offscreen compositing or CustomPainter triggers jank. Avoid Opacity != 0/1.",
|
|
10
|
+
"entry": "Very weak GPU. Even basic animations can drop below 30fps. Stack depth > 8 causes jank."
|
|
11
|
+
},
|
|
12
|
+
"gpu_families": {
|
|
13
|
+
"Adreno 7xx": "flagship",
|
|
14
|
+
"Adreno 6xx": "high",
|
|
15
|
+
"Adreno 5xx": "mid",
|
|
16
|
+
"Immortalis-G7xx":"flagship",
|
|
17
|
+
"Mali-G7xx": "high",
|
|
18
|
+
"Mali-G6xx": "mid",
|
|
19
|
+
"Mali-G5xx": "mid",
|
|
20
|
+
"Mali-G52": "low",
|
|
21
|
+
"Mali-G57": "low",
|
|
22
|
+
"Apple A17": "flagship",
|
|
23
|
+
"Apple A16": "flagship",
|
|
24
|
+
"Apple A15": "flagship",
|
|
25
|
+
"Apple A14": "high",
|
|
26
|
+
"Apple A13": "high",
|
|
27
|
+
"Apple A12": "high"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
"android": {
|
|
32
|
+
|
|
33
|
+
"Samsung Galaxy S24 Ultra": {
|
|
34
|
+
"gpu": "Adreno 750", "gpu_tier": "flagship", "ram_gb": 12,
|
|
35
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 3",
|
|
36
|
+
"flutter_notes": "Impeller performs excellently. All widget types safe."
|
|
37
|
+
},
|
|
38
|
+
"Samsung Galaxy S24+": {
|
|
39
|
+
"gpu": "Adreno 750", "gpu_tier": "flagship", "ram_gb": 12,
|
|
40
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 3",
|
|
41
|
+
"flutter_notes": "Same SoC as S24 Ultra. Full widget support."
|
|
42
|
+
},
|
|
43
|
+
"Samsung Galaxy S24": {
|
|
44
|
+
"gpu": "Xclipse 940", "gpu_tier": "high", "ram_gb": 8,
|
|
45
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Exynos 2400",
|
|
46
|
+
"flutter_notes": "Xclipse 940 (AMD RDNA2). BackdropFilter safe. Watch shader compilation at 120Hz."
|
|
47
|
+
},
|
|
48
|
+
"Samsung Galaxy S23 Ultra": {
|
|
49
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 12,
|
|
50
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 2",
|
|
51
|
+
"flutter_notes": "Excellent. All widgets safe including heavy shader stacks."
|
|
52
|
+
},
|
|
53
|
+
"Samsung Galaxy S23+": {
|
|
54
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 8,
|
|
55
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 2",
|
|
56
|
+
"flutter_notes": "Same SoC as S23 Ultra."
|
|
57
|
+
},
|
|
58
|
+
"Samsung Galaxy S23": {
|
|
59
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 8,
|
|
60
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 2",
|
|
61
|
+
"flutter_notes": "Same SoC as S23 Ultra."
|
|
62
|
+
},
|
|
63
|
+
"Samsung Galaxy S22 Ultra": {
|
|
64
|
+
"gpu": "Xclipse 920", "gpu_tier": "high", "ram_gb": 12,
|
|
65
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Exynos 2200",
|
|
66
|
+
"flutter_notes": "Xclipse 920 (AMD RDNA2). Heavy CustomPainter acceptable. Shader warm-up recommended."
|
|
67
|
+
},
|
|
68
|
+
"Samsung Galaxy S22+": {
|
|
69
|
+
"gpu": "Adreno 730", "gpu_tier": "high", "ram_gb": 8,
|
|
70
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 1",
|
|
71
|
+
"flutter_notes": "BackdropFilter in scrollable lists can cause micro-jank."
|
|
72
|
+
},
|
|
73
|
+
"Samsung Galaxy S22": {
|
|
74
|
+
"gpu": "Adreno 730", "gpu_tier": "high", "ram_gb": 8,
|
|
75
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 1",
|
|
76
|
+
"flutter_notes": "BackdropFilter in scrollable lists can cause micro-jank."
|
|
77
|
+
},
|
|
78
|
+
"Samsung Galaxy S21 Ultra": {
|
|
79
|
+
"gpu": "Mali-G78 MP14", "gpu_tier": "high", "ram_gb": 12,
|
|
80
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Exynos 2100",
|
|
81
|
+
"flutter_notes": "ShaderMask on large widgets may cause 1-2 dropped frames."
|
|
82
|
+
},
|
|
83
|
+
"Samsung Galaxy S21": {
|
|
84
|
+
"gpu": "Adreno 660", "gpu_tier": "high", "ram_gb": 8,
|
|
85
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Snapdragon 888",
|
|
86
|
+
"flutter_notes": "Avoid nesting multiple BackdropFilter widgets."
|
|
87
|
+
},
|
|
88
|
+
"Samsung Galaxy A54 5G": {
|
|
89
|
+
"gpu": "Mali-G68 MP4", "gpu_tier": "mid", "ram_gb": 8,
|
|
90
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Exynos 1380",
|
|
91
|
+
"flutter_notes": "BackdropFilter causes visible jank. Opacity layer >0.99 triggers saveLayer."
|
|
92
|
+
},
|
|
93
|
+
"Samsung Galaxy A34 5G": {
|
|
94
|
+
"gpu": "Mali-G68 MP4", "gpu_tier": "mid", "ram_gb": 6,
|
|
95
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 1080",
|
|
96
|
+
"flutter_notes": "Same as A54. Avoid animated gradients in ListView."
|
|
97
|
+
},
|
|
98
|
+
"Samsung Galaxy A14 5G": {
|
|
99
|
+
"gpu": "Mali-G68", "gpu_tier": "mid", "ram_gb": 4,
|
|
100
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 700",
|
|
101
|
+
"flutter_notes": "4GB RAM limits Flutter engine overhead. Avoid large image caches."
|
|
102
|
+
},
|
|
103
|
+
"Samsung Galaxy A13": {
|
|
104
|
+
"gpu": "Mali-G52 MC2", "gpu_tier": "low", "ram_gb": 4,
|
|
105
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Exynos 850",
|
|
106
|
+
"flutter_notes": "CRITICAL: Mali-G52 has no hardware raytracing. BackdropFilter drops to ~15fps. Avoid ShaderMask entirely."
|
|
107
|
+
},
|
|
108
|
+
"Samsung Galaxy A03": {
|
|
109
|
+
"gpu": "Mali-G52 MC2", "gpu_tier": "low", "ram_gb": 3,
|
|
110
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Snapdragon 450",
|
|
111
|
+
"flutter_notes": "Entry-tier device in many markets. Avoid animations >2 layers deep. No BackdropFilter."
|
|
112
|
+
},
|
|
113
|
+
"Samsung Galaxy A03s": {
|
|
114
|
+
"gpu": "PowerVR GE8320", "gpu_tier": "entry", "ram_gb": 3,
|
|
115
|
+
"api": 30, "arch": "arm64-v8a", "soc": "Helio P35",
|
|
116
|
+
"flutter_notes": "PowerVR GPU — poor Flutter/Skia optimization. Even basic CustomPainter causes jank."
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
"Google Pixel 8 Pro": {
|
|
120
|
+
"gpu": "Immortalis-G715 MC10", "gpu_tier": "flagship", "ram_gb": 12,
|
|
121
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Google Tensor G3",
|
|
122
|
+
"flutter_notes": "Excellent Impeller support. Hardware-accelerated backdrop filters."
|
|
123
|
+
},
|
|
124
|
+
"Google Pixel 8": {
|
|
125
|
+
"gpu": "Immortalis-G715 MC10", "gpu_tier": "flagship", "ram_gb": 8,
|
|
126
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Google Tensor G3",
|
|
127
|
+
"flutter_notes": "Identical GPU to Pixel 8 Pro. All widgets safe."
|
|
128
|
+
},
|
|
129
|
+
"Google Pixel 7 Pro": {
|
|
130
|
+
"gpu": "Mali-G710 MP7", "gpu_tier": "high", "ram_gb": 12,
|
|
131
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Google Tensor G2",
|
|
132
|
+
"flutter_notes": "BackdropFilter at 90Hz is smooth. Complex shader stacks watch at 120Hz mode."
|
|
133
|
+
},
|
|
134
|
+
"Google Pixel 7": {
|
|
135
|
+
"gpu": "Mali-G710 MP7", "gpu_tier": "high", "ram_gb": 8,
|
|
136
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Google Tensor G2",
|
|
137
|
+
"flutter_notes": "Same GPU as Pixel 7 Pro. No 120Hz so less GPU pressure."
|
|
138
|
+
},
|
|
139
|
+
"Google Pixel 7a": {
|
|
140
|
+
"gpu": "Mali-G710 MP7", "gpu_tier": "high", "ram_gb": 8,
|
|
141
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Google Tensor G2",
|
|
142
|
+
"flutter_notes": "Same GPU as Pixel 7."
|
|
143
|
+
},
|
|
144
|
+
"Google Pixel 6 Pro": {
|
|
145
|
+
"gpu": "Mali-G78 MP20", "gpu_tier": "high", "ram_gb": 12,
|
|
146
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Google Tensor G1",
|
|
147
|
+
"flutter_notes": "ShaderMask on full-screen widgets may stutter. Prefer shader warm-up."
|
|
148
|
+
},
|
|
149
|
+
"Google Pixel 6": {
|
|
150
|
+
"gpu": "Mali-G78 MP20", "gpu_tier": "high", "ram_gb": 8,
|
|
151
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Google Tensor G1",
|
|
152
|
+
"flutter_notes": "Same GPU as Pixel 6 Pro."
|
|
153
|
+
},
|
|
154
|
+
"Google Pixel 6a": {
|
|
155
|
+
"gpu": "Mali-G78 MP20", "gpu_tier": "high", "ram_gb": 6,
|
|
156
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Google Tensor G1",
|
|
157
|
+
"flutter_notes": "6GB RAM means larger apps should watch widget tree depth."
|
|
158
|
+
},
|
|
159
|
+
"Google Pixel 5": {
|
|
160
|
+
"gpu": "Adreno 620", "gpu_tier": "mid", "ram_gb": 8,
|
|
161
|
+
"api": 30, "arch": "arm64-v8a", "soc": "Snapdragon 765G",
|
|
162
|
+
"flutter_notes": "Adreno 620 handles most widgets. Avoid stacked BackdropFilters."
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
"OnePlus 12": {
|
|
166
|
+
"gpu": "Adreno 750", "gpu_tier": "flagship", "ram_gb": 12,
|
|
167
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 3",
|
|
168
|
+
"flutter_notes": "Flagship GPU. All Flutter widgets including heavy shaders perform well."
|
|
169
|
+
},
|
|
170
|
+
"OnePlus 11": {
|
|
171
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 16,
|
|
172
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 2",
|
|
173
|
+
"flutter_notes": "16GB RAM. Excellent for complex UI trees."
|
|
174
|
+
},
|
|
175
|
+
"OnePlus Nord CE3 Lite": {
|
|
176
|
+
"gpu": "Adreno 610", "gpu_tier": "mid", "ram_gb": 8,
|
|
177
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 695",
|
|
178
|
+
"flutter_notes": "Mid-range Adreno. BackdropFilter causes 3-5ms GPU overhead per frame."
|
|
179
|
+
},
|
|
180
|
+
"OnePlus Nord 3": {
|
|
181
|
+
"gpu": "Immortalis-G715 MC10", "gpu_tier": "flagship", "ram_gb": 16,
|
|
182
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 9000",
|
|
183
|
+
"flutter_notes": "Surprisingly strong for a Nord device. Full widget support."
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
"Xiaomi 14 Pro": {
|
|
187
|
+
"gpu": "Adreno 750", "gpu_tier": "flagship", "ram_gb": 12,
|
|
188
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 3",
|
|
189
|
+
"flutter_notes": "All Flutter widgets safe."
|
|
190
|
+
},
|
|
191
|
+
"Xiaomi 13": {
|
|
192
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 12,
|
|
193
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8 Gen 2",
|
|
194
|
+
"flutter_notes": "All Flutter widgets safe."
|
|
195
|
+
},
|
|
196
|
+
"Redmi Note 13 Pro+": {
|
|
197
|
+
"gpu": "Immortalis-G77 MC9", "gpu_tier": "high", "ram_gb": 12,
|
|
198
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 7200 Ultra",
|
|
199
|
+
"flutter_notes": "Solid mid-high GPU. BackdropFilter smooth at 120Hz."
|
|
200
|
+
},
|
|
201
|
+
"Redmi Note 12": {
|
|
202
|
+
"gpu": "Adreno 619", "gpu_tier": "mid", "ram_gb": 6,
|
|
203
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 685",
|
|
204
|
+
"flutter_notes": "Avoid BackdropFilter + scrolling simultaneously. Use RepaintBoundary."
|
|
205
|
+
},
|
|
206
|
+
"Redmi 12": {
|
|
207
|
+
"gpu": "Mali-G52 MC2", "gpu_tier": "low", "ram_gb": 4,
|
|
208
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Helio G88",
|
|
209
|
+
"flutter_notes": "Mali-G52 — same warnings as Galaxy A13. No compositing-heavy widgets."
|
|
210
|
+
},
|
|
211
|
+
"Redmi 10C": {
|
|
212
|
+
"gpu": "Adreno 619", "gpu_tier": "mid", "ram_gb": 4,
|
|
213
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Snapdragon 680",
|
|
214
|
+
"flutter_notes": "4GB RAM — watch memory pressure. Avoid caching large decoded images."
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
"Realme 11 Pro+": {
|
|
218
|
+
"gpu": "Mali-G68 MP4", "gpu_tier": "mid", "ram_gb": 12,
|
|
219
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 7050",
|
|
220
|
+
"flutter_notes": "BackdropFilter acceptable but visible CPU overhead with Skia."
|
|
221
|
+
},
|
|
222
|
+
"Realme C55": {
|
|
223
|
+
"gpu": "Mali-G57 MC2", "gpu_tier": "low", "ram_gb": 6,
|
|
224
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Helio G88",
|
|
225
|
+
"flutter_notes": "Budget GPU. Keep widget tree flat. Animated opacity causes frame drops."
|
|
226
|
+
},
|
|
227
|
+
"Realme C35": {
|
|
228
|
+
"gpu": "PowerVR GE8320", "gpu_tier": "entry", "ram_gb": 4,
|
|
229
|
+
"api": 31, "arch": "arm64-v8a", "soc": "Unisoc T616",
|
|
230
|
+
"flutter_notes": "PowerVR — poor Skia/Impeller support. Minimal animations only."
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
"Vivo V29": {
|
|
234
|
+
"gpu": "Mali-G68 MP4", "gpu_tier": "mid", "ram_gb": 8,
|
|
235
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 8200",
|
|
236
|
+
"flutter_notes": "Adequate for most apps. Avoid shader-heavy screens."
|
|
237
|
+
},
|
|
238
|
+
"OPPO Reno 11": {
|
|
239
|
+
"gpu": "Mali-G68 MP4", "gpu_tier": "mid", "ram_gb": 8,
|
|
240
|
+
"api": 34, "arch": "arm64-v8a", "soc": "Dimensity 8200",
|
|
241
|
+
"flutter_notes": "Same SoC as Vivo V29."
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
"Motorola Moto G84": {
|
|
245
|
+
"gpu": "Adreno 619", "gpu_tier": "mid", "ram_gb": 12,
|
|
246
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 695",
|
|
247
|
+
"flutter_notes": "Good mid-range. BackdropFilter usable with care."
|
|
248
|
+
},
|
|
249
|
+
"Motorola Moto G54": {
|
|
250
|
+
"gpu": "Mali-G68 MC4", "gpu_tier": "mid", "ram_gb": 8,
|
|
251
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Dimensity 7020",
|
|
252
|
+
"flutter_notes": "Avoid heavy CustomPainter in ListViews."
|
|
253
|
+
},
|
|
254
|
+
"Motorola Moto E13": {
|
|
255
|
+
"gpu": "Mali-G52", "gpu_tier": "entry", "ram_gb": 2,
|
|
256
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Unisoc T606",
|
|
257
|
+
"flutter_notes": "2GB RAM is hard constraint. Only basic Material widgets. No animations."
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
"Nothing Phone 2": {
|
|
261
|
+
"gpu": "Adreno 740", "gpu_tier": "flagship", "ram_gb": 12,
|
|
262
|
+
"api": 33, "arch": "arm64-v8a", "soc": "Snapdragon 8+ Gen 1",
|
|
263
|
+
"flutter_notes": "Glyph animations are hardware-composited. Flutter UI is unaffected."
|
|
264
|
+
},
|
|
265
|
+
"Nothing Phone 1": {
|
|
266
|
+
"gpu": "Adreno 730", "gpu_tier": "high", "ram_gb": 12,
|
|
267
|
+
"api": 32, "arch": "arm64-v8a", "soc": "Snapdragon 778G+",
|
|
268
|
+
"flutter_notes": "Strong mid-high GPU. All standard widgets safe."
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
"ios": {
|
|
273
|
+
|
|
274
|
+
"iPhone 15 Pro Max": {
|
|
275
|
+
"gpu": "Apple A17 Pro GPU (6-core)", "gpu_tier": "flagship", "ram_gb": 8,
|
|
276
|
+
"ios_min": "17.0", "arch": "arm64", "soc": "Apple A17 Pro",
|
|
277
|
+
"flutter_notes": "Hardware ray tracing. Metal 3. All Flutter/Impeller features perform excellently."
|
|
278
|
+
},
|
|
279
|
+
"iPhone 15 Pro": {
|
|
280
|
+
"gpu": "Apple A17 Pro GPU (6-core)", "gpu_tier": "flagship", "ram_gb": 8,
|
|
281
|
+
"ios_min": "17.0", "arch": "arm64", "soc": "Apple A17 Pro",
|
|
282
|
+
"flutter_notes": "Same GPU as 15 Pro Max."
|
|
283
|
+
},
|
|
284
|
+
"iPhone 15 Plus": {
|
|
285
|
+
"gpu": "Apple A16 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
286
|
+
"ios_min": "17.0", "arch": "arm64", "soc": "Apple A16 Bionic",
|
|
287
|
+
"flutter_notes": "A16 GPU handles all Flutter widgets at 60Hz."
|
|
288
|
+
},
|
|
289
|
+
"iPhone 15": {
|
|
290
|
+
"gpu": "Apple A16 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
291
|
+
"ios_min": "17.0", "arch": "arm64", "soc": "Apple A16 Bionic",
|
|
292
|
+
"flutter_notes": "Same GPU as iPhone 15 Plus."
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
"iPhone 14 Pro Max": {
|
|
296
|
+
"gpu": "Apple A16 GPU (6-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
297
|
+
"ios_min": "16.0", "arch": "arm64", "soc": "Apple A16 Bionic",
|
|
298
|
+
"flutter_notes": "ProMotion 120Hz. Watch widget build time — 8.3ms budget at 120Hz."
|
|
299
|
+
},
|
|
300
|
+
"iPhone 14 Pro": {
|
|
301
|
+
"gpu": "Apple A16 GPU (6-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
302
|
+
"ios_min": "16.0", "arch": "arm64", "soc": "Apple A16 Bionic",
|
|
303
|
+
"flutter_notes": "ProMotion 120Hz. Same 8.3ms frame budget warning."
|
|
304
|
+
},
|
|
305
|
+
"iPhone 14 Plus": {
|
|
306
|
+
"gpu": "Apple A15 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
307
|
+
"ios_min": "16.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
308
|
+
"flutter_notes": "60Hz only. 16.6ms budget."
|
|
309
|
+
},
|
|
310
|
+
"iPhone 14": {
|
|
311
|
+
"gpu": "Apple A15 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
312
|
+
"ios_min": "16.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
313
|
+
"flutter_notes": "60Hz only. Strong GPU — all widgets safe."
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
"iPhone 13 Pro Max": {
|
|
317
|
+
"gpu": "Apple A15 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
318
|
+
"ios_min": "15.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
319
|
+
"flutter_notes": "ProMotion 120Hz. A15 handles BackdropFilter at 120Hz smoothly."
|
|
320
|
+
},
|
|
321
|
+
"iPhone 13 Pro": {
|
|
322
|
+
"gpu": "Apple A15 GPU (5-core)", "gpu_tier": "flagship", "ram_gb": 6,
|
|
323
|
+
"ios_min": "15.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
324
|
+
"flutter_notes": "ProMotion 120Hz."
|
|
325
|
+
},
|
|
326
|
+
"iPhone 13": {
|
|
327
|
+
"gpu": "Apple A15 GPU (4-core)", "gpu_tier": "flagship", "ram_gb": 4,
|
|
328
|
+
"ios_min": "15.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
329
|
+
"flutter_notes": "4-core GPU. BackdropFilter in scrollable content OK at 60Hz."
|
|
330
|
+
},
|
|
331
|
+
"iPhone 13 mini": {
|
|
332
|
+
"gpu": "Apple A15 GPU (4-core)", "gpu_tier": "flagship", "ram_gb": 4,
|
|
333
|
+
"ios_min": "15.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
334
|
+
"flutter_notes": "Small screen reduces raster workload. All widgets safe."
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
"iPhone SE (3rd gen)": {
|
|
338
|
+
"gpu": "Apple A15 GPU (4-core)", "gpu_tier": "flagship", "ram_gb": 4,
|
|
339
|
+
"ios_min": "15.4", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
340
|
+
"flutter_notes": "Strong GPU in a budget body. 60Hz only."
|
|
341
|
+
},
|
|
342
|
+
"iPhone SE (2nd gen)": {
|
|
343
|
+
"gpu": "Apple A13 GPU (4-core)", "gpu_tier": "high", "ram_gb": 3,
|
|
344
|
+
"ios_min": "13.4", "arch": "arm64", "soc": "Apple A13 Bionic",
|
|
345
|
+
"flutter_notes": "3GB RAM — watch memory. Large image caches may trigger OOM."
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
"iPhone 12 Pro Max": {
|
|
349
|
+
"gpu": "Apple A14 GPU (4-core)", "gpu_tier": "high", "ram_gb": 6,
|
|
350
|
+
"ios_min": "14.1", "arch": "arm64", "soc": "Apple A14 Bionic",
|
|
351
|
+
"flutter_notes": "First 5nm chip. Excellent Flutter Skia and Impeller performance."
|
|
352
|
+
},
|
|
353
|
+
"iPhone 12 Pro": {
|
|
354
|
+
"gpu": "Apple A14 GPU (4-core)", "gpu_tier": "high", "ram_gb": 6,
|
|
355
|
+
"ios_min": "14.1", "arch": "arm64", "soc": "Apple A14 Bionic",
|
|
356
|
+
"flutter_notes": "Same GPU as 12 Pro Max."
|
|
357
|
+
},
|
|
358
|
+
"iPhone 12": {
|
|
359
|
+
"gpu": "Apple A14 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
360
|
+
"ios_min": "14.1", "arch": "arm64", "soc": "Apple A14 Bionic",
|
|
361
|
+
"flutter_notes": "4GB RAM. Stack depth and image cache matter."
|
|
362
|
+
},
|
|
363
|
+
"iPhone 12 mini": {
|
|
364
|
+
"gpu": "Apple A14 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
365
|
+
"ios_min": "14.1", "arch": "arm64", "soc": "Apple A14 Bionic",
|
|
366
|
+
"flutter_notes": "Same as iPhone 12."
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
"iPhone 11 Pro Max": {
|
|
370
|
+
"gpu": "Apple A13 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
371
|
+
"ios_min": "13.0", "arch": "arm64", "soc": "Apple A13 Bionic",
|
|
372
|
+
"flutter_notes": "Still very capable. BackdropFilter at 60Hz is smooth."
|
|
373
|
+
},
|
|
374
|
+
"iPhone 11 Pro": {
|
|
375
|
+
"gpu": "Apple A13 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
376
|
+
"ios_min": "13.0", "arch": "arm64", "soc": "Apple A13 Bionic",
|
|
377
|
+
"flutter_notes": "Same as 11 Pro Max."
|
|
378
|
+
},
|
|
379
|
+
"iPhone 11": {
|
|
380
|
+
"gpu": "Apple A13 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
381
|
+
"ios_min": "13.0", "arch": "arm64", "soc": "Apple A13 Bionic",
|
|
382
|
+
"flutter_notes": "Liquid Retina — same GPU, less display area to raster."
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
"iPhone XR": {
|
|
386
|
+
"gpu": "Apple A12 GPU (4-core)", "gpu_tier": "high", "ram_gb": 3,
|
|
387
|
+
"ios_min": "12.0", "arch": "arm64", "soc": "Apple A12 Bionic",
|
|
388
|
+
"flutter_notes": "3GB RAM is limit. Avoid large image grids. BackdropFilter OK at 60Hz."
|
|
389
|
+
},
|
|
390
|
+
"iPhone XS Max": {
|
|
391
|
+
"gpu": "Apple A12 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
392
|
+
"ios_min": "12.0", "arch": "arm64", "soc": "Apple A12 Bionic",
|
|
393
|
+
"flutter_notes": "Large OLED screen increases raster workload but A12 handles it."
|
|
394
|
+
},
|
|
395
|
+
"iPhone XS": {
|
|
396
|
+
"gpu": "Apple A12 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
397
|
+
"ios_min": "12.0", "arch": "arm64", "soc": "Apple A12 Bionic",
|
|
398
|
+
"flutter_notes": "Same GPU as XS Max."
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
"iPad Pro 12.9 (M4)": {
|
|
402
|
+
"gpu": "Apple M4 GPU (10-core)", "gpu_tier": "flagship", "ram_gb": 16,
|
|
403
|
+
"ios_min": "17.5", "arch": "arm64", "soc": "Apple M4",
|
|
404
|
+
"flutter_notes": "Desktop-class GPU. ProMotion 120Hz. Ideal for complex Flutter UIs."
|
|
405
|
+
},
|
|
406
|
+
"iPad Pro 11 (M4)": {
|
|
407
|
+
"gpu": "Apple M4 GPU (10-core)", "gpu_tier": "flagship", "ram_gb": 16,
|
|
408
|
+
"ios_min": "17.5", "arch": "arm64", "soc": "Apple M4",
|
|
409
|
+
"flutter_notes": "Same as 12.9 M4."
|
|
410
|
+
},
|
|
411
|
+
"iPad Air (M2)": {
|
|
412
|
+
"gpu": "Apple M2 GPU (9-core)", "gpu_tier": "flagship", "ram_gb": 8,
|
|
413
|
+
"ios_min": "17.4", "arch": "arm64", "soc": "Apple M2",
|
|
414
|
+
"flutter_notes": "M2 GPU — all Flutter widgets at any complexity."
|
|
415
|
+
},
|
|
416
|
+
"iPad (10th gen)": {
|
|
417
|
+
"gpu": "Apple A14 GPU (4-core)", "gpu_tier": "high", "ram_gb": 4,
|
|
418
|
+
"ios_min": "16.0", "arch": "arm64", "soc": "Apple A14 Bionic",
|
|
419
|
+
"flutter_notes": "Large screen (10.9\") increases raster cost. Watch full-screen BackdropFilter."
|
|
420
|
+
},
|
|
421
|
+
"iPad mini (6th gen)": {
|
|
422
|
+
"gpu": "Apple A15 GPU (4-core)", "gpu_tier": "flagship", "ram_gb": 4,
|
|
423
|
+
"ios_min": "15.0", "arch": "arm64", "soc": "Apple A15 Bionic",
|
|
424
|
+
"flutter_notes": "Small screen + A15 = very light GPU load."
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
"gpu_widget_risk": {
|
|
429
|
+
"BackdropFilter": {
|
|
430
|
+
"cost": "high",
|
|
431
|
+
"risky_on": ["entry", "low"],
|
|
432
|
+
"warning": "Triggers full offscreen compositing pass. Avoid in scrollable lists on low/entry-tier GPUs."
|
|
433
|
+
},
|
|
434
|
+
"ShaderMask": {
|
|
435
|
+
"cost": "high",
|
|
436
|
+
"risky_on": ["entry", "low"],
|
|
437
|
+
"warning": "Custom shader evaluation per pixel. Replace with pre-rendered assets on low-tier devices."
|
|
438
|
+
},
|
|
439
|
+
"Opacity": {
|
|
440
|
+
"cost": "medium",
|
|
441
|
+
"risky_on": ["entry"],
|
|
442
|
+
"warning": "Values between 0 and 1 (exclusive) trigger saveLayer(). Prefer AnimatedOpacity or FadeTransition."
|
|
443
|
+
},
|
|
444
|
+
"CustomPainter": {
|
|
445
|
+
"cost": "medium",
|
|
446
|
+
"risky_on": ["entry", "low"],
|
|
447
|
+
"warning": "Unbounded GPU cost. Profile with shouldRepaint() and use RepaintBoundary."
|
|
448
|
+
},
|
|
449
|
+
"ClipPath": {
|
|
450
|
+
"cost": "medium",
|
|
451
|
+
"risky_on": ["entry", "low"],
|
|
452
|
+
"warning": "Anti-aliased clipping is GPU-expensive. Prefer ClipRRect with simple radii."
|
|
453
|
+
},
|
|
454
|
+
"ImageFilter": {
|
|
455
|
+
"cost": "high",
|
|
456
|
+
"risky_on": ["entry", "low", "mid"],
|
|
457
|
+
"warning": "Per-frame GPU image processing. Cache result with ImageFiltered + RepaintBoundary."
|
|
458
|
+
},
|
|
459
|
+
"Stack": {
|
|
460
|
+
"cost": "low",
|
|
461
|
+
"risky_on": ["entry"],
|
|
462
|
+
"warning": "Deep stacks (>8 layers) with positioned children increase compositing cost on entry GPUs."
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DB_PATH = path.join(__dirname, 'data/device_gpu_db.json');
|
|
5
|
+
|
|
6
|
+
let _db = { android: {}, ios: {}, _meta: {}, gpu_widget_risk: {} };
|
|
7
|
+
try {
|
|
8
|
+
_db = JSON.parse(fs.readFileSync(DB_PATH, 'utf8'));
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.error(` ⚠️ Could not load device DB (${DB_PATH}): ${err.message}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Tier ranking — higher number = more capable GPU
|
|
14
|
+
const TIER_RANK = { entry: 0, low: 1, mid: 2, high: 3, flagship: 4 };
|
|
15
|
+
|
|
16
|
+
/** Flat list of every device, tagged with platform + a stable id. */
|
|
17
|
+
function listDevices() {
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const [name, d] of Object.entries(_db.android || {})) {
|
|
20
|
+
out.push({ id: `android:${name}`, platform: 'android', name, ...d });
|
|
21
|
+
}
|
|
22
|
+
for (const [name, d] of Object.entries(_db.ios || {})) {
|
|
23
|
+
out.push({ id: `ios:${name}`, platform: 'ios', name, ...d });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One representative device per unique (platform, gpu_tier) configuration.
|
|
30
|
+
* The performance projection only varies by gpu_tier (+ platform for risky
|
|
31
|
+
* tiers / API), so this covers the full capability spectrum without evaluating
|
|
32
|
+
* all ~75 devices. Used as the default target set for a module test.
|
|
33
|
+
*/
|
|
34
|
+
function listUniqueConfigs() {
|
|
35
|
+
const byKey = new Map();
|
|
36
|
+
for (const d of listDevices()) {
|
|
37
|
+
const key = `${d.platform}:${d.gpu_tier}`;
|
|
38
|
+
// Prefer the highest-RAM device as the tier's representative (most common spec).
|
|
39
|
+
const cur = byKey.get(key);
|
|
40
|
+
if (!cur || (d.ram_gb || 0) > (cur.ram_gb || 0)) byKey.set(key, d);
|
|
41
|
+
}
|
|
42
|
+
// Sort by platform then tier rank (weakest → strongest) for a readable report.
|
|
43
|
+
return [...byKey.values()].sort((a, b) =>
|
|
44
|
+
a.platform === b.platform
|
|
45
|
+
? tierRank(a.gpu_tier) - tierRank(b.gpu_tier)
|
|
46
|
+
: a.platform.localeCompare(b.platform)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Look up one device by its `platform:name` id. */
|
|
51
|
+
function getDevice(id) {
|
|
52
|
+
if (!id) return null;
|
|
53
|
+
const [platform, ...rest] = id.split(':');
|
|
54
|
+
const name = rest.join(':');
|
|
55
|
+
const d = (_db[platform] || {})[name];
|
|
56
|
+
return d ? { id, platform, name, ...d } : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Best-effort fuzzy match of a raw device-model string reported by the running
|
|
61
|
+
* app (e.g. "SM-A135F", "Pixel 6", "iPhone14,5") against known devices.
|
|
62
|
+
* Returns the matched device or null.
|
|
63
|
+
*/
|
|
64
|
+
function matchDeviceModel(raw) {
|
|
65
|
+
if (!raw) return null;
|
|
66
|
+
const norm = String(raw).toLowerCase().replace(/[\s_-]+/g, '');
|
|
67
|
+
const all = listDevices();
|
|
68
|
+
|
|
69
|
+
// 1. Exact-ish name contains
|
|
70
|
+
for (const d of all) {
|
|
71
|
+
const dn = d.name.toLowerCase().replace(/[\s_-]+/g, '');
|
|
72
|
+
if (dn === norm || dn.includes(norm) || norm.includes(dn)) return d;
|
|
73
|
+
}
|
|
74
|
+
// 2. Token overlap (model numbers, "pixel", "galaxy", etc.)
|
|
75
|
+
const tokens = String(raw).toLowerCase().split(/[\s_-]+/).filter((t) => t.length > 2);
|
|
76
|
+
let best = null, bestScore = 0;
|
|
77
|
+
for (const d of all) {
|
|
78
|
+
const dl = d.name.toLowerCase();
|
|
79
|
+
const score = tokens.reduce((s, t) => s + (dl.includes(t) ? 1 : 0), 0);
|
|
80
|
+
if (score > bestScore) { bestScore = score; best = d; }
|
|
81
|
+
}
|
|
82
|
+
return bestScore > 0 ? best : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function tierRank(tier) {
|
|
86
|
+
return TIER_RANK[tier] ?? 2;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** GPU widget-risk lookup table shipped in the DB. */
|
|
90
|
+
function widgetRisk() {
|
|
91
|
+
return _db.gpu_widget_risk || {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { listDevices, listUniqueConfigs, getDevice, matchDeviceModel, tierRank, widgetRisk, TIER_RANK };
|