web: Support wgpu on web
Add wgpu feature to web build (disabled by default currently).
This commit is contained in:
parent
df11cd6a04
commit
4141909bcb
|
@ -3102,6 +3102,8 @@ dependencies = [
|
|||
"raw-window-handle",
|
||||
"ruffle_core",
|
||||
"ruffle_render_common_tess",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
|
@ -3140,6 +3142,7 @@ dependencies = [
|
|||
"ruffle_core",
|
||||
"ruffle_render_canvas",
|
||||
"ruffle_render_webgl",
|
||||
"ruffle_render_wgpu",
|
||||
"ruffle_web_common",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
|
|
@ -6,22 +6,34 @@ edition = "2021"
|
|||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.23.14", default-features = false }
|
||||
log = "0.4"
|
||||
ruffle_core = { path = "../../core" }
|
||||
ruffle_core = { path = "../../core", default-features = false }
|
||||
ruffle_render_common_tess = { path = "../common_tess" }
|
||||
futures = "0.3.17"
|
||||
bytemuck = { version = "1.7.0", features = ["derive"] }
|
||||
raw-window-handle = "0.3.3"
|
||||
clap = { version = "3.0.0-beta.5", optional = true }
|
||||
enum-map = "1.1.1"
|
||||
|
||||
# wgpu desktop
|
||||
# desktop
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures]
|
||||
version = "0.3.17"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.image]
|
||||
version = "0.23.14"
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu]
|
||||
version = "0.11"
|
||||
features = ["spirv"]
|
||||
|
||||
# wgpu wasm
|
||||
# wasm
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = ["HtmlCanvasElement"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wgpu]
|
||||
version = "0.11"
|
||||
features = ["spirv-web"]
|
||||
|
|
|
@ -5,11 +5,8 @@ use ruffle_core::backend::render::{
|
|||
use ruffle_core::shape_utils::DistilledShape;
|
||||
use ruffle_core::swf;
|
||||
use std::{borrow::Cow, num::NonZeroU32};
|
||||
use target::TextureTarget;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use futures::executor::block_on;
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
|
||||
use crate::pipelines::Pipelines;
|
||||
use crate::target::{RenderTarget, RenderTargetFrame, SwapChainTarget};
|
||||
|
@ -276,7 +273,29 @@ enum DrawType {
|
|||
}
|
||||
|
||||
impl WgpuRenderBackend<SwapChainTarget> {
|
||||
pub fn for_window<W: HasRawWindowHandle>(
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub async fn for_canvas(canvas: &web_sys::HtmlCanvasElement) -> Result<Self, Error> {
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::BROWSER_WEBGPU);
|
||||
let surface = unsafe { instance.create_surface_from_canvas(canvas) };
|
||||
let descriptors = Self::build_descriptors(
|
||||
wgpu::Backends::BROWSER_WEBGPU,
|
||||
instance,
|
||||
Some(&surface),
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let target = SwapChainTarget::new(
|
||||
surface,
|
||||
descriptors.surface_format,
|
||||
(1, 1),
|
||||
&descriptors.device,
|
||||
);
|
||||
Self::new(descriptors, target)
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn for_window<W: raw_window_handle::HasRawWindowHandle>(
|
||||
window: &W,
|
||||
size: (u32, u32),
|
||||
backend: wgpu::Backends,
|
||||
|
@ -291,7 +310,7 @@ impl WgpuRenderBackend<SwapChainTarget> {
|
|||
}
|
||||
let instance = wgpu::Instance::new(backend);
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
let descriptors = block_on(Self::build_descriptors(
|
||||
let descriptors = futures::executor::block_on(Self::build_descriptors(
|
||||
backend,
|
||||
instance,
|
||||
Some(&surface),
|
||||
|
@ -308,7 +327,8 @@ impl WgpuRenderBackend<SwapChainTarget> {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuRenderBackend<TextureTarget> {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl WgpuRenderBackend<target::TextureTarget> {
|
||||
pub fn for_offscreen(
|
||||
size: (u32, u32),
|
||||
backend: wgpu::Backends,
|
||||
|
@ -322,14 +342,14 @@ impl WgpuRenderBackend<TextureTarget> {
|
|||
);
|
||||
}
|
||||
let instance = wgpu::Instance::new(backend);
|
||||
let descriptors = block_on(Self::build_descriptors(
|
||||
let descriptors = futures::executor::block_on(Self::build_descriptors(
|
||||
backend,
|
||||
instance,
|
||||
None,
|
||||
power_preference,
|
||||
trace_path,
|
||||
))?;
|
||||
let target = TextureTarget::new(&descriptors.device, size);
|
||||
let target = target::TextureTarget::new(&descriptors.device, size);
|
||||
Self::new(descriptors, target)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::utils::BufferDimensions;
|
||||
use futures::executor::block_on;
|
||||
use image::buffer::ConvertBuffer;
|
||||
use image::{Bgra, ImageBuffer, RgbaImage};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use image::{buffer::ConvertBuffer, Bgra, ImageBuffer, RgbaImage};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait RenderTargetFrame: Debug {
|
||||
|
@ -109,6 +109,7 @@ impl RenderTarget for SwapChainTarget {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[derive(Debug)]
|
||||
pub struct TextureTarget {
|
||||
size: wgpu::Extent3d,
|
||||
|
@ -118,17 +119,21 @@ pub struct TextureTarget {
|
|||
buffer_dimensions: BufferDimensions,
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[derive(Debug)]
|
||||
pub struct TextureTargetFrame(wgpu::TextureView);
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
type BgraImage = ImageBuffer<Bgra<u8>, Vec<u8>>;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl RenderTargetFrame for TextureTargetFrame {
|
||||
fn view(&self) -> &wgpu::TextureView {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl TextureTarget {
|
||||
pub fn new(device: &wgpu::Device, size: (u32, u32)) -> Self {
|
||||
let buffer_dimensions = BufferDimensions::new(size.0 as usize, size.1 as usize);
|
||||
|
@ -168,7 +173,7 @@ impl TextureTarget {
|
|||
pub fn capture(&self, device: &wgpu::Device) -> Option<RgbaImage> {
|
||||
let buffer_future = self.buffer.slice(..).map_async(wgpu::MapMode::Read);
|
||||
device.poll(wgpu::Maintain::Wait);
|
||||
match block_on(buffer_future) {
|
||||
match futures::executor::block_on(buffer_future) {
|
||||
Ok(()) => {
|
||||
let map = self.buffer.slice(..).get_mapped_range();
|
||||
let mut buffer = Vec::with_capacity(
|
||||
|
@ -195,6 +200,7 @@ impl TextureTarget {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl RenderTarget for TextureTarget {
|
||||
type Frame = TextureTargetFrame;
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use bytemuck::Pod;
|
||||
use futures::{
|
||||
executor::{LocalPool, LocalSpawner},
|
||||
task::LocalSpawnExt,
|
||||
};
|
||||
use std::{convert::TryInto, marker::PhantomData, mem};
|
||||
use wgpu::util::StagingBelt;
|
||||
|
||||
|
@ -13,8 +9,7 @@ pub struct UniformBuffer<T: Pod> {
|
|||
blocks: Vec<Block>,
|
||||
buffer_layout: wgpu::BindGroupLayout,
|
||||
staging_belt: StagingBelt,
|
||||
executor: LocalPool,
|
||||
spawner: LocalSpawner,
|
||||
executor: Executor,
|
||||
aligned_uniforms_size: u32,
|
||||
cur_block: usize,
|
||||
cur_offset: u32,
|
||||
|
@ -32,10 +27,6 @@ impl<T: Pod> UniformBuffer<T> {
|
|||
|
||||
/// Creates a new `UniformBuffer` with the given uniform layout.
|
||||
pub fn new(buffer_layout: wgpu::BindGroupLayout, uniform_alignment: u32) -> Self {
|
||||
// Create local executor for uniform uploads.
|
||||
let executor = LocalPool::new();
|
||||
let spawner = executor.spawner();
|
||||
|
||||
// Calculate alignment of uniforms.
|
||||
let align_mask = uniform_alignment - 1;
|
||||
let aligned_uniforms_size = (Self::UNIFORMS_SIZE as u32 + align_mask) & !align_mask;
|
||||
|
@ -43,8 +34,7 @@ impl<T: Pod> UniformBuffer<T> {
|
|||
Self {
|
||||
blocks: Vec::with_capacity(8),
|
||||
buffer_layout,
|
||||
executor,
|
||||
spawner,
|
||||
executor: Executor::new(),
|
||||
staging_belt: StagingBelt::new(u64::from(Self::BLOCK_SIZE) / 2),
|
||||
aligned_uniforms_size,
|
||||
cur_block: 0,
|
||||
|
@ -63,7 +53,7 @@ impl<T: Pod> UniformBuffer<T> {
|
|||
pub fn reset(&mut self) {
|
||||
self.cur_block = 0;
|
||||
self.cur_offset = 0;
|
||||
let _ = self.spawner.spawn_local(self.staging_belt.recall());
|
||||
self.executor.spawn_local(self.staging_belt.recall());
|
||||
self.executor.run_until_stalled();
|
||||
}
|
||||
|
||||
|
@ -146,3 +136,49 @@ struct Block {
|
|||
buffer: wgpu::Buffer,
|
||||
bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
struct Executor {
|
||||
executor: futures::executor::LocalPool,
|
||||
spawner: futures::executor::LocalSpawner,
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl Executor {
|
||||
fn new() -> Self {
|
||||
let executor = futures::executor::LocalPool::new();
|
||||
let spawner = executor.spawner();
|
||||
Self { executor, spawner }
|
||||
}
|
||||
|
||||
fn spawn_local<Fut>(&self, future: Fut)
|
||||
where
|
||||
Fut: std::future::Future<Output = ()> + 'static,
|
||||
{
|
||||
use futures::task::LocalSpawnExt;
|
||||
let _ = self.spawner.spawn_local(future);
|
||||
}
|
||||
|
||||
fn run_until_stalled(&mut self) {
|
||||
self.executor.run_until_stalled();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
struct Executor;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
impl Executor {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn spawn_local<Fut>(&self, future: Fut)
|
||||
where
|
||||
Fut: std::future::Future<Output = ()> + 'static,
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
|
||||
fn run_until_stalled(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ pub struct BufferDimensions {
|
|||
}
|
||||
|
||||
impl BufferDimensions {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
let bytes_per_pixel = size_of::<u32>();
|
||||
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
||||
|
|
|
@ -24,6 +24,7 @@ lzma = ["ruffle_core/lzma"]
|
|||
# web features
|
||||
canvas = ["ruffle_render_canvas"]
|
||||
webgl = ["ruffle_render_webgl"]
|
||||
wgpu = ["ruffle_render_wgpu"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4"
|
||||
|
@ -36,6 +37,7 @@ log = { version = "0.4", features = ["serde"] }
|
|||
ruffle_render_canvas = { path = "../render/canvas", optional = true }
|
||||
ruffle_web_common = { path = "common" }
|
||||
ruffle_render_webgl = { path = "../render/webgl", optional = true }
|
||||
ruffle_render_wgpu = { path = "../render/wgpu", optional = true }
|
||||
url = "2.2.2"
|
||||
wasm-bindgen = { version = "=0.2.78", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4.28"
|
||||
|
@ -55,7 +57,7 @@ version = "0.3.50"
|
|||
features = [
|
||||
"AddEventListenerOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioParam", "AudioProcessingEvent", "AudioContext", "AudioDestinationNode",
|
||||
"AudioNode", "CanvasRenderingContext2d", "ChannelMergerNode", "ChannelSplitterNode", "CssStyleDeclaration", "Document",
|
||||
"Element", "Event", "EventTarget", "GainNode", "HtmlCanvasElement", "HtmlElement", "HtmlImageElement", "MouseEvent",
|
||||
"Element", "Event", "EventTarget", "GainNode", "Gpu", "HtmlCanvasElement", "HtmlElement", "HtmlImageElement", "MouseEvent",
|
||||
"Navigator", "Node", "Performance", "PointerEvent", "ScriptProcessorNode", "UiEvent", "Window", "Location", "HtmlFormElement",
|
||||
"KeyboardEvent", "Path2d", "CanvasGradient", "CanvasPattern", "SvgMatrix", "SvgsvgElement", "Response", "Request", "RequestInit",
|
||||
"Blob", "BlobPropertyBag", "Storage", "WheelEvent", "ImageData"]
|
||||
|
|
|
@ -1230,8 +1230,9 @@ export class RufflePlayer extends HTMLElement {
|
|||
}
|
||||
|
||||
protected debugPlayerInfo(): string {
|
||||
return `Allows script access: ${this.options?.allowScriptAccess ?? false
|
||||
}\n`;
|
||||
return `Allows script access: ${
|
||||
this.options?.allowScriptAccess ?? false
|
||||
}\n`;
|
||||
}
|
||||
|
||||
private setMetadata(metadata: MovieMetadata) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"serve": "webpack serve"
|
||||
"serve": "webpack serve --port 8081"
|
||||
},
|
||||
"dependencies": {
|
||||
"ruffle-core": "^0.1.0"
|
||||
|
@ -16,4 +16,4 @@
|
|||
"style-loader": "^3.3.0",
|
||||
"webpack-cli": "^4.8.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ mod storage;
|
|||
mod ui;
|
||||
|
||||
use generational_arena::{Arena, Index};
|
||||
use js_sys::{Array, Function, Object, Uint8Array};
|
||||
use js_sys::{Array, Function, Object, Promise, Uint8Array};
|
||||
use ruffle_core::backend::{
|
||||
audio::{AudioBackend, NullAudioBackend},
|
||||
render::RenderBackend,
|
||||
|
@ -205,22 +205,23 @@ pub struct Ruffle(Index);
|
|||
|
||||
#[wasm_bindgen]
|
||||
impl Ruffle {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
parent: HtmlElement,
|
||||
js_player: JavascriptPlayer,
|
||||
config: &JsValue,
|
||||
) -> Result<Ruffle, JsValue> {
|
||||
if RUFFLE_GLOBAL_PANIC.is_completed() {
|
||||
// If an actual panic happened, then we can't trust the state it left us in.
|
||||
// Prevent future players from loading so that they can inform the user about the error.
|
||||
return Err("Ruffle is panicking!".into());
|
||||
}
|
||||
set_panic_handler();
|
||||
|
||||
pub fn new(parent: HtmlElement, js_player: JavascriptPlayer, config: &JsValue) -> Promise {
|
||||
let config: Config = config.into_serde().unwrap_or_default();
|
||||
wasm_bindgen_futures::future_to_promise(async move {
|
||||
if RUFFLE_GLOBAL_PANIC.is_completed() {
|
||||
// If an actual panic happened, then we can't trust the state it left us in.
|
||||
// Prevent future players from loading so that they can inform the user about the error.
|
||||
return Err("Ruffle is panicking!".into());
|
||||
}
|
||||
set_panic_handler();
|
||||
|
||||
Ruffle::new_internal(parent, js_player, config).map_err(|_| "Error creating player".into())
|
||||
let ruffle = Ruffle::new_internal(parent, js_player, config)
|
||||
.await
|
||||
.map_err(|_| JsValue::from("Error creating player"))?;
|
||||
Ok(JsValue::from(ruffle))
|
||||
})
|
||||
}
|
||||
|
||||
/// Stream an arbitrary movie file from (presumably) the Internet.
|
||||
|
@ -450,7 +451,7 @@ impl Ruffle {
|
|||
}
|
||||
|
||||
impl Ruffle {
|
||||
fn new_internal(
|
||||
async fn new_internal(
|
||||
parent: HtmlElement,
|
||||
js_player: JavascriptPlayer,
|
||||
config: Config,
|
||||
|
@ -461,7 +462,7 @@ impl Ruffle {
|
|||
let window = web_sys::window().ok_or("Expected window")?;
|
||||
let document = window.document().ok_or("Expected document")?;
|
||||
|
||||
let (canvas, renderer) = create_renderer(&document)?;
|
||||
let (canvas, renderer) = create_renderer(&document).await?;
|
||||
parent
|
||||
.append_child(&canvas.clone().into())
|
||||
.into_js_result()?;
|
||||
|
@ -1216,12 +1217,37 @@ fn external_to_js_value(external: ExternalValue) -> JsValue {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_renderer(
|
||||
async fn create_renderer(
|
||||
document: &web_sys::Document,
|
||||
) -> Result<(HtmlCanvasElement, Box<dyn RenderBackend>), Box<dyn Error>> {
|
||||
#[cfg(not(any(feature = "canvas", feature = "webgl")))]
|
||||
std::compile_error!("You must enable one of the render backend features (e.g., webgl).");
|
||||
|
||||
// Try to create a backend, falling through to the next backend on failure.
|
||||
// We must recreate the canvas each attempt, as only a single context may be created per canvas
|
||||
// with `getContext`.
|
||||
#[cfg(feature = "wgpu")]
|
||||
{
|
||||
// Check that we have access to WebGPU (navigator.gpu should exist).
|
||||
if web_sys::window()
|
||||
.ok_or(JsValue::FALSE)
|
||||
.and_then(|window| js_sys::Reflect::has(&window.navigator(), &JsValue::from_str("gpu")))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
log::info!("Creating wgpu renderer...");
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.into_js_result()?
|
||||
.dyn_into()
|
||||
.map_err(|_| "Expected HtmlCanvasElement")?;
|
||||
|
||||
match ruffle_render_wgpu::WgpuRenderBackend::for_canvas(&canvas).await {
|
||||
Ok(renderer) => return Ok((canvas, Box::new(renderer))),
|
||||
Err(error) => log::error!("Error creating wgpu renderer: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to create a backend, falling through to the next backend on failure.
|
||||
// We must recreate the canvas each attempt, as only a single context may be created per canvas
|
||||
// with `getContext`.
|
||||
|
|
Loading…
Reference in New Issue