web: Add preferred renderer config

This commit is contained in:
nosamu 2023-03-07 06:01:41 -06:00 committed by relrelb
parent 569e822044
commit 1e3701279c
4 changed files with 169 additions and 76 deletions

View File

@ -35,4 +35,5 @@ export const DEFAULT_CONFIG: Required<BaseLoadOptions> = {
publicPath: null, publicPath: null,
polyfills: true, polyfills: true,
playerVersion: null, playerVersion: null,
preferredRenderer: null,
}; };

View File

@ -117,6 +117,39 @@ export const enum WindowMode {
Gpu = "gpu", Gpu = "gpu",
} }
/**
* The render backend of a Ruffle player.
*/
export const enum RenderBackend {
/*
* A draft API that is currently unavailable, but will be preferred if available in the future.
* Should behave the same as wgpu-webgl, except with lower overhead and thus better performance.
*/
WebGpu = "webgpu",
/*
* The most featureful and currently preferred backend.
* Rendering is done the same way as in the desktop app, then translated to WebGL on-the-fly.
*/
WgpuWebgl = "wgpu-webgl",
/*
* A vanilla WebGL backend. Was previously the default backend,
* but is now used as a fallback for devices that do not support WebGL 2.
* Supports fewer features and has a faster initialization time;
* may be useful for content that does not need advanced features like bitmap drawing or blend modes.
*/
Webgl = "webgl",
/*
* The slowest and most basic backend, used as a fallback when all else fails.
* However, this is currently the only backend that accurately scales hairline strokes.
* If you notice excessively thick strokes in specific content,
* you may want to use the canvas renderer for that content until the issue is resolved.
*/
Canvas = "canvas",
}
/** /**
* Any options used for loading a movie. * Any options used for loading a movie.
*/ */
@ -326,6 +359,20 @@ export interface BaseLoadOptions {
*/ */
playerVersion?: number | null; playerVersion?: number | null;
/**
* The preferred render backend of the Ruffle player.
*
* This option should only be used for testing;
* The available backends may change in future releases.
* By default, Ruffle chooses the most featureful backend supported by the user's system,
* falling back to more basic backends if necessary.
* The available values in order of default preference are:
* "webgpu", "wgpu-webgl", "webgl", "canvas".
*
* @default null
*/
preferredRenderer?: RenderBackend | null;
/** /**
* The URL at which Ruffle can load its extra files (i.e. `.wasm`). * The URL at which Ruffle can load its extra files (i.e. `.wasm`).
* *

View File

@ -24,13 +24,17 @@
<label for="ruffle_enable">Play Flash content in Ruffle</label> <label for="ruffle_enable">Play Flash content in Ruffle</label>
</div> </div>
<div class="option checkbox"> <div class="option checkbox">
<input type="checkbox" id="ignore_optout" /> <input type="checkbox" id="show_swf_download" />
<label for="ignore_optout">Ignore website compatibility warnings</label> <label for="show_swf_download">Show SWF download in context menu</label>
</div> </div>
<div class="option checkbox"> <div class="option checkbox">
<input type="checkbox" id="warn_on_unsupported_content" /> <input type="checkbox" id="warn_on_unsupported_content" />
<label for="warn_on_unsupported_content">Warn on unsupported content</label> <label for="warn_on_unsupported_content">Warn on unsupported content</label>
</div> </div>
<div class="option checkbox">
<input type="checkbox" id="ignore_optout" />
<label for="ignore_optout">Ignore website compatibility warnings</label>
</div>
<div class="option select"> <div class="option select">
<select id="log_level"> <select id="log_level">
<option value="info">Info</option> <option value="info">Info</option>
@ -39,9 +43,14 @@
</select> </select>
<label for="log_level">Log level</label> <label for="log_level">Log level</label>
</div> </div>
<div class="option checkbox"> <div class="option select">
<input type="checkbox" id="show_swf_download" /> <select id="preferred_renderer">
<label for="show_swf_download">Show SWF download in context menu</label> <option value="webgpu">WebGPU</option>
<option value="wgpu-webgl">Wgpu (via WebGL)</option>
<option value="webgl">WebGL</option>
<option value="canvas">Canvas2D</option>
</select>
<label for="preferred_renderer">Preferred renderer</label>
</div> </div>
<div class="option checkbox"> <div class="option checkbox">
<input type="checkbox" id="autostart" /> <input type="checkbox" id="autostart" />

View File

@ -170,6 +170,8 @@ struct Config {
max_execution_duration: Duration, max_execution_duration: Duration,
player_version: Option<u8>, player_version: Option<u8>,
preferred_renderer: Option<String>,
} }
/// Metadata about the playing SWF file to be passed back to JavaScript. /// Metadata about the playing SWF file to be passed back to JavaScript.
@ -1292,89 +1294,123 @@ async fn create_renderer(
document: &web_sys::Document, document: &web_sys::Document,
config: &Config, config: &Config,
) -> Result<(PlayerBuilder, HtmlCanvasElement), Box<dyn Error>> { ) -> Result<(PlayerBuilder, HtmlCanvasElement), Box<dyn Error>> {
#[cfg(not(any(feature = "canvas", feature = "webgpu", feature = "wgpu-webgl")))] #[cfg(not(any(
feature = "canvas",
feature = "webgl",
feature = "webgpu",
feature = "wgpu-webgl"
)))]
std::compile_error!("You must enable one of the render backend features (e.g., webgl)."); std::compile_error!("You must enable one of the render backend features (e.g., webgl).");
let _is_transparent = config.wmode.as_deref() == Some("transparent"); let _is_transparent = config.wmode.as_deref() == Some("transparent");
let mut renderer_list = vec!["webgpu", "wgpu-webgl", "webgl", "canvas"];
if let Some(preferred_renderer) = &config.preferred_renderer {
if let Some(pos) = renderer_list.iter().position(|&r| r == preferred_renderer) {
renderer_list.remove(pos);
renderer_list.insert(0, preferred_renderer.as_str());
}
}
// Try to create a backend, falling through to the next backend on failure. // 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 // We must recreate the canvas each attempt, as only a single context may be created per canvas
// with `getContext`. // with `getContext`.
#[cfg(all(feature = "webgpu", target_family = "wasm"))] for renderer in renderer_list {
{ match renderer {
// Check that we have access to WebGPU (navigator.gpu should exist). "webgpu" => {
if web_sys::window() #[cfg(all(feature = "webgpu", target_family = "wasm"))]
.ok_or(JsValue::FALSE) {
.and_then(|window| js_sys::Reflect::has(&window.navigator(), &JsValue::from_str("gpu"))) // Check that we have access to WebGPU (navigator.gpu should exist).
.unwrap_or_default() if web_sys::window()
{ .ok_or(JsValue::FALSE)
tracing::info!("Creating wgpu webgpu renderer..."); .and_then(|window| {
let canvas: HtmlCanvasElement = document js_sys::Reflect::has(&window.navigator(), &JsValue::from_str("gpu"))
.create_element("canvas") })
.into_js_result()? .unwrap_or_default()
.dyn_into() {
.map_err(|_| "Expected HtmlCanvasElement")?; tracing::info!("Creating wgpu webgpu renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas).await { match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas)
Ok(renderer) => { .await
return Ok((builder.with_renderer(renderer), canvas)); {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => {
tracing::error!("Error creating wgpu webgpu renderer: {}", error)
}
}
}
} }
Err(error) => tracing::error!("Error creating wgpu webgpu renderer: {}", error), }
"wgpu-webgl" => {
#[cfg(all(feature = "wgpu-webgl", target_family = "wasm"))]
{
tracing::info!("Creating wgpu webgl renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas).await
{
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => {
tracing::error!("Error creating wgpu webgl renderer: {}", error)
}
}
}
}
"webgl" => {
#[cfg(feature = "webgl")]
{
tracing::info!("Creating WebGL renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_webgl::WebGlRenderBackend::new(&canvas, _is_transparent) {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating WebGL renderer: {}", error),
}
}
}
"canvas" => {
#[cfg(feature = "canvas")]
{
tracing::info!("Creating Canvas renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_canvas::WebCanvasRenderBackend::new(
&canvas,
_is_transparent,
) {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating canvas renderer: {}", error),
}
}
}
_ => {
tracing::error!("Unrecognized renderer name: {}", renderer);
} }
} }
} }
#[cfg(all(feature = "wgpu-webgl", target_family = "wasm"))]
{
tracing::info!("Creating wgpu webgl renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas).await {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating wgpu webgl 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`.
#[cfg(feature = "webgl")]
{
tracing::info!("Creating WebGL renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_webgl::WebGlRenderBackend::new(&canvas, _is_transparent) {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating WebGL renderer: {}", error),
}
}
#[cfg(feature = "canvas")]
{
tracing::info!("Falling back to Canvas renderer...");
let canvas: HtmlCanvasElement = document
.create_element("canvas")
.into_js_result()?
.dyn_into()
.map_err(|_| "Expected HtmlCanvasElement")?;
match ruffle_render_canvas::WebCanvasRenderBackend::new(&canvas, _is_transparent) {
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating canvas renderer: {}", error),
}
}
Err("Unable to create renderer".into()) Err("Unable to create renderer".into())
} }