From 8155dd2b7bfc688d3008d748897ad96f78b8dfcd Mon Sep 17 00:00:00 2001 From: richerfu Date: Thu, 20 Feb 2025 09:26:31 +0800 Subject: [PATCH] feat: add OpenHarmony platform support --- Cargo.toml | 19 +++- benches/buffer_mut.rs | 2 +- build.rs | 2 +- examples/utils/winit_app.rs | 5 +- examples/winit.rs | 5 +- examples/winit_ohos.rs | 18 ++++ src/backend_dispatch.rs | 2 + src/backends/mod.rs | 2 + src/backends/ohos.rs | 170 ++++++++++++++++++++++++++++++++++++ 9 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 examples/winit_ohos.rs create mode 100644 src/backends/ohos.rs diff --git a/Cargo.toml b/Cargo.toml index f90cbbae..6897ff04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,9 @@ tracing = { version = "0.1.41", default-features = false } bytemuck = "1.12.3" ndk = "0.9.0" +[target.'cfg(target_env = "ohos")'.dependencies] +ohos-native-window-binding = { git = "https://github.com/ohos-rs/ohos-native-bindings.git", branch = "master"} + [target.'cfg(all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))))'.dependencies] as-raw-xcb-connection = { version = "1.0.0", optional = true } bytemuck = { version = "1.12.3", optional = true } @@ -139,12 +142,19 @@ criterion = { version = "0.4.0", default-features = false, features = [ "cargo_bench_support", ] } web-time = "1.0.0" -winit = "0.30.0" +winit = { git = "https://github.com/richerfu/winit.git", branch = "feat-ohos-30" } [target.'cfg(target_os = "android")'.dev-dependencies] -winit = { version = "0.30.0", features = ["android-native-activity"] } +winit = { git = "https://github.com/richerfu/winit.git", branch = "feat-ohos-30", features = [ + "android-native-activity", +] } android-activity = "0.6" +[target.'cfg(target_env = "ohos")'.dev-dependencies] +winit = { git = "https://github.com/richerfu/winit.git", branch = "feat-ohos-30" } +openharmony-ability = { version = "0.0.4" } +openharmony-ability-derive = { version = "0.0.3" } + [dev-dependencies.image] version = "0.25.0" # Disable rayon on web @@ -169,6 +179,11 @@ members = ["run-wasm"] name = "winit_android" crate-type = ["cdylib"] +[[example]] +# Run with `ohrs build -- --example winit_android` +name = "winit_ohos" +crate-type = ["cdylib"] + [[example]] # Run with `cargo apk r --example winit_multithread_android` name = "winit_multithread_android" diff --git a/benches/buffer_mut.rs b/benches/buffer_mut.rs index beaea990..be8054e3 100644 --- a/benches/buffer_mut.rs +++ b/benches/buffer_mut.rs @@ -9,7 +9,7 @@ fn buffer_mut(c: &mut Criterion) { let _ = c; } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64", target_env = "ohos")))] { use criterion::black_box; use softbuffer::{Context, Surface}; diff --git a/build.rs b/build.rs index 9f046eb2..a3c3e6b8 100644 --- a/build.rs +++ b/build.rs @@ -5,7 +5,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(wayland_platform)"); cfg_aliases::cfg_aliases! { - free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox"))) }, + free_unix: { all(unix, not(any(target_vendor = "apple", target_os = "android", target_os = "redox", target_env = "ohos"))) }, kms_platform: { all(feature = "kms", free_unix, not(target_arch = "wasm32")) }, x11_platform: { all(feature = "x11", free_unix, not(target_arch = "wasm32")) }, wayland_platform: { all(feature = "wayland", free_unix, not(target_arch = "wasm32")) }, diff --git a/examples/utils/winit_app.rs b/examples/utils/winit_app.rs index 3b4cb4d7..1f42a555 100644 --- a/examples/utils/winit_app.rs +++ b/examples/utils/winit_app.rs @@ -10,11 +10,14 @@ use winit::window::{Window, WindowAttributes, WindowId}; /// Run a Winit application. #[allow(unused_mut)] pub(crate) fn run_app(event_loop: EventLoop<()>, mut app: impl ApplicationHandler<()> + 'static) { - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64", target_env = "ohos")))] event_loop.run_app(&mut app).unwrap(); #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] winit::platform::web::EventLoopExtWebSys::spawn_app(event_loop, app); + + #[cfg(target_env = "ohos")] + winit::platform::ohos::EventLoopExtOpenHarmony::spawn_app(event_loop, app); } /// Create a window from a set of window attributes. diff --git a/examples/winit.rs b/examples/winit.rs index 27a3cefb..59348cb3 100644 --- a/examples/winit.rs +++ b/examples/winit.rs @@ -6,11 +6,14 @@ use winit::keyboard::{Key, NamedKey}; #[path = "utils/winit_app.rs"] mod winit_app; -#[cfg(not(target_os = "android"))] +#[cfg(not(any(target_os = "android", target_env = "ohos")))] fn main() { entry(EventLoop::new().unwrap()) } +#[cfg(any(target_os = "android", target_env = "ohos"))] +fn main() {} + pub(crate) fn entry(event_loop: EventLoop<()>) { let app = winit_app::WinitAppBuilder::with_init( |elwt| { diff --git a/examples/winit_ohos.rs b/examples/winit_ohos.rs new file mode 100644 index 00000000..45f7c7be --- /dev/null +++ b/examples/winit_ohos.rs @@ -0,0 +1,18 @@ +#![cfg(target_env = "ohos")] + +pub use winit::platform::ohos::{ability::OpenHarmonyApp, EventLoopBuilderExtOpenHarmony}; +use winit::{event_loop::EventLoop, platform::ohos::ability::ability}; + +#[path = "winit.rs"] +mod desktop_example; + +/// Run with `ohrs build -- --example winit_ohos` +#[ability] +fn openharmony(app: OpenHarmonyApp) { + let mut builder = EventLoop::builder(); + + // Install the Android event loop extension if necessary. + builder.with_openharmony_app(app); + + desktop_example::entry(builder.build().unwrap()) +} diff --git a/src/backend_dispatch.rs b/src/backend_dispatch.rs index a86832ea..1f67bb24 100644 --- a/src/backend_dispatch.rs +++ b/src/backend_dispatch.rs @@ -194,4 +194,6 @@ make_dispatch! { Web(backends::web::WebDisplayImpl, backends::web::WebImpl, backends::web::BufferImpl<'a, D, W>), #[cfg(target_os = "redox")] Orbital(D, backends::orbital::OrbitalImpl, backends::orbital::BufferImpl<'a, D, W>), + #[cfg(target_env = "ohos")] + Ohos(D,backends::ohos::OpenHarmonyImpl, backends::ohos::BufferImpl<'a, D, W>), } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 703401d0..c34aa1e6 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -17,6 +17,8 @@ pub(crate) mod web; pub(crate) mod win32; #[cfg(x11_platform)] pub(crate) mod x11; +#[cfg(target_env = "ohos")] +pub(crate) mod ohos; impl ContextInterface for D { fn new(display: D) -> Result> { diff --git a/src/backends/ohos.rs b/src/backends/ohos.rs new file mode 100644 index 00000000..44e11336 --- /dev/null +++ b/src/backends/ohos.rs @@ -0,0 +1,170 @@ +//! Implementation of software buffering for Android. + +use std::marker::PhantomData; +use std::num::{NonZeroI32, NonZeroU32}; + +#[cfg(doc)] +use raw_window_handle::OhosNdkWindowHandle; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle}; + +use crate::error::InitError; +use crate::{BufferInterface, Rect, SoftBufferError, SurfaceInterface}; +use ohos_native_window_binding::{NativeBufferFormat, NativeWindow, NativeWindowBuffer}; + +/// The handle to a window for software buffering. +pub struct OpenHarmonyImpl { + native_window: NativeWindow, + window: W, + _display: PhantomData, +} + +impl SurfaceInterface for OpenHarmonyImpl { + type Context = D; + type Buffer<'a> + = BufferImpl<'a, D, W> + where + Self: 'a; + + /// Create a new [`AndroidImpl`] from an [`OhosNdkWindowHandle`]. + fn new(window: W, _display: &Self::Context) -> Result> { + let raw = window.window_handle()?.as_raw(); + let RawWindowHandle::OhosNdk(a) = raw else { + return Err(InitError::Unsupported(window)); + }; + + // Acquire a new owned reference to the window, that will be freed on drop. + // SAFETY: We have confirmed that the window handle is valid. + let native_window = NativeWindow::clone_from_ptr(a.native_window.as_ptr()); + + Ok(Self { + native_window, + _display: PhantomData, + window, + }) + } + + #[inline] + fn window(&self) -> &W { + &self.window + } + + /// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`]. + fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> { + let (width, height) = (|| { + let width = NonZeroI32::try_from(width).ok()?; + let height = NonZeroI32::try_from(height).ok()?; + Some((width, height)) + })() + .ok_or(SoftBufferError::SizeOutOfRange { width, height })?; + + self.native_window + .set_buffer_geometry(width.into(), height.into()) + .map_err(|err| { + SoftBufferError::PlatformError( + Some("Failed to set buffer geometry on NativeWindow".to_owned()), + Some(Box::new(err)), + ) + }) + } + + fn buffer_mut(&mut self) -> Result, SoftBufferError> { + let native_window_buffer = self.native_window.request_buffer(None).map_err(|err| { + SoftBufferError::PlatformError( + Some("Failed to request native window buffer".to_owned()), + Some(Box::new(err)), + ) + })?; + + if !matches!( + native_window_buffer.format(), + // These are the only formats we support + NativeBufferFormat::RGBA_8888 | NativeBufferFormat::RGBX_8888 + ) { + return Err(SoftBufferError::PlatformError( + Some(format!( + "Unexpected buffer format {:?}, please call \ + .resize() first to change it to RGBx8888", + native_window_buffer.format() + )), + None, + )); + } + let size = (native_window_buffer.width() * native_window_buffer.height()) + .try_into() + .map_err(|e| { + SoftBufferError::PlatformError( + Some("Failed to convert width to u32".to_owned()), + Some(Box::new(e)), + ) + })?; + let buffer = vec![0; size]; + + Ok(BufferImpl { + native_window_buffer, + buffer, + marker: PhantomData, + }) + } + + /// Fetch the buffer from the window. + fn fetch(&mut self) -> Result, SoftBufferError> { + Err(SoftBufferError::Unimplemented) + } +} + +pub struct BufferImpl<'a, D: ?Sized, W> { + native_window_buffer: NativeWindowBuffer<'a>, + buffer: Vec, + marker: PhantomData<(&'a D, &'a W)>, +} + +unsafe impl<'a, D, W> Send for BufferImpl<'a, D, W> {} + +impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W> { + #[inline] + fn pixels(&self) -> &[u32] { + &self.buffer + } + + #[inline] + fn pixels_mut(&mut self) -> &mut [u32] { + &mut self.buffer + } + + #[inline] + fn age(&self) -> u8 { + 0 + } + + // TODO: This function is pretty slow this way + fn present(mut self) -> Result<(), SoftBufferError> { + let input_lines = self.buffer.chunks(self.native_window_buffer.width() as _); + for (output, input) in self + .native_window_buffer + .lines() + // Unreachable as we checked before that this is a valid, mappable format + .unwrap() + .zip(input_lines) + { + // .lines() removed the stride + assert_eq!(output.len(), input.len() * 4); + + for (i, pixel) in input.iter().enumerate() { + // Swizzle colors from RGBX to BGR + let [b, g, r, _] = pixel.to_le_bytes(); + output[i * 4].write(b); + output[i * 4 + 1].write(g); + output[i * 4 + 2].write(r); + // TODO alpha? + } + } + Ok(()) + } + + fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> { + // TODO: Android requires the damage rect _at lock time_ + // Since we're faking the backing buffer _anyway_, we could even fake the surface lock + // and lock it here (if it doesn't influence timings). + self.present() + } +}