Skip to content

Commit

Permalink
[app-surface] Support creating AppSurface directly with Canvas and Of…
Browse files Browse the repository at this point in the history
…fscreenCanvas.
  • Loading branch information
jinleili committed Nov 6, 2024
1 parent f3c7acb commit 450e87a
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions app-surface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[package]
name = "app-surface"
authors = ["jinleili"]
description = "Integrate wgpu into your existing iOS and Android apps."
description = "Integrate wgpu into your existing iOS, Android and Web apps without relying on winit."
edition = "2021"
version = "1.2.4"
version = "1.3.0"
rust-version = "1.76"
repository = "https://github.com/jinleili/wgpu-in-app"
keywords = ["android", "SurfaceView", "ios", "macos", "wgpu"]
keywords = ["android", "SurfaceView", "CAMetalLayer", "Canvas", "wgpu"]
license = "MIT"

[lib]
Expand All @@ -23,6 +23,14 @@ log.workspace = true
glam.workspace = true
pollster.workspace = true
wgpu.workspace = true
web-sys = { workspace = true, features = [
"Document",
"Window",
"Location",
"HtmlCanvasElement",
"OffscreenCanvas",
], optional = true }
wasm-bindgen = { workspace = true, optional = true }

[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))'.dependencies]
winit.workspace = true
Expand All @@ -43,6 +51,7 @@ ash.workspace = true

[target.'cfg(target_arch = "wasm32")'.dependencies]
winit.workspace = true
web-sys = { workspace = true, features = ["Document", "Window", "Location"] }
web-sys = { workspace = true }
wasm-bindgen.workspace = true
web-time.workspace = true
raw-window-handle.workspace = true
1 change: 0 additions & 1 deletion app-surface/src/ios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use core_graphics::{base::CGFloat, geometry::CGRect};
use libc::c_void;
use objc::{runtime::Object, *};
use std::marker::Sync;
use std::sync::Arc;

#[repr(C)]
pub struct IOSViewObj {
Expand Down
3 changes: 3 additions & 0 deletions app-surface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ pub use touch::*;
mod app_surface;
pub use app_surface::*;

#[cfg(target_arch = "wasm32")]
pub mod web;

#[repr(C)]
#[derive(Debug)]
pub struct ViewSize {
Expand Down
104 changes: 104 additions & 0 deletions app-surface/src/web/canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::SendSyncWrapper;
use raw_window_handle::{
HasDisplayHandle, HasWindowHandle, RawWindowHandle, WebCanvasWindowHandle, WindowHandle,
};
use std::{ops::Deref, ptr::NonNull};
use wasm_bindgen::{JsCast, JsValue};

#[derive(Debug)]
pub struct CanvasWrapper(SendSyncWrapper<Canvas>);
impl CanvasWrapper {
pub fn new(canvas: Canvas) -> Self {
CanvasWrapper(SendSyncWrapper(canvas))
}
}

impl Deref for CanvasWrapper {
type Target = Canvas;

fn deref(&self) -> &Self::Target {
&self.0 .0
}
}

#[derive(Debug, Clone)]
pub struct Canvas {
pub(crate) element: web_sys::HtmlCanvasElement,
pub scale_factor: f32,
pub(crate) handle: u32,
}

#[allow(dead_code)]
impl Canvas {
pub fn new(element_id: &str, handle: u32) -> Self {
// 0 is reserved for window itself.
assert!(handle > 0);

// 添加 `raw-window-handle` 需要的属性/值.
let (element, scale_factor) = Self::get_canvas_element(element_id);
element
.set_attribute("data-raw-handle", handle.to_string().as_str())
.unwrap();

Self {
element,
scale_factor,
handle,
}
}

pub fn get_canvas_element(element_id: &str) -> (web_sys::HtmlCanvasElement, f32) {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let element = document
.get_element_by_id(element_id)
.expect("页面中找不到 canvas 元素 ");

let canvas = element.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
let scale_factor = window.device_pixel_ratio() as f32;

(canvas, scale_factor)
}

#[inline]
pub fn handle(&self) -> u32 {
self.handle
}

pub fn logical_resolution(&self) -> (u32, u32) {
let width = self.element.width();
let height = self.element.height();
(width, height)
}
}

impl Deref for Canvas {
type Target = web_sys::HtmlCanvasElement;

fn deref(&self) -> &Self::Target {
&self.element
}
}

impl HasWindowHandle for Canvas {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
let value: &JsValue = &self.element;
let obj: NonNull<std::ffi::c_void> = NonNull::from(value).cast();
let handle = WebCanvasWindowHandle::new(obj);
let raw = RawWindowHandle::WebCanvas(handle);
unsafe { Ok(WindowHandle::borrow_raw(raw)) }
}
}

impl HasDisplayHandle for Canvas {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
use raw_window_handle::{DisplayHandle, RawDisplayHandle, WebDisplayHandle};
let handle = WebDisplayHandle::new();
let raw = RawDisplayHandle::Web(handle);
unsafe { Ok(DisplayHandle::borrow_raw(raw)) }
}
}
148 changes: 148 additions & 0 deletions app-surface/src/web/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use crate::IASDQContext;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::ops::Deref;

