Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add OpenHarmony platform support #261

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
19 changes: 17 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion benches/buffer_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")) },
Expand Down
5 changes: 4 additions & 1 deletion examples/utils/winit_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
18 changes: 18 additions & 0 deletions examples/winit_ohos.rs
Original file line number Diff line number Diff line change
@@ -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())
}
2 changes: 2 additions & 0 deletions src/backend_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,6 @@ make_dispatch! {
Web(backends::web::WebDisplayImpl<D>, backends::web::WebImpl<D, W>, backends::web::BufferImpl<'a, D, W>),
#[cfg(target_os = "redox")]
Orbital(D, backends::orbital::OrbitalImpl<D, W>, backends::orbital::BufferImpl<'a, D, W>),
#[cfg(target_env = "ohos")]
Ohos(D,backends::ohos::OpenHarmonyImpl<D, W>, backends::ohos::BufferImpl<'a, D, W>),
}
2 changes: 2 additions & 0 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<D: HasDisplayHandle> ContextInterface<D> for D {
fn new(display: D) -> Result<Self, InitError<D>> {
Expand Down
170 changes: 170 additions & 0 deletions src/backends/ohos.rs
Original file line number Diff line number Diff line change
@@ -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<D, W> {
native_window: NativeWindow,
window: W,
_display: PhantomData<D>,
}

impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for OpenHarmonyImpl<D, W> {
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<Self, InitError<W>> {
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<BufferImpl<'_, D, W>, 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<Vec<u32>, SoftBufferError> {
Err(SoftBufferError::Unimplemented)
}
}

pub struct BufferImpl<'a, D: ?Sized, W> {
native_window_buffer: NativeWindowBuffer<'a>,
buffer: Vec<u32>,
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()
}
}
Loading