Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ calendar = []
deeplink = []
microphone = []
media_session = []
# Render GPUI windows into images (`gpui_mobile::offscreen`). Needs the vendored
# `gpui-pre-wgpu` (see `[patch.crates-io]`), which exposes its headless renderer.
offscreen = ["gpui_wgpu/offscreen", "dep:image"]

[lib]
name = "gpui_mobile"
Expand All @@ -61,6 +64,8 @@ log = { version = "0.4", features = ["kv_unstable_serde", "serde"] }
parking_lot = "0.12"
futures = "0.3"
raw-window-handle = "0.6"
# Same major version as `gpui-pre-wgpu`, whose `RgbaImage` `offscreen` returns.
image = { version = "0.25", default-features = false, optional = true }

# ── GPUI core crate (provides Platform trait, event types, geometry, etc.) ───
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false }
Expand Down Expand Up @@ -139,3 +144,10 @@ strip = true
[profile.dev]
opt-level = 0
debug = true

# `gpui-pre-wgpu` 0.3.8 with its headless renderer behind an `offscreen` feature
# instead of `test-support` / `bench-support`, and a constructor that takes the
# wgpu instance (iOS needs Metal). Applications that enable `offscreen` must
# patch it the same way.
[patch.crates-io]
gpui-pre-wgpu = { path = "vendor/gpui-pre-wgpu" }
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,35 @@ cd android/gradle && ./gradlew assembleDebug
| Android (arm64) | ✅ | API 26+ | Vulkan (preferred), GL ES 3.0 |
| Android (armv7/x86_64) | ⚠️ Untested | API 26+ | Vulkan / GL ES |

## Rendering views to images

The `offscreen` feature adds `gpui_mobile::offscreen::OffscreenWindow`, a GPUI
window drawn into a texture instead of onto the screen, for turning a view into
an image (a share card, a long screenshot):

```rust
// Open with the view to capture, then render from an async task
// (`render` drives the window's own frame callbacks, which borrow the `App`).
let window = OffscreenWindow::open(size(px(390.), px(844.)), 3.0, WindowAppearance::Light, cx, |window, cx| {
cx.new(|cx| MyView::new(window, cx))
})?;
window.layout(size(px(390.), px(844.)))?; // lay out without reading pixels back, e.g. to measure content
let image = window.render(size(px(390.), content_height))?; // image::RgbaImage
window.close(cx);
```

It needs `gpui-pre-wgpu`'s headless renderer, which upstream only builds for
tests and benchmarks and which picks a Vulkan/GL device that iOS lacks. This
repository vendors `gpui-pre-wgpu` 0.3.8 with that renderer behind an
`offscreen` feature and a constructor that takes the wgpu instance
(`vendor/gpui-pre-wgpu`). An application enabling `offscreen` must patch it the
same way:

```toml
[patch.crates-io]
gpui-pre-wgpu = { git = "https://github.com/longbridge/gpui-mobile" }
```

## Screenshots

Running on a Motorola Edge 50 Pro (Android, Vulkan):
Expand Down
4 changes: 4 additions & 0 deletions src/android/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,10 @@ impl Platform for AndroidPlatform {
_handle: AnyWindowHandle,
_options: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> {
#[cfg(feature = "offscreen")]
if let Some(window) = crate::offscreen::take_pending(&_options) {
return Ok(window);
}
// On Android the native window is created by the system via
// MainEvent::InitWindow, not by the application. If a window
// already exists we wrap it in an AndroidPlatformWindow and hand
Expand Down
4 changes: 4 additions & 0 deletions src/ios/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ impl Platform for IosPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> {
#[cfg(feature = "offscreen")]
if let Some(window) = crate::offscreen::take_pending(&options) {
return Ok(window);
}
let window = Box::new(IosWindow::new(handle, options)?);
// Register the window with FFI layer so Objective-C can access it for rendering
window.register_with_ffi();
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub(crate) mod frame_demand;
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
pub(crate) mod frame_pacer;
pub mod momentum;
#[cfg(all(feature = "offscreen", any(target_os = "ios", target_os = "android")))]
pub mod offscreen;
pub mod packages;
pub mod platform_view;
pub mod target_platform;
Expand Down
Loading
Loading