mod canvas;
pub use canvas::*;

mod offscreen_canvas;
pub use offscreen_canvas::*;

pub struct AppSurface {
pub view: ViewObj,
pub scale_factor: f32,
pub ctx: IASDQContext,
}

impl AppSurface {
pub async fn new(view: ViewObj) -> Self {
let (scale_factor, logical_size) = match view {
ViewObj::Canvas(ref canvas) => (canvas.scale_factor, canvas.logical_resolution()),
ViewObj::Offscreen(ref offscreen) => {
(offscreen.scale_factor, offscreen.logical_resolution())
}
};
let physical_size = (
(logical_size.0 as f32 * scale_factor) as u32,
(logical_size.1 as f32 * scale_factor) as u32,
);

let backends = wgpu::Backends::BROWSER_WEBGPU;
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
..Default::default()
});
let surface = unsafe {
instance
.create_surface_unsafe(match view {
ViewObj::Canvas(ref canvas) => wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: canvas.display_handle().unwrap().into(),
raw_window_handle: canvas.window_handle().unwrap().into(),
},
ViewObj::Offscreen(ref offscreen) => wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: offscreen.display_handle().unwrap().into(),
raw_window_handle: offscreen.window_handle().unwrap().into(),
},
})
.expect("Surface creation failed")
};

let ctx = crate::create_iasdq_context(instance, surface, physical_size).await;

Self {
view,
scale_factor,
ctx,
}
}

pub fn get_view_size(&self) -> (u32, u32) {
let (scale_factor, logical_size) = match self.view {
ViewObj::Canvas(ref canvas) => (canvas.scale_factor, canvas.logical_resolution()),
ViewObj::Offscreen(ref offscreen) => {
(offscreen.scale_factor, offscreen.logical_resolution())
}
};
(
(logical_size.0 as f32 * scale_factor) as u32,
(logical_size.1 as f32 * scale_factor) as u32,
)
}
}

/// 用 Canvas id 创建 AppSurface
///
/// element_id: 存在于当前页面中的 canvas 元素的 id
/// handle: 用于 WebGPU 的 raw handle number, 0 是保留的值, 不能使用
pub async fn app_surface_from_canvas(element_id: &str, handle: u32) -> AppSurface {
let wrapper = CanvasWrapper::new(Canvas::new(element_id, handle));
AppSurface::new(ViewObj::Canvas(wrapper)).await
}

// 封装 ViewObj 来同时支持 Canvas 与 Offscreen
#[derive(Debug)]
pub enum ViewObj {
Canvas(CanvasWrapper),
Offscreen(OffscreenCanvasWrapper),
}

impl ViewObj {
pub fn from_canvas(canvas: Canvas) -> Self {
ViewObj::Canvas(CanvasWrapper::new(canvas))
}

pub fn from_offscreen_canvas(canvas: OffscreenCanvas) -> Self {
ViewObj::Offscreen(OffscreenCanvasWrapper::new(canvas))
}
}

#[derive(Clone, Debug)]
pub(crate) struct SendSyncWrapper<T>(pub(crate) T);

unsafe impl<T> Send for SendSyncWrapper<T> {}
unsafe impl<T> Sync for SendSyncWrapper<T> {}

impl Deref for AppSurface {
type Target = IASDQContext;
fn deref(&self) -> &Self::Target {
&self.ctx
}
}

impl crate::SurfaceFrame for AppSurface {
fn view_size(&self) -> crate::ViewSize {
let size = self.get_view_size();
crate::ViewSize {
width: size.0,
height: size.1,
}
}

fn resize_surface(&mut self) {
let size = self.get_view_size();
self.ctx.config.width = size.0;
self.ctx.config.height = size.1;
self.surface.configure(&self.device, &self.config);
}

fn resize_surface_by_size(&mut self, size: (u32, u32)) {
self.ctx.config.width = size.0;
self.ctx.config.height = size.1;
self.surface.configure(&self.device, &self.config);
}

fn normalize_touch_point(&self, touch_point_x: f32, touch_point_y: f32) -> (f32, f32) {
let size = self.get_view_size();
(
touch_point_x * self.scale_factor / size.0 as f32,
touch_point_y * self.scale_factor / size.1 as f32,
)
}

fn get_current_frame_view(
&self,
view_format: Option<wgpu::TextureFormat>,
) -> (wgpu::SurfaceTexture, wgpu::TextureView) {
self.create_current_frame_view(&self.device, &self.surface, &self.config, view_format)
}
}
Loading

0 comments on commit 450e87a

Please sign in to comment.