| author | |
| committer | |
| log | 20174448f624d39ffedda6ad2291f07b96542644 |
| tree | 21a40c982e7614309df3c330ac47e951e43bd755 |
| parent | 4534c84f0003ef1bc0827ad8d1ab37a6ce30655f |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover) |
On a device without Vulkan, wgpu kept one EGL surface across resizes, and the
emulator's EGL took the window's new size a resize late. A cold start that
opened with the keyboard showing drew the page into buffers of the old height,
leaving it blank above a black band until a tap; the next resize left a stray
copy of the page's title box near the bottom. A resize under GL now makes a
new surface, as GLSurfaceView does, and the Android check fails when the
reopened page leaves the screen black.
- The page draws on Android 8 as soon as it opens, without a tap.
Fixes #153
Assisted-by: claude-opus-5.52 files changed, 29 insertions(+), 3 deletions(-)
apps/android/check.py+11-1| ... | @@ -6,12 +6,14 @@ the caller starts and stops: | ... | @@ -6,12 +6,14 @@ the caller starts and stops: |
| 6 | 6 | ||
| 7 | It types a token into the page, waits until the section reports it saved, force-stops the app, | 7 | It types a token into the page, waits until the section reports it saved, force-stops the app, |
| 8 | turns the network off, starts it again and screenshots the page, then reads the section file | 8 | turns the network off, starts it again and screenshots the page, then reads the section file |
| 9 | back with onestore's inspect example and looks for the token in it. | 9 | back with onestore's inspect example and looks for the token in it. The page must have drawn |
| 10 | where the keyboard was, which GL on Android 8 once left black. | ||
| 10 | """ | 11 | """ |
| 11 | import argparse | 12 | import argparse |
| 12 | import json | 13 | import json |
| 13 | from pathlib import Path | 14 | from pathlib import Path |
| 14 | import secrets | 15 | import secrets |
| 16 | import struct | ||
| 15 | import subprocess | 17 | import subprocess |
| 16 | import time | 18 | import time |
| 17 | 19 | ||
| ... | @@ -70,6 +72,11 @@ def main(): | ... | @@ -70,6 +72,11 @@ def main(): |
| 70 | adb('shell', 'cmd', 'connectivity', 'airplane-mode', 'enable') | 72 | adb('shell', 'cmd', 'connectivity', 'airplane-mode', 'enable') |
| 71 | launch() | 73 | launch() |
| 72 | (output / 'reopened.png').write_bytes(adb('exec-out', 'screencap', '-p', binary=True)) | 74 | (output / 'reopened.png').write_bytes(adb('exec-out', 'screencap', '-p', binary=True)) |
| 75 | # A raw capture: width and height, then RGBA rows after a header whose size varies by release. | ||
| 76 | screen = adb('exec-out', 'screencap', binary=True) | ||
| 77 | columns, rows = struct.unpack_from('<2I', screen) | ||
| 78 | pixel = len(screen) - columns * rows * 4 + (rows * 3 // 4 * columns + columns // 2) * 4 | ||
| 79 | drawn = any(screen[pixel:pixel + 3]) | ||
| 73 | section = output / 'section.one' | 80 | section = output / 'section.one' |
| 74 | section.write_bytes(adb('exec-out', 'run-as', PACKAGE, 'cat', SECTION, binary=True)) | 81 | section.write_bytes(adb('exec-out', 'run-as', PACKAGE, 'cat', SECTION, binary=True)) |
| 75 | (output / 'logcat.txt').write_text(adb('logcat', '-d')) | 82 | (output / 'logcat.txt').write_text(adb('logcat', '-d')) |
| ... | @@ -84,11 +91,14 @@ def main(): | ... | @@ -84,11 +91,14 @@ def main(): |
| 84 | 'intact': inspected.endswith(' 0 checksum mismatches'), | 91 | 'intact': inspected.endswith(' 0 checksum mismatches'), |
| 85 | # Text is stored as UTF-16, or as single bytes where it fits them. | 92 | # Text is stored as UTF-16, or as single bytes where it fits them. |
| 86 | 'tokenInSection': token.encode('utf-16-le') in data or token.encode() in data, | 93 | 'tokenInSection': token.encode('utf-16-le') in data or token.encode() in data, |
| 94 | 'drawn': drawn, | ||
| 87 | } | 95 | } |
| 88 | (output / 'result.json').write_text(json.dumps(result, indent=2) + '\n') | 96 | (output / 'result.json').write_text(json.dumps(result, indent=2) + '\n') |
| 89 | print(json.dumps(result, indent=2)) | 97 | print(json.dumps(result, indent=2)) |
| 90 | if not (result['intact'] and result['tokenInSection']): | 98 | if not (result['intact'] and result['tokenInSection']): |
| 91 | raise SystemExit('The edit did not survive') | 99 | raise SystemExit('The edit did not survive') |
| 100 | if not drawn: | ||
| 101 | raise SystemExit('The reopened page left the screen black') | ||
| 92 | 102 | ||
| 93 | 103 | ||
| 94 | if __name__ == '__main__': | 104 | if __name__ == '__main__': |
crates/mobile/src/lib.rs+18-2| ... | @@ -1167,8 +1167,8 @@ pub unsafe extern "C" fn sb_view_free(view: *mut View) { | ... | @@ -1167,8 +1167,8 @@ pub unsafe extern "C" fn sb_view_free(view: *mut View) { |
| 1167 | 1167 | ||
| 1168 | #[unsafe(no_mangle)] | 1168 | #[unsafe(no_mangle)] |
| 1169 | pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale: f32) { | 1169 | pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale: f32) { |
| 1170 | let gpu = gpu(); | 1170 | let guard = gpu(); |
| 1171 | let Some(gpu) = gpu.as_ref() else { return }; | 1171 | let Some(gpu) = guard.as_ref() else { return }; |
| 1172 | let Some(pixels) = report(view_pixels( | 1172 | let Some(pixels) = report(view_pixels( |
| 1173 | [width, height], | 1173 | [width, height], |
| 1174 | scale, | 1174 | scale, |
| ... | @@ -1182,6 +1182,22 @@ pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale | ... | @@ -1182,6 +1182,22 @@ pub extern "C" fn sb_view_resize(view: &mut View, width: f32, height: f32, scale |
| 1182 | let _ = report(canvas.page.scale_factor_changed(scale)); | 1182 | let _ = report(canvas.page.scale_factor_changed(scale)); |
| 1183 | } | 1183 | } |
| 1184 | let _ = report(canvas.page.resized(pixels)); | 1184 | let _ = report(canvas.page.resized(pixels)); |
| 1185 | #[cfg(target_os = "android")] | ||
| 1186 | if gpu.adapter.get_info().backend == wgpu::Backend::Gl | ||
| 1187 | && let Some(Presentation { | ||
| 1188 | surface, | ||
| 1189 | _window: window, | ||
| 1190 | .. | ||
| 1191 | }) = view.presentation.take() | ||
| 1192 | { | ||
| 1193 | // The emulator's EGL takes a window's new size late, so GL makes a new surface, as | ||
| 1194 | // GLSurfaceView does. | ||
| 1195 | drop(surface); | ||
| 1196 | drop(guard); | ||
| 1197 | // SAFETY: `window` holds a reference until the new presentation has acquired its own. | ||
| 1198 | view.presentation = report(unsafe { Presentation::new(window.0.as_ptr(), pixels) }); | ||
| 1199 | return; | ||
| 1200 | } | ||
| 1185 | if let Some(presentation) = &mut view.presentation { | 1201 | if let Some(presentation) = &mut view.presentation { |
| 1186 | presentation.config.width = pixels[0]; | 1202 | presentation.config.width = pixels[0]; |
| 1187 | presentation.config.height = pixels[1]; | 1203 | presentation.config.height = pixels[1]; |