Hand Android's develop frame to the compositor as a texture again
With Skia drawing pre-rotated on wgpu's Vulkan swapchain, Android no longer needs to draw with Skia over OpenGL, which was the only reason the develop view read its frame back through memory (TD-1). So `unstable-wgpu-29` moves back to the common slint dependency. The android-activity backend then builds `SkiaRenderer::default_wgpu_29`, and `shared_gpu` loses its Android arm. The one wgpu device is handed to Slint through `BackendSelector::require_wgpu_29` on both platforms. `slint::android::init_with_event_listener` runs before `dr_ui::run`, so the selector reaches the Android adapter before its window exists. `renderer-femtovg-wgpu` stays desktop-only, since Android has no FemtoVG. The two `#[cfg(target_os = "android")]` readbacks in `develop::render` (the frame through `export_pixels` and the focus overlay through `read_overlay`) are gone. `read_overlay` stays for the tests that check what the overlay marks. Built for arm64 and release-signed. Not yet run on the tablet.
This commit is contained in:
@@ -470,7 +470,7 @@ impl FocusPeakPass {
|
|||||||
// TEXTURE_BINDING to be sampled by the compositor.
|
// TEXTURE_BINDING to be sampled by the compositor.
|
||||||
// RENDER_ATTACHMENT is not used by anything here and is required
|
// RENDER_ATTACHMENT is not used by anything here and is required
|
||||||
// anyway: Slint rejects an imported texture without it. COPY_SRC
|
// anyway: Slint rejects an imported texture without it. COPY_SRC
|
||||||
// is for `read_overlay` and its two callers.
|
// is for `read_overlay` and the tests that call it.
|
||||||
usage: wgpu::TextureUsages::STORAGE_BINDING
|
usage: wgpu::TextureUsages::STORAGE_BINDING
|
||||||
| wgpu::TextureUsages::TEXTURE_BINDING
|
| wgpu::TextureUsages::TEXTURE_BINDING
|
||||||
| wgpu::TextureUsages::RENDER_ATTACHMENT
|
| wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||||
@@ -490,18 +490,11 @@ impl FocusPeakPass {
|
|||||||
/// TRACES: AC-8
|
/// TRACES: AC-8
|
||||||
/// Copy the overlay to the CPU, as RGBA8 rows with no padding.
|
/// Copy the overlay to the CPU, as RGBA8 rows with no padding.
|
||||||
///
|
///
|
||||||
/// **Two callers, and neither is the desktop display path.** The tests
|
/// **The tests below are the only caller, and never the display path.** An
|
||||||
/// below are one: an overlay is a claim about which pixels are sharp, and
|
/// overlay is a claim about which pixels are sharp, and there is no way to
|
||||||
/// there is no way to check that claim without looking at the pixels. The
|
/// check that claim without looking at the pixels. On screen, on desktop
|
||||||
/// other is the Android develop view, which reads the *frame* back for the
|
/// and Android alike, the overlay reaches the compositor as a texture and
|
||||||
/// reasons `technical-debt.md` TD-1 records — wgpu's Android swapchain
|
/// ARCH §6.1 holds. (Android read it back here until TD-1 was paid off.)
|
||||||
/// tears a portrait window, so Slint is not drawing with wgpu there and no
|
|
||||||
/// texture can be handed over. An overlay that stayed on the device on a
|
|
||||||
/// platform where the picture underneath it does not would simply never be
|
|
||||||
/// seen.
|
|
||||||
///
|
|
||||||
/// On desktop nothing calls this, and ARCH §6.1 holds on the path that
|
|
||||||
/// matters: the overlay reaches the compositor as a texture.
|
|
||||||
pub fn read_overlay(&self) -> Result<(Vec<u8>, u32, u32), GpuError> {
|
pub fn read_overlay(&self) -> Result<(Vec<u8>, u32, u32), GpuError> {
|
||||||
let Some(layer) = self.layers[self.current].as_ref() else {
|
let Some(layer) = self.layers[self.current].as_ref() else {
|
||||||
return Err(GpuError::Readback("no overlay has been rendered".into()));
|
return Err(GpuError::Readback("no overlay has been rendered".into()));
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! module doc said for eight releases that it held no pipeline and no masks.
|
//! module doc said for eight releases that it held no pipeline and no masks.
|
||||||
//! It holds both now, plus demosaic, detail, segmentation masks, two
|
//! It holds both now, plus demosaic, detail, segmentation masks, two
|
||||||
//! histograms and focus peaking. The zero-copy claim is still the one that
|
//! histograms and focus peaking. The zero-copy claim is still the one that
|
||||||
//! matters, and TD-1 records the one platform where it does not hold.
|
//! matters, and since TD-1 was paid off it holds on Android too.
|
||||||
//!
|
//!
|
||||||
//! Deliberately free of UI dependencies (ARCH §6.5a). The texture is handed
|
//! Deliberately free of UI dependencies (ARCH §6.5a). The texture is handed
|
||||||
//! out as a `wgpu::Texture`; who composites it is not this crate's concern.
|
//! out as a `wgpu::Texture`; who composites it is not this crate's concern.
|
||||||
|
|||||||
+37
-37
File diff suppressed because one or more lines are too long
+11
-13
@@ -87,14 +87,16 @@ rusqlite.workspace = true
|
|||||||
# Consequence worth stating plainly: the desktop app now needs a working wgpu
|
# Consequence worth stating plainly: the desktop app now needs a working wgpu
|
||||||
# adapter to open a window at all. Slint refuses a CPU adapter for this
|
# adapter to open a window at all. Slint refuses a CPU adapter for this
|
||||||
# renderer unless `SLINT_WGPU_CPU` is set in the environment.
|
# renderer unless `SLINT_WGPU_CPU` is set in the environment.
|
||||||
# TEST BUILD: the wgpu renderer features have moved to the desktop-only
|
#
|
||||||
# dependency below. On Android they made `AndroidWindowAdapter` choose
|
# On Android the renderer is Skia, not FemtoVG — the android-activity backend
|
||||||
# `SkiaRenderer::default_wgpu_29`, and so put the app on wgpu's Vulkan
|
# has no other — so `renderer-femtovg-wgpu` stays with winit below, and only
|
||||||
# swapchain — which hardcodes `preTransform = IDENTITY` (gfx-rs/wgpu#3345).
|
# `unstable-wgpu-29` is shared. With it, `AndroidWindowAdapter` builds
|
||||||
# Without them the Android backend uses `SkiaRenderer::default`, which on
|
# `SkiaRenderer::default_wgpu_29`: Skia drawing on wgpu's Vulkan swapchain,
|
||||||
# Android resolves to Skia over OpenGL, where the driver owns the display
|
# which is what lets it sample our texture. That swapchain used to tear in
|
||||||
# rotation and there is no transform to get wrong.
|
# portrait on a landscape-mounted panel (gfx-rs/wgpu#3345), and Android drew
|
||||||
slint = { workspace = true, features = ["compat-1-2"] }
|
# with Skia over OpenGL behind a readback instead; the patched wgpu-hal and
|
||||||
|
# Skia renderer in third_party/ pre-rotate it, which closed TD-1.
|
||||||
|
slint = { workspace = true, features = ["compat-1-2", "unstable-wgpu-29"] }
|
||||||
wgpu.workspace = true
|
wgpu.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
# `SettingsError` distinguishes an io failure from a malformed file, which the
|
# `SettingsError` distinguishes an io failure from a malformed file, which the
|
||||||
@@ -110,11 +112,7 @@ serde_norway = { workspace = true, optional = true }
|
|||||||
# under `cfg(target_os = "android")`, so this only has to name the feature;
|
# under `cfg(target_os = "android")`, so this only has to name the feature;
|
||||||
# cargo resolves it away entirely on desktop.
|
# cargo resolves it away entirely on desktop.
|
||||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||||
slint = { workspace = true, features = [
|
slint = { workspace = true, features = ["backend-winit", "renderer-femtovg-wgpu"] }
|
||||||
"backend-winit",
|
|
||||||
"renderer-femtovg-wgpu",
|
|
||||||
"unstable-wgpu-29",
|
|
||||||
] }
|
|
||||||
# The manual's `file:` URL (`manual::desktop_open`): a Windows path and a
|
# The manual's `file:` URL (`manual::desktop_open`): a Windows path and a
|
||||||
# path with a space in it are both URLs only after encoding, and this is the
|
# path with a space in it are both URLs only after encoding, and this is the
|
||||||
# crate the workspace already encodes URLs with.
|
# crate the workspace already encodes URLs with.
|
||||||
|
|||||||
@@ -278,31 +278,10 @@ impl DevelopSession {
|
|||||||
// in `AdjustPass`'s texture descriptor — so a failure here is a
|
// in `AdjustPass`'s texture descriptor — so a failure here is a
|
||||||
// descriptor that drifted, not anything the caller did. Say that,
|
// descriptor that drifted, not anything the caller did. Say that,
|
||||||
// rather than surfacing "InvalidUsage" to a photographer.
|
// rather than surfacing "InvalidUsage" to a photographer.
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
{
|
|
||||||
slint::Image::try_from(texture.clone())
|
slint::Image::try_from(texture.clone())
|
||||||
.map_err(|e| format!("the render target is not importable by the compositor: {e}"))
|
.map_err(|e| format!("the render target is not importable by the compositor: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Android draws with Skia over OpenGL and cannot sample a
|
|
||||||
// `wgpu::Texture`, so the frame comes back through memory. See
|
|
||||||
// `crate::shared_gpu`'s Android arm for why that is the trade on this
|
|
||||||
// platform. The pass still runs on the GPU; only this last hop does not.
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
{
|
|
||||||
let _ = texture;
|
|
||||||
let (rgba, w, h) = self
|
|
||||||
.adjust
|
|
||||||
.export_pixels()
|
|
||||||
.map_err(|e| format!("reading the rendered frame back: {e}"))?;
|
|
||||||
let mut buf = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::new(w, h);
|
|
||||||
let wanted = (w as usize) * (h as usize) * 4;
|
|
||||||
let src = &rgba[..wanted.min(rgba.len())];
|
|
||||||
buf.make_mut_bytes()[..src.len()].copy_from_slice(src);
|
|
||||||
Ok(slint::Image::from_rgba8(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TRACES: FR-DSP-7
|
/// TRACES: FR-DSP-7
|
||||||
/// Count the frame that is currently on the canvas.
|
/// Count the frame that is currently on the canvas.
|
||||||
///
|
///
|
||||||
@@ -463,8 +442,6 @@ impl DevelopSession {
|
|||||||
.ok()?
|
.ok()?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
{
|
|
||||||
// A layer over the canvas rather than a tint in it, so nothing
|
// A layer over the canvas rather than a tint in it, so nothing
|
||||||
// here reaches the histogram or an export — see `FocusPeakPass`
|
// here reaches the histogram or an export — see `FocusPeakPass`
|
||||||
// for the whole of that argument.
|
// for the whole of that argument.
|
||||||
@@ -473,25 +450,6 @@ impl DevelopSession {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Android draws with Skia over OpenGL and cannot sample a
|
|
||||||
// `wgpu::Texture`, so the overlay follows the frame it belongs to back
|
|
||||||
// through memory (technical-debt.md TD-1). The measurement still
|
|
||||||
// happens on the GPU; only this last hop does not.
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
{
|
|
||||||
let _ = overlay;
|
|
||||||
let (rgba, w, h) = pass
|
|
||||||
.read_overlay()
|
|
||||||
.inspect_err(|e| log::warn!("reading the focus overlay back: {e}"))
|
|
||||||
.ok()?;
|
|
||||||
let mut buf = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::new(w, h);
|
|
||||||
let wanted = (w as usize) * (h as usize) * 4;
|
|
||||||
let src = &rgba[..wanted.min(rgba.len())];
|
|
||||||
buf.make_mut_bytes()[..src.len()].copy_from_slice(src);
|
|
||||||
Some(slint::Image::from_rgba8(buf))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Render the *whole* frame for the crop overlay to be drawn over.
|
/// Render the *whole* frame for the crop overlay to be drawn over.
|
||||||
///
|
///
|
||||||
/// Crop mode cannot use [`Self::render`]: that applies the crop, so the
|
/// Crop mode cannot use [`Self::render`]: that applies the crop, so the
|
||||||
|
|||||||
+9
-45
@@ -1071,7 +1071,13 @@ enum PointsUpdate {
|
|||||||
///
|
///
|
||||||
/// `None` means develop is unavailable and the viewer falls back to embedded
|
/// `None` means develop is unavailable and the viewer falls back to embedded
|
||||||
/// previews — the same degradation as a machine with no adapter at all.
|
/// previews — the same degradation as a machine with no adapter at all.
|
||||||
#[cfg(not(target_os = "android"))]
|
///
|
||||||
|
/// **The same on Android**, where Slint draws with Skia rather than FemtoVG
|
||||||
|
/// but on this device all the same. It used to open a device of its own
|
||||||
|
/// there and read every frame back (TD-1), because wgpu's Vulkan swapchain
|
||||||
|
/// could not pre-rotate and a portrait window tore on the tablet's
|
||||||
|
/// landscape-mounted panel. The patched wgpu-hal and Skia renderer in
|
||||||
|
/// `third_party/` pre-rotate it now; see technical-debt.md TD-1.
|
||||||
fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
||||||
let shared = match pollster::block_on(dr_gpu::GpuContext::new_shared()) {
|
let shared = match pollster::block_on(dr_gpu::GpuContext::new_shared()) {
|
||||||
Ok(shared) => shared,
|
Ok(shared) => shared,
|
||||||
@@ -1112,45 +1118,6 @@ fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
|||||||
Some(ctx)
|
Some(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRACES: FR-DSP-1 | AC-8
|
|
||||||
/// Open the compute device on Android — and do **not** give it to Slint.
|
|
||||||
///
|
|
||||||
/// # Why this platform is different
|
|
||||||
///
|
|
||||||
/// The desktop version above exists so one `wgpu::Texture` can be written by
|
|
||||||
/// the adjust pass and sampled by the compositor without a round-trip. That
|
|
||||||
/// requires Slint to be drawing with wgpu, and on Android drawing with wgpu
|
|
||||||
/// means drawing on wgpu's Vulkan swapchain — which hardcodes
|
|
||||||
/// `preTransform = IDENTITY` (gfx-rs/wgpu#3345).
|
|
||||||
///
|
|
||||||
/// On a tablet whose panel is mounted landscape, portrait then presents an
|
|
||||||
/// unrotated buffer, every present returns `VK_SUBOPTIMAL_KHR`, and the frames
|
|
||||||
/// arrive torn. Measured on the device: portrait sits on `composition=CLIENT`
|
|
||||||
/// with `bufferTransform=ROT_270`, landscape on `composition=DEVICE` with
|
|
||||||
/// `ROT_180`, and only portrait tore. Taking Slint off wgpu — it then uses
|
|
||||||
/// Skia over OpenGL, where the driver owns the rotation — fixed it.
|
|
||||||
///
|
|
||||||
/// # What it costs, and why that is the right trade here
|
|
||||||
///
|
|
||||||
/// Without a shared device the display path needs a readback:
|
|
||||||
/// `AdjustPass::export_pixels` instead of `Image::try_from(texture)`. That is
|
|
||||||
/// the transfer ARCH §6.1 and AC-8 exist to avoid, and it is still the better
|
|
||||||
/// bargain on this platform — the alternative is not a faster develop view, it
|
|
||||||
/// is a torn one, in the orientation a tablet is mostly held in.
|
|
||||||
///
|
|
||||||
/// The compute passes are untouched: demosaic and adjust still run on the GPU,
|
|
||||||
/// on this device. Only the last hop to the screen goes through memory.
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
fn shared_gpu() -> Option<dr_gpu::GpuContext> {
|
|
||||||
match pollster::block_on(dr_gpu::GpuContext::new_headless()) {
|
|
||||||
Ok(ctx) => Some(ctx),
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("no GPU for the develop passes: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TRACES: M-13 | M-14
|
/// TRACES: M-13 | M-14
|
||||||
/// Build and run the viewer.
|
/// Build and run the viewer.
|
||||||
pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
||||||
@@ -1181,11 +1148,8 @@ pub fn run(paths: Vec<PathBuf>) -> Result<()> {
|
|||||||
// app still browses through the preview path, just without develop.
|
// app still browses through the preview path, just without develop.
|
||||||
//
|
//
|
||||||
// Timed rather than moved: this is a Vulkan instance, an adapter
|
// Timed rather than moved: this is a Vulkan instance, an adapter
|
||||||
// enumeration and a device request, none of which can be deferred on the
|
// enumeration and a device request, none of which can be deferred
|
||||||
// desktop because the backend must be chosen before a window exists. On
|
// because the backend must be chosen before a window exists.
|
||||||
// Android it could be — nothing shares that device with the compositor
|
|
||||||
// (TD-1) — but "could be" is not "costs enough to be worth it", and this
|
|
||||||
// line is what will say which.
|
|
||||||
let gpu = shared_gpu();
|
let gpu = shared_gpu();
|
||||||
log::info!("gpu opened in {} ms", launch_began.elapsed().as_millis());
|
log::info!("gpu opened in {} ms", launch_began.elapsed().as_millis());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user