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,
polyfills: true,
playerVersion: null,
preferredRenderer: null,
};

View File

@ -117,6 +117,39 @@ export const enum WindowMode {
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.
*/
@ -326,6 +359,20 @@ export interface BaseLoadOptions {
*/
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`).
*

View File

@ -24,13 +24,17 @@
<label for="ruffle_enable">Play Flash content in Ruffle</label>
</div>
<div class="option checkbox">
<input type="checkbox" id="ignore_optout" />
<label for="ignore_optout">Ignore website compatibility warnings</label>
<input type="checkbox" id="show_swf_download" />
<label for="show_swf_download">Show SWF download in context menu</label>
</div>
<div class="option checkbox">
<input type="checkbox" id="warn_on_unsupported_content" />
<label for="warn_on_unsupported_content">Warn on unsupported content</label>
</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">
<select id="log_level">
<option value="info">Info</option>
@ -39,9 +43,14 @@
</select>
<label for="log_level">Log level</label>
</div>
<div class="option checkbox">
<input type="checkbox" id="show_swf_download" />
<label for="show_swf_download">Show SWF download in context menu</label>
<div class="option select">
<select id="preferred_renderer">
<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 class="option checkbox">
<input type="checkbox" id="autostart" />

View File

@ -170,6 +170,8 @@ struct Config {
max_execution_duration: Duration,
player_version: Option<u8>,
preferred_renderer: Option<String>,
}
/// Metadata about the playing SWF file to be passed back to JavaScript.
@ -1292,20 +1294,38 @@ async fn create_renderer(
document: &web_sys::Document,
config: &Config,
) -> 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).");
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.
// We must recreate the canvas each attempt, as only a single context may be created per canvas
// with `getContext`.
for renderer in renderer_list {
match renderer {
"webgpu" => {
#[cfg(all(feature = "webgpu", target_family = "wasm"))]
{
// 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")))
.and_then(|window| {
js_sys::Reflect::has(&window.navigator(), &JsValue::from_str("gpu"))
})
.unwrap_or_default()
{
tracing::info!("Creating wgpu webgpu renderer...");
@ -1315,14 +1335,20 @@ async fn create_renderer(
.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)
.await
{
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...");
@ -1332,17 +1358,18 @@ async fn create_renderer(
.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).await
{
Ok(renderer) => {
return Ok((builder.with_renderer(renderer), canvas));
}
Err(error) => tracing::error!("Error creating wgpu webgl renderer: {}", error),
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`.
}
}
"webgl" => {
#[cfg(feature = "webgl")]
{
tracing::info!("Creating WebGL renderer...");
@ -1358,23 +1385,32 @@ async fn create_renderer(
Err(error) => tracing::error!("Error creating WebGL renderer: {}", error),
}
}
}
"canvas" => {
#[cfg(feature = "canvas")]
{
tracing::info!("Falling back to Canvas renderer...");
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) {
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);
}
}
}
Err("Unable to create renderer".into())
}