diff --git a/desktop/src/main.rs b/desktop/src/main.rs index a17545f78..de4ebbd5e 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -23,8 +23,8 @@ use ruffle_core::{ config::Letterbox, events::KeyCode, tag_utils::SwfMovie, Player, PlayerBuilder, PlayerEvent, StageDisplayState, StaticCallstack, ViewportDimensions, }; +use ruffle_render_wgpu::backend::WgpuRenderBackend; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; -use ruffle_render_wgpu::WgpuRenderBackend; use std::cell::RefCell; use std::io::Read; use std::path::{Path, PathBuf}; diff --git a/exporter/src/main.rs b/exporter/src/main.rs index 579ab48f6..cbf335605 100644 --- a/exporter/src/main.rs +++ b/exporter/src/main.rs @@ -5,10 +5,11 @@ use indicatif::{ProgressBar, ProgressStyle}; use rayon::prelude::*; use ruffle_core::tag_utils::SwfMovie; use ruffle_core::PlayerBuilder; +use ruffle_render_wgpu::backend::WgpuRenderBackend; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use ruffle_render_wgpu::descriptors::Descriptors; use ruffle_render_wgpu::target::TextureTarget; -use ruffle_render_wgpu::{wgpu, WgpuRenderBackend}; +use ruffle_render_wgpu::wgpu; use std::fs::create_dir_all; use std::panic::catch_unwind; use std::path::{Path, PathBuf}; diff --git a/render/wgpu/src/backend.rs b/render/wgpu/src/backend.rs new file mode 100644 index 000000000..9f895cc8b --- /dev/null +++ b/render/wgpu/src/backend.rs @@ -0,0 +1,1580 @@ +use crate::descriptors::DescriptorsTargetData; +use crate::target::RenderTargetFrame; +use crate::target::TextureTarget; +use crate::{ + create_buffer_with_data, create_quad_buffers, format_list, get_backend_names, target, + BufferDimensions, ColorAdjustments, Descriptors, Draw, DrawType, Error, Frame, Globals, + GradientStorage, GradientUniforms, MaskState, Mesh, RegistryData, RenderTarget, + SwapChainTarget, Texture, TextureOffscreen, TextureTransforms, Transforms, UniformBuffer, + Vertex, +}; +use fnv::FnvHashMap; +use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions}; +use ruffle_render::bitmap::{Bitmap, BitmapHandle, BitmapSource}; +use ruffle_render::error::Error as BitmapError; +use ruffle_render::shape_utils::DistilledShape; +use ruffle_render::tessellator::{DrawType as TessDrawType, ShapeTessellator}; +use ruffle_render::transform::Transform; +use std::num::NonZeroU32; +use std::path::Path; +use std::sync::Arc; +use swf::{BlendMode, Color}; + +const DEFAULT_SAMPLE_COUNT: u32 = 4; + +pub struct WgpuRenderBackend { + descriptors: Arc, + globals: Globals, + uniform_buffers: UniformBuffer, + target: T, + frame_buffer_view: Option, + depth_texture_view: wgpu::TextureView, + copy_srgb_view: Option, + copy_srgb_bind_group: Option, + current_frame: Option>, + meshes: Vec, + mask_state: MaskState, + shape_tessellator: ShapeTessellator, + num_masks: u32, + quad_vbo: wgpu::Buffer, + quad_ibo: wgpu::Buffer, + quad_tex_transforms: wgpu::Buffer, + blend_modes: Vec, + bitmap_registry: FnvHashMap, + next_bitmap_handle: BitmapHandle, + // This is currently unused - we just store it to report in + // `get_viewport_dimensions` + viewport_scale_factor: f64, + offscreen: bool, +} + +impl WgpuRenderBackend { + #[cfg(target_family = "wasm")] + pub async fn for_canvas(canvas: &web_sys::HtmlCanvasElement) -> Result { + let instance = wgpu::Instance::new(wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL); + let surface = instance.create_surface_from_canvas(canvas); + let descriptors = Self::build_descriptors( + wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL, + instance, + Some(&surface), + wgpu::PowerPreference::HighPerformance, + None, + ) + .await?; + let target = SwapChainTarget::new( + surface, + descriptors.onscreen.surface_format, + (1, 1), + &descriptors.device, + ); + Self::new(Arc::new(descriptors), target) + } + + #[cfg(not(target_family = "wasm"))] + pub fn for_window( + window: &W, + size: (u32, u32), + backend: wgpu::Backends, + power_preference: wgpu::PowerPreference, + trace_path: Option<&Path>, + ) -> Result { + if wgpu::Backends::SECONDARY.contains(backend) { + log::warn!( + "{} graphics backend support may not be fully supported.", + format_list(&get_backend_names(backend), "and") + ); + } + let instance = wgpu::Instance::new(backend); + let surface = unsafe { instance.create_surface(window) }; + let descriptors = futures::executor::block_on(Self::build_descriptors( + backend, + instance, + Some(&surface), + power_preference, + trace_path, + ))?; + let target = SwapChainTarget::new( + surface, + descriptors.onscreen.surface_format, + size, + &descriptors.device, + ); + Self::new(Arc::new(descriptors), target) + } +} + +#[cfg(not(target_family = "wasm"))] +impl WgpuRenderBackend { + pub fn for_offscreen( + size: (u32, u32), + backend: wgpu::Backends, + power_preference: wgpu::PowerPreference, + trace_path: Option<&Path>, + ) -> Result { + if wgpu::Backends::SECONDARY.contains(backend) { + log::warn!( + "{} graphics backend support may not be fully supported.", + format_list(&get_backend_names(backend), "and") + ); + } + let instance = wgpu::Instance::new(backend); + let descriptors = futures::executor::block_on(Self::build_descriptors( + backend, + instance, + None, + power_preference, + trace_path, + ))?; + let target = target::TextureTarget::new(&descriptors.device, size)?; + Self::new(Arc::new(descriptors), target) + } + + pub fn capture_frame(&self, premultiplied_alpha: bool) -> Option { + self.target + .capture(&self.descriptors.device, premultiplied_alpha) + } +} + +macro_rules! target_data { + ($this:expr) => {{ + if $this.offscreen { + &$this.descriptors.offscreen + } else { + &$this.descriptors.onscreen + } + }}; +} + +impl WgpuRenderBackend { + pub fn new(descriptors: Arc, target: T) -> Result { + if target.width() > descriptors.limits.max_texture_dimension_2d + || target.height() > descriptors.limits.max_texture_dimension_2d + { + return Err(format!( + "Render target texture cannot be larger than {}px on either dimension (requested {} x {})", + descriptors.limits.max_texture_dimension_2d, + target.width(), + target.height() + ) + .into()); + } + + let extent = wgpu::Extent3d { + width: target.width(), + height: target.height(), + depth_or_array_layers: 1, + }; + + let frame_buffer_view = if descriptors.onscreen.msaa_sample_count > 1 { + let frame_buffer = descriptors.device.create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Framebuffer texture").as_deref(), + size: extent, + mip_level_count: 1, + sample_count: descriptors.onscreen.msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: descriptors.onscreen.frame_buffer_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + Some(frame_buffer.create_view(&Default::default())) + } else { + None + }; + + let depth_texture = descriptors.device.create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Depth texture").as_deref(), + size: extent, + mip_level_count: 1, + sample_count: descriptors.onscreen.msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24PlusStencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + let depth_texture_view = depth_texture.create_view(&Default::default()); + + let (quad_vbo, quad_ibo, quad_tex_transforms) = create_quad_buffers(&descriptors.device); + + let (copy_srgb_view, copy_srgb_bind_group) = if descriptors.onscreen.frame_buffer_format + != descriptors.offscreen.surface_format + { + let copy_srgb_buffer = descriptors.device.create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Copy sRGB framebuffer texture").as_deref(), + size: extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: descriptors.onscreen.frame_buffer_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + }); + let copy_srgb_view = copy_srgb_buffer.create_view(&Default::default()); + let copy_srgb_bind_group = + descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &descriptors.onscreen.pipelines.bitmap_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &quad_tex_transforms, + offset: 0, + size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(©_srgb_view), + }, + ], + label: create_debug_label!("Copy sRGB bind group").as_deref(), + }); + (Some(copy_srgb_view), Some(copy_srgb_bind_group)) + } else { + (None, None) + }; + + let mut globals = Globals::new(&descriptors.device, &descriptors.globals_layout); + globals.set_resolution(target.width(), target.height()); + + let uniform_buffers = + UniformBuffer::new(descriptors.limits.min_uniform_buffer_offset_alignment); + + Ok(Self { + descriptors, + globals, + uniform_buffers, + target, + frame_buffer_view, + depth_texture_view, + copy_srgb_view, + copy_srgb_bind_group, + current_frame: None, + meshes: Vec::new(), + shape_tessellator: ShapeTessellator::new(), + + num_masks: 0, + mask_state: MaskState::NoMask, + + quad_vbo, + quad_ibo, + quad_tex_transforms, + blend_modes: vec![BlendMode::Normal], + bitmap_registry: Default::default(), + next_bitmap_handle: BitmapHandle(0), + viewport_scale_factor: 1.0, + offscreen: false, + }) + } + + pub async fn build_descriptors( + backend: wgpu::Backends, + instance: wgpu::Instance, + surface: Option<&wgpu::Surface>, + power_preference: wgpu::PowerPreference, + trace_path: Option<&Path>, + ) -> Result { + let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference, + compatible_surface: surface, + force_fallback_adapter: false, + }).await + .ok_or_else(|| { + let names = get_backend_names(backend); + if names.is_empty() { + "Ruffle requires hardware acceleration, but no compatible graphics device was found (no backend provided?)".to_string() + } else if cfg!(any(windows, target_os = "macos")) { + format!("Ruffle does not support OpenGL on {}.", if cfg!(windows) { "Windows" } else { "macOS" }) + } else { + format!("Ruffle requires hardware acceleration, but no compatible graphics device was found supporting {}", format_list(&names, "or")) + } + })?; + + let (device, queue) = request_device(&adapter, trace_path).await?; + let info = adapter.get_info(); + // Ideally we want to use an RGBA non-sRGB surface format, because Flash colors and + // blending are done in sRGB space -- we don't want the GPU to adjust the colors. + // Some platforms may only support an sRGB surface, in which case we will draw to an + // intermediate linear buffer and then copy to the sRGB surface. + let surface_format = surface + .and_then(|surface| { + let formats = surface.get_supported_formats(&adapter); + formats + .iter() + .find(|format| { + matches!( + format, + wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm + ) + }) + .or_else(|| formats.first()) + .cloned() + }) + // No surface (rendering to texture), default to linear RBGA. + .unwrap_or(wgpu::TextureFormat::Rgba8Unorm); + // TODO: Allow the sample count to be set from command line/settings file. + Ok(Descriptors::new( + device, + queue, + info, + surface_format, + DEFAULT_SAMPLE_COUNT, + )) + } + + fn register_shape_internal( + &mut self, + shape: DistilledShape, + bitmap_source: &dyn BitmapSource, + ) -> Mesh { + let shape_id = shape.id; // TODO: remove? + let lyon_mesh = self + .shape_tessellator + .tessellate_shape(shape, bitmap_source); + + let mut draws = Vec::with_capacity(lyon_mesh.len()); + for draw in lyon_mesh { + let vertices: Vec<_> = draw.vertices.into_iter().map(Vertex::from).collect(); + let vertex_buffer = create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&vertices), + wgpu::BufferUsages::VERTEX, + create_debug_label!("Shape {} ({}) vbo", shape_id, draw.draw_type.name()), + ); + + let index_buffer = create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&draw.indices), + wgpu::BufferUsages::INDEX, + create_debug_label!("Shape {} ({}) ibo", shape_id, draw.draw_type.name()), + ); + + let index_count = draw.indices.len() as u32; + let draw_id = draws.len(); + + draws.push(match draw.draw_type { + TessDrawType::Color => Draw { + draw_type: DrawType::Color, + vertex_buffer, + index_buffer, + num_indices: index_count, + num_mask_indices: draw.mask_index_count, + }, + TessDrawType::Gradient(gradient) => { + // TODO: Extract to function? + let mut texture_transform = [[0.0; 4]; 4]; + texture_transform[0][..3].copy_from_slice(&gradient.matrix[0]); + texture_transform[1][..3].copy_from_slice(&gradient.matrix[1]); + texture_transform[2][..3].copy_from_slice(&gradient.matrix[2]); + + let tex_transforms_ubo = create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&[texture_transform]), + wgpu::BufferUsages::UNIFORM, + create_debug_label!( + "Shape {} draw {} textransforms ubo transfer buffer", + shape_id, + draw_id + ), + ); + + let (gradient_ubo, buffer_size) = if self + .descriptors + .limits + .max_storage_buffers_per_shader_stage + > 0 + { + ( + create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&[GradientStorage::from(gradient)]), + wgpu::BufferUsages::STORAGE, + create_debug_label!( + "Shape {} draw {} gradient ubo transfer buffer", + shape_id, + draw_id + ), + ), + wgpu::BufferSize::new(std::mem::size_of::() as u64), + ) + } else { + ( + create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&[GradientUniforms::from(gradient)]), + wgpu::BufferUsages::UNIFORM, + create_debug_label!( + "Shape {} draw {} gradient ubo transfer buffer", + shape_id, + draw_id + ), + ), + wgpu::BufferSize::new(std::mem::size_of::() as u64), + ) + }; + + let bind_group_label = create_debug_label!( + "Shape {} (gradient) draw {} bindgroup", + shape_id, + draw_id + ); + let bind_group = + self.descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &target_data!(self).pipelines.gradient_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &tex_transforms_ubo, + offset: 0, + size: wgpu::BufferSize::new(std::mem::size_of::< + TextureTransforms, + >( + ) + as u64), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &gradient_ubo, + offset: 0, + size: buffer_size, + }, + ), + }, + ], + label: bind_group_label.as_deref(), + }); + + Draw { + draw_type: DrawType::Gradient { + texture_transforms: tex_transforms_ubo, + gradient: gradient_ubo, + bind_group, + }, + vertex_buffer, + index_buffer, + num_indices: index_count, + num_mask_indices: draw.mask_index_count, + } + } + TessDrawType::Bitmap(bitmap) => { + let entry = self.bitmap_registry.get(&bitmap.bitmap).unwrap(); + let texture_view = entry + .texture_wrapper + .texture + .create_view(&Default::default()); + + // TODO: Extract to function? + let mut texture_transform = [[0.0; 4]; 4]; + texture_transform[0][..3].copy_from_slice(&bitmap.matrix[0]); + texture_transform[1][..3].copy_from_slice(&bitmap.matrix[1]); + texture_transform[2][..3].copy_from_slice(&bitmap.matrix[2]); + + let tex_transforms_ubo = create_buffer_with_data( + &self.descriptors.device, + bytemuck::cast_slice(&[texture_transform]), + wgpu::BufferUsages::UNIFORM, + create_debug_label!( + "Shape {} draw {} textransforms ubo transfer buffer", + shape_id, + draw_id + ), + ); + + let bind_group_label = create_debug_label!( + "Shape {} (bitmap) draw {} bindgroup", + shape_id, + draw_id + ); + let bind_group = + self.descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &target_data!(self).pipelines.bitmap_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &tex_transforms_ubo, + offset: 0, + size: wgpu::BufferSize::new(std::mem::size_of::< + TextureTransforms, + >( + ) + as u64), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + ], + label: bind_group_label.as_deref(), + }); + + Draw { + draw_type: DrawType::Bitmap { + texture_transforms: tex_transforms_ubo, + texture_view, + is_smoothed: bitmap.is_smoothed, + is_repeating: bitmap.is_repeating, + bind_group, + }, + vertex_buffer, + index_buffer, + num_indices: index_count, + num_mask_indices: draw.mask_index_count, + } + } + }); + } + + Mesh { draws } + } + + fn blend_mode(&self) -> BlendMode { + *self.blend_modes.last().unwrap() + } + + pub fn descriptors(&self) -> &Arc { + &self.descriptors + } + + pub fn target(&self) -> &T { + &self.target + } + + pub fn device(&self) -> &wgpu::Device { + &self.descriptors.device + } + + fn render_offscreen_internal( + mut self, + handle: BitmapHandle, + width: u32, + height: u32, + f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>, + ) -> Result<(Self, ruffle_render::bitmap::Bitmap), ruffle_render::error::Error> { + // We need ownership of `Texture` to access the non-`Clone` + // `wgpu` fields. At the end of this method, we re-insert + // `texture` into the map. + // + // This means that the target texture will be inaccessible + // while the callback `f` is a problem. This would only be + // an issue if a caller tried to render the target texture + // to itself, which probably isn't supported by Flash. If it + // is, then we could change `TextureTarget` to use an `Rc` + let mut texture = self.bitmap_registry.remove(&handle).unwrap(); + + let extent = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + if self.offscreen { + panic!("Nested render_onto_bitmap is not supported!") + } + + let descriptors = self.descriptors.clone(); + + // We will (presumably) never render to the majority of textures, so + // we lazily create the buffer and depth texture. + // Once created, we never destroy this data, under the assumption + // that the SWF will try to render to this more than once. + // + // If we end up hitting wgpu device limits due to having too + // many buffers / depth textures rendered at once, we could + // try storing this data in an LRU cache, evicting entries + // as needed. + let mut texture_offscreen = + texture + .texture_wrapper + .texture_offscreen + .unwrap_or_else(|| { + let depth_texture_view = + create_depth_texture_view(&descriptors, &descriptors.offscreen, extent); + let buffer_dimensions = BufferDimensions::new(width as usize, height as usize); + let buffer_label = create_debug_label!("Render target buffer"); + let buffer = descriptors.device.create_buffer(&wgpu::BufferDescriptor { + label: buffer_label.as_deref(), + size: (buffer_dimensions.padded_bytes_per_row.get() as u64 + * buffer_dimensions.height as u64), + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + TextureOffscreen { + depth_texture_view, + buffer, + buffer_dimensions, + } + }); + + let target = TextureTarget { + size: extent, + texture: texture.texture_wrapper.texture, + format: wgpu::TextureFormat::Rgba8Unorm, + buffer: texture_offscreen.buffer, + buffer_dimensions: texture_offscreen.buffer_dimensions, + }; + + let (old_width, old_height) = self.globals.resolution(); + + // Is it worth caching this? + self.globals.set_resolution(width, height); + + let mut texture_backend = WgpuRenderBackend { + descriptors, + target, + // FIXME - Enable MSAA for textures + frame_buffer_view: None, + depth_texture_view: texture_offscreen.depth_texture_view, + // We explicitly request a non-SRGB format for textures + copy_srgb_view: None, + copy_srgb_bind_group: None, + current_frame: None, + meshes: self.meshes, + mask_state: MaskState::NoMask, + shape_tessellator: self.shape_tessellator, + num_masks: 0, + quad_vbo: self.quad_vbo, + quad_ibo: self.quad_ibo, + quad_tex_transforms: self.quad_tex_transforms, + bitmap_registry: self.bitmap_registry, + offscreen: true, + next_bitmap_handle: self.next_bitmap_handle, + globals: self.globals, + uniform_buffers: self.uniform_buffers, + viewport_scale_factor: self.viewport_scale_factor, + blend_modes: vec![BlendMode::Normal], + }; + + let f_res = f(&mut texture_backend); + + // Capture with premultiplied alpha, which is what we use for all textures + let image = texture_backend + .target + .capture(&texture_backend.descriptors.device, true); + + let image = image.map(|image| { + ruffle_render::bitmap::Bitmap::new( + image.dimensions().0, + image.dimensions().1, + ruffle_render::bitmap::BitmapFormat::Rgba, + image.into_raw(), + ) + }); + + self.offscreen = false; + self.meshes = texture_backend.meshes; + self.shape_tessellator = texture_backend.shape_tessellator; + self.bitmap_registry = texture_backend.bitmap_registry; + self.quad_tex_transforms = texture_backend.quad_tex_transforms; + self.quad_ibo = texture_backend.quad_ibo; + self.quad_vbo = texture_backend.quad_vbo; + self.globals = texture_backend.globals; + self.uniform_buffers = texture_backend.uniform_buffers; + self.next_bitmap_handle = texture_backend.next_bitmap_handle; + self.globals.set_resolution(old_width, old_height); + + texture_offscreen.buffer = texture_backend.target.buffer; + texture_offscreen.buffer_dimensions = texture_backend.target.buffer_dimensions; + texture_offscreen.depth_texture_view = texture_backend.depth_texture_view; + texture.texture_wrapper.texture_offscreen = Some(texture_offscreen); + texture.texture_wrapper.texture = texture_backend.target.texture; + self.bitmap_registry.insert(handle, texture); + + // Check result after restoring the backend fields + f_res?; + + Ok((self, image.unwrap())) + } +} + +impl RenderBackend for WgpuRenderBackend { + fn set_viewport_dimensions(&mut self, dimensions: ViewportDimensions) { + // Avoid panics from creating 0-sized framebuffers. + // TODO: find a way to bubble an error when the size is too large + let width = std::cmp::max( + std::cmp::min( + dimensions.width, + self.descriptors.limits.max_texture_dimension_2d, + ), + 1, + ); + let height = std::cmp::max( + std::cmp::min( + dimensions.height, + self.descriptors.limits.max_texture_dimension_2d, + ), + 1, + ); + self.target.resize(&self.descriptors.device, width, height); + + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + self.frame_buffer_view = if target_data!(self).msaa_sample_count > 1 { + let frame_buffer = self + .descriptors + .device + .create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Framebuffer texture").as_deref(), + size, + mip_level_count: 1, + sample_count: target_data!(self).msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: target_data!(self).frame_buffer_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + Some(frame_buffer.create_view(&Default::default())) + } else { + None + }; + + let depth_texture = self + .descriptors + .device + .create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Depth texture").as_deref(), + size, + mip_level_count: 1, + sample_count: target_data!(self).msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24PlusStencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + self.depth_texture_view = depth_texture.create_view(&Default::default()); + + (self.copy_srgb_view, self.copy_srgb_bind_group) = if target_data!(self).frame_buffer_format + != target_data!(self).surface_format + { + let copy_srgb_buffer = + self.descriptors + .device + .create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Copy sRGB framebuffer texture").as_deref(), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: target_data!(self).frame_buffer_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + }); + let copy_srgb_view = copy_srgb_buffer.create_view(&Default::default()); + let copy_srgb_bind_group = + self.descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &target_data!(self).pipelines.bitmap_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &self.quad_tex_transforms, + offset: 0, + size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(©_srgb_view), + }, + ], + label: create_debug_label!("Copy sRGB bind group").as_deref(), + }); + (Some(copy_srgb_view), Some(copy_srgb_bind_group)) + } else { + (None, None) + }; + + self.globals.set_resolution(width, height); + self.viewport_scale_factor = dimensions.scale_factor; + } + + fn viewport_dimensions(&self) -> ViewportDimensions { + ViewportDimensions { + width: self.target.width(), + height: self.target.height(), + scale_factor: self.viewport_scale_factor, + } + } + + fn register_shape( + &mut self, + shape: DistilledShape, + bitmap_source: &dyn BitmapSource, + ) -> ShapeHandle { + let handle = ShapeHandle(self.meshes.len()); + let mesh = self.register_shape_internal(shape, bitmap_source); + self.meshes.push(mesh); + handle + } + + fn replace_shape( + &mut self, + shape: DistilledShape, + bitmap_source: &dyn BitmapSource, + handle: ShapeHandle, + ) { + let mesh = self.register_shape_internal(shape, bitmap_source); + self.meshes[handle.0] = mesh; + } + + fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle { + let shape = ruffle_render::shape_utils::swf_glyph_to_shape(glyph); + let handle = ShapeHandle(self.meshes.len()); + let mesh = self.register_shape_internal( + (&shape).into(), + &ruffle_render::backend::null::NullBitmapSource, + ); + self.meshes.push(mesh); + handle + } + + fn begin_frame(&mut self, clear: Color) { + self.mask_state = MaskState::NoMask; + self.num_masks = 0; + self.uniform_buffers.reset(); + + let frame_output = match self.target.get_next_texture() { + Ok(frame) => frame, + Err(e) => { + log::warn!("Couldn't begin new render frame: {}", e); + // Attemp to recreate the swap chain in this case. + self.target.resize( + &self.descriptors.device, + self.target.width(), + self.target.height(), + ); + return; + } + }; + + let label = create_debug_label!("Draw encoder"); + let draw_encoder = + self.descriptors + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: label.as_deref(), + }); + let uniform_encoder_label = create_debug_label!("Uniform upload command encoder"); + let uniform_encoder = + self.descriptors + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: uniform_encoder_label.as_deref(), + }); + let mut frame_data = Box::new((draw_encoder, frame_output, uniform_encoder)); + + self.globals + .update_uniform(&self.descriptors.device, &mut frame_data.0); + + // Use intermediate render targets when resolving MSAA or copying from linear-to-sRGB texture. + let (color_view, resolve_target) = match (&self.frame_buffer_view, &self.copy_srgb_view) { + (None, None) => (frame_data.1.view(), None), + (None, Some(copy)) => (copy, None), + (Some(frame_buffer), None) => (frame_buffer, Some(frame_data.1.view())), + (Some(frame_buffer), Some(copy)) => (frame_buffer, Some(copy)), + }; + + let render_pass = frame_data.0.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: color_view, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: f64::from(clear.r) / 255.0, + g: f64::from(clear.g) / 255.0, + b: f64::from(clear.b) / 255.0, + a: f64::from(clear.a) / 255.0, + }), + store: true, + }, + resolve_target, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_texture_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0.0), + store: false, + }), + stencil_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: true, + }), + }), + label: None, + }); + + // Since RenderPass holds a reference to the CommandEncoder, we cast the lifetime + // away to allow for the self-referencing struct. draw_encoder is boxed so its + // address should remain stable. + self.current_frame = Some(Frame { + render_pass: unsafe { + std::mem::transmute::<_, wgpu::RenderPass<'static>>(render_pass) + }, + frame_data, + }); + } + + fn render_offscreen( + &mut self, + handle: BitmapHandle, + width: u32, + height: u32, + f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>, + ) -> Result { + // Rendering to a texture backend requires us to use non-`Clone` + // wgpu resources (e.g. `wgpu::Device`, `wgpu::Queue`. + // + // We expect that in the majority of SWFs, we will spend most + // of our time performing 'normal' (non-offscreen) renders. + // Therefore, we want to avoid penalizing this case by adding + // in a check for 'normal' or 'offscreen' mode in the main + // rendering code. + // + // To accomplish this, we use `take_mut` to temporarily + // move out of `self`. This allows us to construct a new + // `WgpuRenderBackend` with a `TextureTarget` corresponding to + // `handle`. This allows us to re-use many of the fields from + // our normal `WgpuRenderBackend` without wrapping in an `Rc` + // or other indirection. + // + // Note that `take_mut` causes the process to abort if the + // `with_offscreen_render_backend_internal` panics, since + // the `&mut self` reference would be logically uninitialized. + // However, we normally compile Ruffle with `panic=abort`, + // so this shouldn't actually have an effect in practice. + // Even with `panic=unwind`, we would still get a backtrace + // printed, and there's not really much point in attempting + // to recover from a partially failed render operation, anyway. + Ok(take_mut(self, |this| { + this.render_offscreen_internal(handle, width, height, f) + .expect("Failed to render to offscreen backend") + })) + } + + fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) { + let target_data = target_data!(self); + if let Some(entry) = self.bitmap_registry.get(&bitmap) { + let texture = &entry.texture_wrapper; + let blend_mode = self.blend_mode(); + let frame = if let Some(frame) = &mut self.current_frame { + frame.get() + } else { + return; + }; + + let transform = Transform { + matrix: transform.matrix + * ruffle_render::matrix::Matrix { + a: texture.width as f32, + d: texture.height as f32, + ..Default::default() + }, + ..*transform + }; + + let world_matrix = [ + [transform.matrix.a, transform.matrix.b, 0.0, 0.0], + [transform.matrix.c, transform.matrix.d, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [ + transform.matrix.tx.to_pixels() as f32, + transform.matrix.ty.to_pixels() as f32, + 0.0, + 1.0, + ], + ]; + + frame.render_pass.set_pipeline( + target_data + .pipelines + .bitmap_pipelines + .pipeline_for(blend_mode.into(), self.mask_state), + ); + frame + .render_pass + .set_bind_group(0, self.globals.bind_group(), &[]); + + self.uniform_buffers.write_uniforms( + &self.descriptors.device, + &self.descriptors.uniform_buffers_layout, + &mut frame.frame_data.2, + &mut frame.render_pass, + 1, + &Transforms { + world_matrix, + color_adjustments: ColorAdjustments::from(transform.color_transform), + }, + ); + + frame + .render_pass + .set_bind_group(2, &texture.bind_group, &[]); + frame.render_pass.set_bind_group( + 3, + self.descriptors + .bitmap_samplers + .get_bind_group(false, smoothing), + &[], + ); + frame + .render_pass + .set_vertex_buffer(0, self.quad_vbo.slice(..)); + frame + .render_pass + .set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); + + match self.mask_state { + MaskState::NoMask => (), + MaskState::DrawMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks - 1); + } + MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks); + } + }; + + frame.render_pass.draw_indexed(0..6, 0, 0..1); + } + } + + fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) { + let blend_mode = self.blend_mode(); + let frame = if let Some(frame) = &mut self.current_frame { + frame.get() + } else { + return; + }; + + let mesh = &mut self.meshes[shape.0]; + + let world_matrix = [ + [transform.matrix.a, transform.matrix.b, 0.0, 0.0], + [transform.matrix.c, transform.matrix.d, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [ + transform.matrix.tx.to_pixels() as f32, + transform.matrix.ty.to_pixels() as f32, + 0.0, + 1.0, + ], + ]; + + frame + .render_pass + .set_bind_group(0, self.globals.bind_group(), &[]); + + self.uniform_buffers.write_uniforms( + &self.descriptors.device, + &self.descriptors.uniform_buffers_layout, + &mut frame.frame_data.2, + &mut frame.render_pass, + 1, + &Transforms { + world_matrix, + color_adjustments: ColorAdjustments::from(transform.color_transform), + }, + ); + + for draw in &mesh.draws { + let num_indices = if self.mask_state != MaskState::DrawMaskStencil + && self.mask_state != MaskState::ClearMaskStencil + { + draw.num_indices + } else { + // Omit strokes when drawing a mask stencil. + draw.num_mask_indices + }; + if num_indices == 0 { + continue; + } + + match &draw.draw_type { + DrawType::Color => { + frame.render_pass.set_pipeline( + target_data!(self) + .pipelines + .color_pipelines + .pipeline_for(blend_mode.into(), self.mask_state), + ); + } + DrawType::Gradient { bind_group, .. } => { + frame.render_pass.set_pipeline( + target_data!(self) + .pipelines + .gradient_pipelines + .pipeline_for(blend_mode.into(), self.mask_state), + ); + frame.render_pass.set_bind_group(2, bind_group, &[]); + } + DrawType::Bitmap { + is_repeating, + is_smoothed, + bind_group, + .. + } => { + frame.render_pass.set_pipeline( + target_data!(self) + .pipelines + .bitmap_pipelines + .pipeline_for(blend_mode.into(), self.mask_state), + ); + frame.render_pass.set_bind_group(2, bind_group, &[]); + frame.render_pass.set_bind_group( + 3, + self.descriptors + .bitmap_samplers + .get_bind_group(*is_repeating, *is_smoothed), + &[], + ); + } + } + + frame + .render_pass + .set_vertex_buffer(0, draw.vertex_buffer.slice(..)); + frame + .render_pass + .set_index_buffer(draw.index_buffer.slice(..), wgpu::IndexFormat::Uint32); + + match self.mask_state { + MaskState::NoMask => (), + MaskState::DrawMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks - 1); + } + MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks); + } + }; + + frame.render_pass.draw_indexed(0..num_indices, 0, 0..1); + } + } + + fn draw_rect(&mut self, color: Color, matrix: &ruffle_render::matrix::Matrix) { + let blend_mode = self.blend_mode(); + let frame = if let Some(frame) = &mut self.current_frame { + frame.get() + } else { + return; + }; + + let world_matrix = [ + [matrix.a, matrix.b, 0.0, 0.0], + [matrix.c, matrix.d, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [ + matrix.tx.to_pixels() as f32, + matrix.ty.to_pixels() as f32, + 0.0, + 1.0, + ], + ]; + + let mult_color = [ + f32::from(color.r) / 255.0, + f32::from(color.g) / 255.0, + f32::from(color.b) / 255.0, + f32::from(color.a) / 255.0, + ]; + + let add_color = [0.0, 0.0, 0.0, 0.0]; + frame.render_pass.set_pipeline( + target_data!(self) + .pipelines + .color_pipelines + .pipeline_for(blend_mode.into(), self.mask_state), + ); + + frame + .render_pass + .set_bind_group(0, self.globals.bind_group(), &[]); + + self.uniform_buffers.write_uniforms( + &self.descriptors.device, + &self.descriptors.uniform_buffers_layout, + &mut frame.frame_data.2, + &mut frame.render_pass, + 1, + &Transforms { + world_matrix, + color_adjustments: ColorAdjustments { + mult_color, + add_color, + }, + }, + ); + + frame + .render_pass + .set_vertex_buffer(0, self.quad_vbo.slice(..)); + frame + .render_pass + .set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); + + match self.mask_state { + MaskState::NoMask => (), + MaskState::DrawMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks - 1); + } + MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { + debug_assert!(self.num_masks > 0); + frame.render_pass.set_stencil_reference(self.num_masks); + } + }; + + frame.render_pass.draw_indexed(0..6, 0, 0..1); + } + + fn end_frame(&mut self) { + if let Some(frame) = self.current_frame.take() { + let draw_encoder = frame.frame_data.0; + let mut uniform_encoder = frame.frame_data.2; + let render_pass = frame.render_pass; + // Finalize render pass. + drop(render_pass); + + // If we have an sRGB surface, copy from our linear intermediate buffer to the sRGB surface. + let command_buffers = if let Some(copy_srgb_bind_group) = &self.copy_srgb_bind_group { + debug_assert!(self.copy_srgb_view.is_some()); + let mut copy_encoder = self.descriptors.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: create_debug_label!("Frame copy command encoder").as_deref(), + }, + ); + + let mut render_pass = copy_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: frame.frame_data.1.view(), + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: true, + }, + resolve_target: None, + })], + depth_stencil_attachment: None, + label: None, + }); + + render_pass.set_pipeline(&target_data!(self).pipelines.copy_srgb_pipeline); + render_pass.set_bind_group(0, self.globals.bind_group(), &[]); + self.uniform_buffers.write_uniforms( + &self.descriptors.device, + &self.descriptors.uniform_buffers_layout, + &mut uniform_encoder, + &mut render_pass, + 1, + &Transforms { + world_matrix: [ + [self.target.width() as f32, 0.0, 0.0, 0.0], + [0.0, self.target.height() as f32, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ], + color_adjustments: ColorAdjustments { + mult_color: [1.0, 1.0, 1.0, 1.0], + add_color: [0.0, 0.0, 0.0, 0.0], + }, + }, + ); + render_pass.set_bind_group(2, copy_srgb_bind_group, &[]); + render_pass.set_bind_group( + 3, + self.descriptors + .bitmap_samplers + .get_bind_group(false, false), + &[], + ); + render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..)); + render_pass.set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); + render_pass.draw_indexed(0..6, 0, 0..1); + drop(render_pass); + vec![ + uniform_encoder.finish(), + draw_encoder.finish(), + copy_encoder.finish(), + ] + } else { + vec![uniform_encoder.finish(), draw_encoder.finish()] + }; + + self.uniform_buffers.finish(); + self.target.submit( + &self.descriptors.device, + &self.descriptors.queue, + command_buffers, + frame.frame_data.1, + ); + } + } + + fn push_mask(&mut self) { + debug_assert!( + self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent + ); + self.num_masks += 1; + self.mask_state = MaskState::DrawMaskStencil; + } + + fn activate_mask(&mut self) { + debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskStencil); + self.mask_state = MaskState::DrawMaskedContent; + } + + fn deactivate_mask(&mut self) { + debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskedContent); + self.mask_state = MaskState::ClearMaskStencil; + } + + fn pop_mask(&mut self) { + debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::ClearMaskStencil); + self.num_masks -= 1; + self.mask_state = if self.num_masks == 0 { + MaskState::NoMask + } else { + MaskState::DrawMaskedContent + }; + } + + fn push_blend_mode(&mut self, blend: BlendMode) { + self.blend_modes.push(blend); + } + + fn pop_blend_mode(&mut self) { + self.blend_modes.pop(); + } + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option { + self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone()) + } + + fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { + if bitmap.width() > self.descriptors.limits.max_texture_dimension_2d + || bitmap.height() > self.descriptors.limits.max_texture_dimension_2d + { + return Err(BitmapError::TooLarge); + } + + let bitmap = bitmap.to_rgba(); + let extent = wgpu::Extent3d { + width: bitmap.width(), + height: bitmap.height(), + depth_or_array_layers: 1, + }; + + let texture_label = create_debug_label!("Bitmap"); + let texture = self + .descriptors + .device + .create_texture(&wgpu::TextureDescriptor { + label: texture_label.as_deref(), + size: extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_SRC, + }); + + self.descriptors.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: Default::default(), + aspect: wgpu::TextureAspect::All, + }, + bitmap.data(), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(4 * extent.width), + rows_per_image: None, + }, + extent, + ); + + let handle = self.next_bitmap_handle; + self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1); + let width = bitmap.width(); + let height = bitmap.height(); + + // Make bind group for bitmap quad. + let texture_view = texture.create_view(&Default::default()); + let bind_group = self + .descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &target_data!(self).pipelines.bitmap_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &self.quad_tex_transforms, + offset: 0, + size: wgpu::BufferSize::new( + std::mem::size_of::() as u64 + ), + }), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + ], + label: create_debug_label!("Bitmap {} bind group", handle.0).as_deref(), + }); + + if self + .bitmap_registry + .insert( + handle, + RegistryData { + bitmap, + texture_wrapper: Texture { + width, + height, + texture, + bind_group, + texture_offscreen: None, + }, + }, + ) + .is_some() + { + panic!("Overwrote existing bitmap {:?}", handle); + } + + Ok(handle) + } + + fn unregister_bitmap(&mut self, handle: BitmapHandle) { + self.bitmap_registry.remove(&handle); + } + + fn update_texture( + &mut self, + handle: BitmapHandle, + width: u32, + height: u32, + rgba: Vec, + ) -> Result { + let texture = if let Some(entry) = self.bitmap_registry.get(&handle) { + &entry.texture_wrapper.texture + } else { + log::warn!("Tried to replace nonexistent texture"); + return Ok(handle); + }; + + let extent = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + self.descriptors.queue.write_texture( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin: Default::default(), + aspect: wgpu::TextureAspect::All, + }, + &rgba, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new(4 * extent.width), + rows_per_image: None, + }, + extent, + ); + + Ok(handle) + } +} + +fn create_depth_texture_view( + descriptors: &Descriptors, + target_data: &DescriptorsTargetData, + extent: wgpu::Extent3d, +) -> wgpu::TextureView { + let depth_texture = descriptors.device.create_texture(&wgpu::TextureDescriptor { + label: create_debug_label!("Depth texture").as_deref(), + size: extent, + mip_level_count: 1, + sample_count: target_data.msaa_sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth24PlusStencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + depth_texture.create_view(&Default::default()) +} + +// Based on https://github.com/Sgeo/take_mut +fn take_mut(mut_ref: &mut T, closure: F) -> R +where + F: FnOnce(T) -> (T, R), +{ + unsafe { + let old_t = std::ptr::read(mut_ref); + let (new_t, ret) = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| closure(old_t))) + .unwrap_or_else(|e| { + eprintln!("Caught panic: {:?}", e); + ::std::process::abort() + }); + std::ptr::write(mut_ref, new_t); + ret + } +} + +// We try to request the highest limits we can get away with +async fn request_device( + adapter: &wgpu::Adapter, + trace_path: Option<&Path>, +) -> Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> { + // We start off with the lowest limits we actually need - basically GL-ES 3.0 + let mut limits = wgpu::Limits::downlevel_webgl2_defaults(); + // Then we increase parts of it to the maximum supported by the adapter, to take advantage of + // more powerful hardware or capabilities + limits = limits.using_resolution(adapter.limits()); + limits = limits.using_alignment(adapter.limits()); + + limits.max_storage_buffers_per_shader_stage = + adapter.limits().max_storage_buffers_per_shader_stage; + limits.max_storage_buffer_binding_size = adapter.limits().max_storage_buffer_binding_size; + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits, + }, + trace_path, + ) + .await +} diff --git a/render/wgpu/src/lib.rs b/render/wgpu/src/lib.rs index c34506f99..7f8fbf9bf 100644 --- a/render/wgpu/src/lib.rs +++ b/render/wgpu/src/lib.rs @@ -1,29 +1,15 @@ use crate::bitmaps::BitmapSamplers; -use crate::descriptors::DescriptorsTargetData; use crate::globals::Globals; use crate::pipelines::Pipelines; -use crate::target::TextureTarget; -use crate::target::{RenderTarget, RenderTargetFrame, SwapChainTarget}; +use crate::target::{RenderTarget, SwapChainTarget}; use crate::uniform_buffer::UniformBuffer; use crate::utils::{create_buffer_with_data, format_list, get_backend_names, BufferDimensions}; use bytemuck::{Pod, Zeroable}; use descriptors::Descriptors; use enum_map::Enum; -use fnv::FnvHashMap; -use ruffle_render::backend::{RenderBackend, ShapeHandle, ViewportDimensions}; -use ruffle_render::bitmap::{Bitmap, BitmapHandle, BitmapSource}; +use ruffle_render::bitmap::Bitmap; use ruffle_render::color_transform::ColorTransform; -use ruffle_render::error::Error as BitmapError; -use ruffle_render::shape_utils::DistilledShape; -use ruffle_render::tessellator::{ - DrawType as TessDrawType, Gradient as TessGradient, GradientType, ShapeTessellator, - Vertex as TessVertex, -}; -use ruffle_render::transform::Transform; -use std::num::NonZeroU32; -use std::path::Path; -use std::sync::Arc; -use swf::{BlendMode, Color}; +use ruffle_render::tessellator::{Gradient as TessGradient, GradientType, Vertex as TessVertex}; pub use wgpu; type Error = Box; @@ -37,38 +23,11 @@ mod pipelines; pub mod target; mod uniform_buffer; +pub mod backend; #[cfg(feature = "clap")] pub mod clap; pub mod descriptors; -pub const DEFAULT_SAMPLE_COUNT: u32 = 4; - -pub struct WgpuRenderBackend { - descriptors: Arc, - globals: Globals, - uniform_buffers: UniformBuffer, - target: T, - frame_buffer_view: Option, - depth_texture_view: wgpu::TextureView, - copy_srgb_view: Option, - copy_srgb_bind_group: Option, - current_frame: Option>, - meshes: Vec, - mask_state: MaskState, - shape_tessellator: ShapeTessellator, - num_masks: u32, - quad_vbo: wgpu::Buffer, - quad_ibo: wgpu::Buffer, - quad_tex_transforms: wgpu::Buffer, - blend_modes: Vec, - bitmap_registry: FnvHashMap, - next_bitmap_handle: BitmapHandle, - // This is currently unused - we just store it to report in - // `get_viewport_dimensions` - viewport_scale_factor: f64, - offscreen: bool, -} - struct RegistryData { bitmap: Bitmap, texture_wrapper: Texture, @@ -269,1490 +228,6 @@ enum DrawType { }, } -impl WgpuRenderBackend { - #[cfg(target_family = "wasm")] - pub async fn for_canvas(canvas: &web_sys::HtmlCanvasElement) -> Result { - let instance = wgpu::Instance::new(wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL); - let surface = instance.create_surface_from_canvas(canvas); - let descriptors = Self::build_descriptors( - wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL, - instance, - Some(&surface), - wgpu::PowerPreference::HighPerformance, - None, - ) - .await?; - let target = SwapChainTarget::new( - surface, - descriptors.onscreen.surface_format, - (1, 1), - &descriptors.device, - ); - Self::new(Arc::new(descriptors), target) - } - - #[cfg(not(target_family = "wasm"))] - pub fn for_window( - window: &W, - size: (u32, u32), - backend: wgpu::Backends, - power_preference: wgpu::PowerPreference, - trace_path: Option<&Path>, - ) -> Result { - if wgpu::Backends::SECONDARY.contains(backend) { - log::warn!( - "{} graphics backend support may not be fully supported.", - format_list(&get_backend_names(backend), "and") - ); - } - let instance = wgpu::Instance::new(backend); - let surface = unsafe { instance.create_surface(window) }; - let descriptors = futures::executor::block_on(Self::build_descriptors( - backend, - instance, - Some(&surface), - power_preference, - trace_path, - ))?; - let target = SwapChainTarget::new( - surface, - descriptors.onscreen.surface_format, - size, - &descriptors.device, - ); - Self::new(Arc::new(descriptors), target) - } -} - -#[cfg(not(target_family = "wasm"))] -impl WgpuRenderBackend { - pub fn for_offscreen( - size: (u32, u32), - backend: wgpu::Backends, - power_preference: wgpu::PowerPreference, - trace_path: Option<&Path>, - ) -> Result { - if wgpu::Backends::SECONDARY.contains(backend) { - log::warn!( - "{} graphics backend support may not be fully supported.", - format_list(&get_backend_names(backend), "and") - ); - } - let instance = wgpu::Instance::new(backend); - let descriptors = futures::executor::block_on(Self::build_descriptors( - backend, - instance, - None, - power_preference, - trace_path, - ))?; - let target = target::TextureTarget::new(&descriptors.device, size)?; - Self::new(Arc::new(descriptors), target) - } - - pub fn capture_frame(&self, premultiplied_alpha: bool) -> Option { - self.target - .capture(&self.descriptors.device, premultiplied_alpha) - } -} - -macro_rules! target_data { - ($this:expr) => {{ - if $this.offscreen { - &$this.descriptors.offscreen - } else { - &$this.descriptors.onscreen - } - }}; -} - -impl WgpuRenderBackend { - pub fn new(descriptors: Arc, target: T) -> Result { - if target.width() > descriptors.limits.max_texture_dimension_2d - || target.height() > descriptors.limits.max_texture_dimension_2d - { - return Err(format!( - "Render target texture cannot be larger than {}px on either dimension (requested {} x {})", - descriptors.limits.max_texture_dimension_2d, - target.width(), - target.height() - ) - .into()); - } - - let extent = wgpu::Extent3d { - width: target.width(), - height: target.height(), - depth_or_array_layers: 1, - }; - - let frame_buffer_view = if descriptors.onscreen.msaa_sample_count > 1 { - let frame_buffer = descriptors.device.create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Framebuffer texture").as_deref(), - size: extent, - mip_level_count: 1, - sample_count: descriptors.onscreen.msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: descriptors.onscreen.frame_buffer_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - Some(frame_buffer.create_view(&Default::default())) - } else { - None - }; - - let depth_texture = descriptors.device.create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Depth texture").as_deref(), - size: extent, - mip_level_count: 1, - sample_count: descriptors.onscreen.msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24PlusStencil8, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - let depth_texture_view = depth_texture.create_view(&Default::default()); - - let (quad_vbo, quad_ibo, quad_tex_transforms) = create_quad_buffers(&descriptors.device); - - let (copy_srgb_view, copy_srgb_bind_group) = if descriptors.onscreen.frame_buffer_format - != descriptors.offscreen.surface_format - { - let copy_srgb_buffer = descriptors.device.create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Copy sRGB framebuffer texture").as_deref(), - size: extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: descriptors.onscreen.frame_buffer_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - }); - let copy_srgb_view = copy_srgb_buffer.create_view(&Default::default()); - let copy_srgb_bind_group = - descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &descriptors.onscreen.pipelines.bitmap_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &quad_tex_transforms, - offset: 0, - size: wgpu::BufferSize::new( - std::mem::size_of::() as u64, - ), - }), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(©_srgb_view), - }, - ], - label: create_debug_label!("Copy sRGB bind group").as_deref(), - }); - (Some(copy_srgb_view), Some(copy_srgb_bind_group)) - } else { - (None, None) - }; - - let mut globals = Globals::new(&descriptors.device, &descriptors.globals_layout); - globals.set_resolution(target.width(), target.height()); - - let uniform_buffers = - UniformBuffer::new(descriptors.limits.min_uniform_buffer_offset_alignment); - - Ok(Self { - descriptors, - globals, - uniform_buffers, - target, - frame_buffer_view, - depth_texture_view, - copy_srgb_view, - copy_srgb_bind_group, - current_frame: None, - meshes: Vec::new(), - shape_tessellator: ShapeTessellator::new(), - - num_masks: 0, - mask_state: MaskState::NoMask, - - quad_vbo, - quad_ibo, - quad_tex_transforms, - blend_modes: vec![BlendMode::Normal], - bitmap_registry: Default::default(), - next_bitmap_handle: BitmapHandle(0), - viewport_scale_factor: 1.0, - offscreen: false, - }) - } - - pub async fn build_descriptors( - backend: wgpu::Backends, - instance: wgpu::Instance, - surface: Option<&wgpu::Surface>, - power_preference: wgpu::PowerPreference, - trace_path: Option<&Path>, - ) -> Result { - let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference, - compatible_surface: surface, - force_fallback_adapter: false, - }).await - .ok_or_else(|| { - let names = get_backend_names(backend); - if names.is_empty() { - "Ruffle requires hardware acceleration, but no compatible graphics device was found (no backend provided?)".to_string() - } else if cfg!(any(windows, target_os = "macos")) { - format!("Ruffle does not support OpenGL on {}.", if cfg!(windows) { "Windows" } else { "macOS" }) - } else { - format!("Ruffle requires hardware acceleration, but no compatible graphics device was found supporting {}", format_list(&names, "or")) - } - })?; - - let (device, queue) = request_device(&adapter, trace_path).await?; - let info = adapter.get_info(); - // Ideally we want to use an RGBA non-sRGB surface format, because Flash colors and - // blending are done in sRGB space -- we don't want the GPU to adjust the colors. - // Some platforms may only support an sRGB surface, in which case we will draw to an - // intermediate linear buffer and then copy to the sRGB surface. - let surface_format = surface - .and_then(|surface| { - let formats = surface.get_supported_formats(&adapter); - formats - .iter() - .find(|format| { - matches!( - format, - wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm - ) - }) - .or_else(|| formats.first()) - .cloned() - }) - // No surface (rendering to texture), default to linear RBGA. - .unwrap_or(wgpu::TextureFormat::Rgba8Unorm); - // TODO: Allow the sample count to be set from command line/settings file. - Ok(Descriptors::new( - device, - queue, - info, - surface_format, - DEFAULT_SAMPLE_COUNT, - )) - } - - fn register_shape_internal( - &mut self, - shape: DistilledShape, - bitmap_source: &dyn BitmapSource, - ) -> Mesh { - let shape_id = shape.id; // TODO: remove? - let lyon_mesh = self - .shape_tessellator - .tessellate_shape(shape, bitmap_source); - - let mut draws = Vec::with_capacity(lyon_mesh.len()); - for draw in lyon_mesh { - let vertices: Vec<_> = draw.vertices.into_iter().map(Vertex::from).collect(); - let vertex_buffer = create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&vertices), - wgpu::BufferUsages::VERTEX, - create_debug_label!("Shape {} ({}) vbo", shape_id, draw.draw_type.name()), - ); - - let index_buffer = create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&draw.indices), - wgpu::BufferUsages::INDEX, - create_debug_label!("Shape {} ({}) ibo", shape_id, draw.draw_type.name()), - ); - - let index_count = draw.indices.len() as u32; - let draw_id = draws.len(); - - draws.push(match draw.draw_type { - TessDrawType::Color => Draw { - draw_type: DrawType::Color, - vertex_buffer, - index_buffer, - num_indices: index_count, - num_mask_indices: draw.mask_index_count, - }, - TessDrawType::Gradient(gradient) => { - // TODO: Extract to function? - let mut texture_transform = [[0.0; 4]; 4]; - texture_transform[0][..3].copy_from_slice(&gradient.matrix[0]); - texture_transform[1][..3].copy_from_slice(&gradient.matrix[1]); - texture_transform[2][..3].copy_from_slice(&gradient.matrix[2]); - - let tex_transforms_ubo = create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&[texture_transform]), - wgpu::BufferUsages::UNIFORM, - create_debug_label!( - "Shape {} draw {} textransforms ubo transfer buffer", - shape_id, - draw_id - ), - ); - - let (gradient_ubo, buffer_size) = if self - .descriptors - .limits - .max_storage_buffers_per_shader_stage - > 0 - { - ( - create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&[GradientStorage::from(gradient)]), - wgpu::BufferUsages::STORAGE, - create_debug_label!( - "Shape {} draw {} gradient ubo transfer buffer", - shape_id, - draw_id - ), - ), - wgpu::BufferSize::new(std::mem::size_of::() as u64), - ) - } else { - ( - create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&[GradientUniforms::from(gradient)]), - wgpu::BufferUsages::UNIFORM, - create_debug_label!( - "Shape {} draw {} gradient ubo transfer buffer", - shape_id, - draw_id - ), - ), - wgpu::BufferSize::new(std::mem::size_of::() as u64), - ) - }; - - let bind_group_label = create_debug_label!( - "Shape {} (gradient) draw {} bindgroup", - shape_id, - draw_id - ); - let bind_group = - self.descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &target_data!(self).pipelines.gradient_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &tex_transforms_ubo, - offset: 0, - size: wgpu::BufferSize::new(std::mem::size_of::< - TextureTransforms, - >( - ) - as u64), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &gradient_ubo, - offset: 0, - size: buffer_size, - }, - ), - }, - ], - label: bind_group_label.as_deref(), - }); - - Draw { - draw_type: DrawType::Gradient { - texture_transforms: tex_transforms_ubo, - gradient: gradient_ubo, - bind_group, - }, - vertex_buffer, - index_buffer, - num_indices: index_count, - num_mask_indices: draw.mask_index_count, - } - } - TessDrawType::Bitmap(bitmap) => { - let entry = self.bitmap_registry.get(&bitmap.bitmap).unwrap(); - let texture_view = entry - .texture_wrapper - .texture - .create_view(&Default::default()); - - // TODO: Extract to function? - let mut texture_transform = [[0.0; 4]; 4]; - texture_transform[0][..3].copy_from_slice(&bitmap.matrix[0]); - texture_transform[1][..3].copy_from_slice(&bitmap.matrix[1]); - texture_transform[2][..3].copy_from_slice(&bitmap.matrix[2]); - - let tex_transforms_ubo = create_buffer_with_data( - &self.descriptors.device, - bytemuck::cast_slice(&[texture_transform]), - wgpu::BufferUsages::UNIFORM, - create_debug_label!( - "Shape {} draw {} textransforms ubo transfer buffer", - shape_id, - draw_id - ), - ); - - let bind_group_label = create_debug_label!( - "Shape {} (bitmap) draw {} bindgroup", - shape_id, - draw_id - ); - let bind_group = - self.descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &target_data!(self).pipelines.bitmap_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &tex_transforms_ubo, - offset: 0, - size: wgpu::BufferSize::new(std::mem::size_of::< - TextureTransforms, - >( - ) - as u64), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&texture_view), - }, - ], - label: bind_group_label.as_deref(), - }); - - Draw { - draw_type: DrawType::Bitmap { - texture_transforms: tex_transforms_ubo, - texture_view, - is_smoothed: bitmap.is_smoothed, - is_repeating: bitmap.is_repeating, - bind_group, - }, - vertex_buffer, - index_buffer, - num_indices: index_count, - num_mask_indices: draw.mask_index_count, - } - } - }); - } - - Mesh { draws } - } - - fn blend_mode(&self) -> BlendMode { - *self.blend_modes.last().unwrap() - } - - pub fn descriptors(&self) -> &Arc { - &self.descriptors - } - - pub fn target(&self) -> &T { - &self.target - } - - pub fn device(&self) -> &wgpu::Device { - &self.descriptors.device - } - - fn render_offscreen_internal( - mut self, - handle: BitmapHandle, - width: u32, - height: u32, - f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>, - ) -> Result<(Self, ruffle_render::bitmap::Bitmap), ruffle_render::error::Error> { - // We need ownership of `Texture` to access the non-`Clone` - // `wgpu` fields. At the end of this method, we re-insert - // `texture` into the map. - // - // This means that the target texture will be inaccessible - // while the callback `f` is a problem. This would only be - // an issue if a caller tried to render the target texture - // to itself, which probably isn't supported by Flash. If it - // is, then we could change `TextureTarget` to use an `Rc` - let mut texture = self.bitmap_registry.remove(&handle).unwrap(); - - let extent = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - - if self.offscreen { - panic!("Nested render_onto_bitmap is not supported!") - } - - let descriptors = self.descriptors.clone(); - - // We will (presumably) never render to the majority of textures, so - // we lazily create the buffer and depth texture. - // Once created, we never destroy this data, under the assumption - // that the SWF will try to render to this more than once. - // - // If we end up hitting wgpu device limits due to having too - // many buffers / depth textures rendered at once, we could - // try storing this data in an LRU cache, evicting entries - // as needed. - let mut texture_offscreen = - texture - .texture_wrapper - .texture_offscreen - .unwrap_or_else(|| { - let depth_texture_view = - create_depth_texture_view(&descriptors, &descriptors.offscreen, extent); - let buffer_dimensions = BufferDimensions::new(width as usize, height as usize); - let buffer_label = create_debug_label!("Render target buffer"); - let buffer = descriptors.device.create_buffer(&wgpu::BufferDescriptor { - label: buffer_label.as_deref(), - size: (buffer_dimensions.padded_bytes_per_row.get() as u64 - * buffer_dimensions.height as u64), - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, - mapped_at_creation: false, - }); - TextureOffscreen { - depth_texture_view, - buffer, - buffer_dimensions, - } - }); - - let target = TextureTarget { - size: extent, - texture: texture.texture_wrapper.texture, - format: wgpu::TextureFormat::Rgba8Unorm, - buffer: texture_offscreen.buffer, - buffer_dimensions: texture_offscreen.buffer_dimensions, - }; - - let (old_width, old_height) = self.globals.resolution(); - - // Is it worth caching this? - self.globals.set_resolution(width, height); - - let mut texture_backend = WgpuRenderBackend { - descriptors, - target, - // FIXME - Enable MSAA for textures - frame_buffer_view: None, - depth_texture_view: texture_offscreen.depth_texture_view, - // We explicitly request a non-SRGB format for textures - copy_srgb_view: None, - copy_srgb_bind_group: None, - current_frame: None, - meshes: self.meshes, - mask_state: MaskState::NoMask, - shape_tessellator: self.shape_tessellator, - num_masks: 0, - quad_vbo: self.quad_vbo, - quad_ibo: self.quad_ibo, - quad_tex_transforms: self.quad_tex_transforms, - bitmap_registry: self.bitmap_registry, - offscreen: true, - next_bitmap_handle: self.next_bitmap_handle, - globals: self.globals, - uniform_buffers: self.uniform_buffers, - viewport_scale_factor: self.viewport_scale_factor, - blend_modes: vec![BlendMode::Normal], - }; - - let f_res = f(&mut texture_backend); - // Capture with premultiplied alpha, which is what we use for all textures - let image = texture_backend - .target - .capture(&texture_backend.descriptors.device, true); - - let image = image.map(|image| { - ruffle_render::bitmap::Bitmap::new( - image.dimensions().0, - image.dimensions().1, - ruffle_render::bitmap::BitmapFormat::Rgba, - image.into_raw(), - ) - }); - - self.offscreen = false; - self.meshes = texture_backend.meshes; - self.shape_tessellator = texture_backend.shape_tessellator; - self.bitmap_registry = texture_backend.bitmap_registry; - self.quad_tex_transforms = texture_backend.quad_tex_transforms; - self.quad_ibo = texture_backend.quad_ibo; - self.quad_vbo = texture_backend.quad_vbo; - self.globals = texture_backend.globals; - self.uniform_buffers = texture_backend.uniform_buffers; - self.next_bitmap_handle = texture_backend.next_bitmap_handle; - self.globals.set_resolution(old_width, old_height); - - texture_offscreen.buffer = texture_backend.target.buffer; - texture_offscreen.buffer_dimensions = texture_backend.target.buffer_dimensions; - texture_offscreen.depth_texture_view = texture_backend.depth_texture_view; - texture.texture_wrapper.texture_offscreen = Some(texture_offscreen); - texture.texture_wrapper.texture = texture_backend.target.texture; - self.bitmap_registry.insert(handle, texture); - - // Check result after restoring the backend fields - f_res?; - - Ok((self, image.unwrap())) - } -} - -impl RenderBackend for WgpuRenderBackend { - fn set_viewport_dimensions(&mut self, dimensions: ViewportDimensions) { - // Avoid panics from creating 0-sized framebuffers. - // TODO: find a way to bubble an error when the size is too large - let width = std::cmp::max( - std::cmp::min( - dimensions.width, - self.descriptors.limits.max_texture_dimension_2d, - ), - 1, - ); - let height = std::cmp::max( - std::cmp::min( - dimensions.height, - self.descriptors.limits.max_texture_dimension_2d, - ), - 1, - ); - self.target.resize(&self.descriptors.device, width, height); - - let size = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - - self.frame_buffer_view = if target_data!(self).msaa_sample_count > 1 { - let frame_buffer = self - .descriptors - .device - .create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Framebuffer texture").as_deref(), - size, - mip_level_count: 1, - sample_count: target_data!(self).msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: target_data!(self).frame_buffer_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - Some(frame_buffer.create_view(&Default::default())) - } else { - None - }; - - let depth_texture = self - .descriptors - .device - .create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Depth texture").as_deref(), - size, - mip_level_count: 1, - sample_count: target_data!(self).msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24PlusStencil8, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - self.depth_texture_view = depth_texture.create_view(&Default::default()); - - (self.copy_srgb_view, self.copy_srgb_bind_group) = if target_data!(self).frame_buffer_format - != target_data!(self).surface_format - { - let copy_srgb_buffer = - self.descriptors - .device - .create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Copy sRGB framebuffer texture").as_deref(), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: target_data!(self).frame_buffer_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - }); - let copy_srgb_view = copy_srgb_buffer.create_view(&Default::default()); - let copy_srgb_bind_group = - self.descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &target_data!(self).pipelines.bitmap_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &self.quad_tex_transforms, - offset: 0, - size: wgpu::BufferSize::new( - std::mem::size_of::() as u64, - ), - }), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(©_srgb_view), - }, - ], - label: create_debug_label!("Copy sRGB bind group").as_deref(), - }); - (Some(copy_srgb_view), Some(copy_srgb_bind_group)) - } else { - (None, None) - }; - - self.globals.set_resolution(width, height); - self.viewport_scale_factor = dimensions.scale_factor; - } - - fn viewport_dimensions(&self) -> ViewportDimensions { - ViewportDimensions { - width: self.target.width(), - height: self.target.height(), - scale_factor: self.viewport_scale_factor, - } - } - - fn register_shape( - &mut self, - shape: DistilledShape, - bitmap_source: &dyn BitmapSource, - ) -> ShapeHandle { - let handle = ShapeHandle(self.meshes.len()); - let mesh = self.register_shape_internal(shape, bitmap_source); - self.meshes.push(mesh); - handle - } - - fn replace_shape( - &mut self, - shape: DistilledShape, - bitmap_source: &dyn BitmapSource, - handle: ShapeHandle, - ) { - let mesh = self.register_shape_internal(shape, bitmap_source); - self.meshes[handle.0] = mesh; - } - - fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle { - let shape = ruffle_render::shape_utils::swf_glyph_to_shape(glyph); - let handle = ShapeHandle(self.meshes.len()); - let mesh = self.register_shape_internal( - (&shape).into(), - &ruffle_render::backend::null::NullBitmapSource, - ); - self.meshes.push(mesh); - handle - } - - fn begin_frame(&mut self, clear: Color) { - self.mask_state = MaskState::NoMask; - self.num_masks = 0; - self.uniform_buffers.reset(); - - let frame_output = match self.target.get_next_texture() { - Ok(frame) => frame, - Err(e) => { - log::warn!("Couldn't begin new render frame: {}", e); - // Attemp to recreate the swap chain in this case. - self.target.resize( - &self.descriptors.device, - self.target.width(), - self.target.height(), - ); - return; - } - }; - - let label = create_debug_label!("Draw encoder"); - let draw_encoder = - self.descriptors - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: label.as_deref(), - }); - let uniform_encoder_label = create_debug_label!("Uniform upload command encoder"); - let uniform_encoder = - self.descriptors - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: uniform_encoder_label.as_deref(), - }); - let mut frame_data = Box::new((draw_encoder, frame_output, uniform_encoder)); - - self.globals - .update_uniform(&self.descriptors.device, &mut frame_data.0); - - // Use intermediate render targets when resolving MSAA or copying from linear-to-sRGB texture. - let (color_view, resolve_target) = match (&self.frame_buffer_view, &self.copy_srgb_view) { - (None, None) => (frame_data.1.view(), None), - (None, Some(copy)) => (copy, None), - (Some(frame_buffer), None) => (frame_buffer, Some(frame_data.1.view())), - (Some(frame_buffer), Some(copy)) => (frame_buffer, Some(copy)), - }; - - let render_pass = frame_data.0.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: color_view, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: f64::from(clear.r) / 255.0, - g: f64::from(clear.g) / 255.0, - b: f64::from(clear.b) / 255.0, - a: f64::from(clear.a) / 255.0, - }), - store: true, - }, - resolve_target, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_texture_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0.0), - store: false, - }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0), - store: true, - }), - }), - label: None, - }); - - // Since RenderPass holds a reference to the CommandEncoder, we cast the lifetime - // away to allow for the self-referencing struct. draw_encoder is boxed so its - // address should remain stable. - self.current_frame = Some(Frame { - render_pass: unsafe { - std::mem::transmute::<_, wgpu::RenderPass<'static>>(render_pass) - }, - frame_data, - }); - } - - fn render_offscreen( - &mut self, - handle: BitmapHandle, - width: u32, - height: u32, - f: &mut dyn FnMut(&mut dyn RenderBackend) -> Result<(), ruffle_render::error::Error>, - ) -> Result { - // Rendering to a texture backend requires us to use non-`Clone` - // wgpu resources (e.g. `wgpu::Device`, `wgpu::Queue`. - // - // We expect that in the majority of SWFs, we will spend most - // of our time performing 'normal' (non-offscreen) renders. - // Therefore, we want to avoid penalizing this case by adding - // in a check for 'normal' or 'offscreen' mode in the main - // rendering code. - // - // To accomplish this, we use `take_mut` to temporarily - // move out of `self`. This allows us to construct a new - // `WgpuRenderBackend` with a `TextureTarget` corresponding to - // `handle`. This allows us to re-use many of the fields from - // our normal `WgpuRenderBackend` without wrapping in an `Rc` - // or other indirection. - // - // Note that `take_mut` causes the process to abort if the - // `with_offscreen_render_backend_internal` panics, since - // the `&mut self` reference would be logically uninitialized. - // However, we normally compile Ruffle with `panic=abort`, - // so this shouldn't actually have an effect in practice. - // Even with `panic=unwind`, we would still get a backtrace - // printed, and there's not really much point in attempting - // to recover from a partially failed render operation, anyway. - Ok(take_mut(self, |this| { - this.render_offscreen_internal(handle, width, height, f) - .expect("Failed to render to offscreen backend") - })) - } - - fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform, smoothing: bool) { - let target_data = target_data!(self); - if let Some(entry) = self.bitmap_registry.get(&bitmap) { - let texture = &entry.texture_wrapper; - let blend_mode = self.blend_mode(); - let frame = if let Some(frame) = &mut self.current_frame { - frame.get() - } else { - return; - }; - - let transform = Transform { - matrix: transform.matrix - * ruffle_render::matrix::Matrix { - a: texture.width as f32, - d: texture.height as f32, - ..Default::default() - }, - ..*transform - }; - - let world_matrix = [ - [transform.matrix.a, transform.matrix.b, 0.0, 0.0], - [transform.matrix.c, transform.matrix.d, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [ - transform.matrix.tx.to_pixels() as f32, - transform.matrix.ty.to_pixels() as f32, - 0.0, - 1.0, - ], - ]; - - frame.render_pass.set_pipeline( - target_data - .pipelines - .bitmap_pipelines - .pipeline_for(blend_mode.into(), self.mask_state), - ); - frame - .render_pass - .set_bind_group(0, self.globals.bind_group(), &[]); - - self.uniform_buffers.write_uniforms( - &self.descriptors.device, - &self.descriptors.uniform_buffers_layout, - &mut frame.frame_data.2, - &mut frame.render_pass, - 1, - &Transforms { - world_matrix, - color_adjustments: ColorAdjustments::from(transform.color_transform), - }, - ); - - frame - .render_pass - .set_bind_group(2, &texture.bind_group, &[]); - frame.render_pass.set_bind_group( - 3, - self.descriptors - .bitmap_samplers - .get_bind_group(false, smoothing), - &[], - ); - frame - .render_pass - .set_vertex_buffer(0, self.quad_vbo.slice(..)); - frame - .render_pass - .set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); - - match self.mask_state { - MaskState::NoMask => (), - MaskState::DrawMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks - 1); - } - MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks); - } - }; - - frame.render_pass.draw_indexed(0..6, 0, 0..1); - } - } - - fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) { - let blend_mode = self.blend_mode(); - let frame = if let Some(frame) = &mut self.current_frame { - frame.get() - } else { - return; - }; - - let mesh = &mut self.meshes[shape.0]; - - let world_matrix = [ - [transform.matrix.a, transform.matrix.b, 0.0, 0.0], - [transform.matrix.c, transform.matrix.d, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [ - transform.matrix.tx.to_pixels() as f32, - transform.matrix.ty.to_pixels() as f32, - 0.0, - 1.0, - ], - ]; - - frame - .render_pass - .set_bind_group(0, self.globals.bind_group(), &[]); - - self.uniform_buffers.write_uniforms( - &self.descriptors.device, - &self.descriptors.uniform_buffers_layout, - &mut frame.frame_data.2, - &mut frame.render_pass, - 1, - &Transforms { - world_matrix, - color_adjustments: ColorAdjustments::from(transform.color_transform), - }, - ); - - for draw in &mesh.draws { - let num_indices = if self.mask_state != MaskState::DrawMaskStencil - && self.mask_state != MaskState::ClearMaskStencil - { - draw.num_indices - } else { - // Omit strokes when drawing a mask stencil. - draw.num_mask_indices - }; - if num_indices == 0 { - continue; - } - - match &draw.draw_type { - DrawType::Color => { - frame.render_pass.set_pipeline( - target_data!(self) - .pipelines - .color_pipelines - .pipeline_for(blend_mode.into(), self.mask_state), - ); - } - DrawType::Gradient { bind_group, .. } => { - frame.render_pass.set_pipeline( - target_data!(self) - .pipelines - .gradient_pipelines - .pipeline_for(blend_mode.into(), self.mask_state), - ); - frame.render_pass.set_bind_group(2, bind_group, &[]); - } - DrawType::Bitmap { - is_repeating, - is_smoothed, - bind_group, - .. - } => { - frame.render_pass.set_pipeline( - target_data!(self) - .pipelines - .bitmap_pipelines - .pipeline_for(blend_mode.into(), self.mask_state), - ); - frame.render_pass.set_bind_group(2, bind_group, &[]); - frame.render_pass.set_bind_group( - 3, - self.descriptors - .bitmap_samplers - .get_bind_group(*is_repeating, *is_smoothed), - &[], - ); - } - } - - frame - .render_pass - .set_vertex_buffer(0, draw.vertex_buffer.slice(..)); - frame - .render_pass - .set_index_buffer(draw.index_buffer.slice(..), wgpu::IndexFormat::Uint32); - - match self.mask_state { - MaskState::NoMask => (), - MaskState::DrawMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks - 1); - } - MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks); - } - }; - - frame.render_pass.draw_indexed(0..num_indices, 0, 0..1); - } - } - - fn draw_rect(&mut self, color: Color, matrix: &ruffle_render::matrix::Matrix) { - let blend_mode = self.blend_mode(); - let frame = if let Some(frame) = &mut self.current_frame { - frame.get() - } else { - return; - }; - - let world_matrix = [ - [matrix.a, matrix.b, 0.0, 0.0], - [matrix.c, matrix.d, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [ - matrix.tx.to_pixels() as f32, - matrix.ty.to_pixels() as f32, - 0.0, - 1.0, - ], - ]; - - let mult_color = [ - f32::from(color.r) / 255.0, - f32::from(color.g) / 255.0, - f32::from(color.b) / 255.0, - f32::from(color.a) / 255.0, - ]; - - let add_color = [0.0, 0.0, 0.0, 0.0]; - frame.render_pass.set_pipeline( - target_data!(self) - .pipelines - .color_pipelines - .pipeline_for(blend_mode.into(), self.mask_state), - ); - - frame - .render_pass - .set_bind_group(0, self.globals.bind_group(), &[]); - - self.uniform_buffers.write_uniforms( - &self.descriptors.device, - &self.descriptors.uniform_buffers_layout, - &mut frame.frame_data.2, - &mut frame.render_pass, - 1, - &Transforms { - world_matrix, - color_adjustments: ColorAdjustments { - mult_color, - add_color, - }, - }, - ); - - frame - .render_pass - .set_vertex_buffer(0, self.quad_vbo.slice(..)); - frame - .render_pass - .set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); - - match self.mask_state { - MaskState::NoMask => (), - MaskState::DrawMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks - 1); - } - MaskState::DrawMaskedContent | MaskState::ClearMaskStencil => { - debug_assert!(self.num_masks > 0); - frame.render_pass.set_stencil_reference(self.num_masks); - } - }; - - frame.render_pass.draw_indexed(0..6, 0, 0..1); - } - - fn end_frame(&mut self) { - if let Some(frame) = self.current_frame.take() { - let draw_encoder = frame.frame_data.0; - let mut uniform_encoder = frame.frame_data.2; - let render_pass = frame.render_pass; - // Finalize render pass. - drop(render_pass); - - // If we have an sRGB surface, copy from our linear intermediate buffer to the sRGB surface. - let command_buffers = if let Some(copy_srgb_bind_group) = &self.copy_srgb_bind_group { - debug_assert!(self.copy_srgb_view.is_some()); - let mut copy_encoder = self.descriptors.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: create_debug_label!("Frame copy command encoder").as_deref(), - }, - ); - - let mut render_pass = copy_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: frame.frame_data.1.view(), - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), - store: true, - }, - resolve_target: None, - })], - depth_stencil_attachment: None, - label: None, - }); - - render_pass.set_pipeline(&target_data!(self).pipelines.copy_srgb_pipeline); - render_pass.set_bind_group(0, self.globals.bind_group(), &[]); - self.uniform_buffers.write_uniforms( - &self.descriptors.device, - &self.descriptors.uniform_buffers_layout, - &mut uniform_encoder, - &mut render_pass, - 1, - &Transforms { - world_matrix: [ - [self.target.width() as f32, 0.0, 0.0, 0.0], - [0.0, self.target.height() as f32, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - color_adjustments: ColorAdjustments { - mult_color: [1.0, 1.0, 1.0, 1.0], - add_color: [0.0, 0.0, 0.0, 0.0], - }, - }, - ); - render_pass.set_bind_group(2, copy_srgb_bind_group, &[]); - render_pass.set_bind_group( - 3, - self.descriptors - .bitmap_samplers - .get_bind_group(false, false), - &[], - ); - render_pass.set_vertex_buffer(0, self.quad_vbo.slice(..)); - render_pass.set_index_buffer(self.quad_ibo.slice(..), wgpu::IndexFormat::Uint32); - render_pass.draw_indexed(0..6, 0, 0..1); - drop(render_pass); - vec![ - uniform_encoder.finish(), - draw_encoder.finish(), - copy_encoder.finish(), - ] - } else { - vec![uniform_encoder.finish(), draw_encoder.finish()] - }; - - self.uniform_buffers.finish(); - self.target.submit( - &self.descriptors.device, - &self.descriptors.queue, - command_buffers, - frame.frame_data.1, - ); - } - } - - fn push_mask(&mut self) { - debug_assert!( - self.mask_state == MaskState::NoMask || self.mask_state == MaskState::DrawMaskedContent - ); - self.num_masks += 1; - self.mask_state = MaskState::DrawMaskStencil; - } - - fn activate_mask(&mut self) { - debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskStencil); - self.mask_state = MaskState::DrawMaskedContent; - } - - fn deactivate_mask(&mut self) { - debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::DrawMaskedContent); - self.mask_state = MaskState::ClearMaskStencil; - } - - fn pop_mask(&mut self) { - debug_assert!(self.num_masks > 0 && self.mask_state == MaskState::ClearMaskStencil); - self.num_masks -= 1; - self.mask_state = if self.num_masks == 0 { - MaskState::NoMask - } else { - MaskState::DrawMaskedContent - }; - } - - fn push_blend_mode(&mut self, blend: BlendMode) { - self.blend_modes.push(blend); - } - - fn pop_blend_mode(&mut self) { - self.blend_modes.pop(); - } - - fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> Option { - self.bitmap_registry.get(&bitmap).map(|e| e.bitmap.clone()) - } - - fn register_bitmap(&mut self, bitmap: Bitmap) -> Result { - if bitmap.width() > self.descriptors.limits.max_texture_dimension_2d - || bitmap.height() > self.descriptors.limits.max_texture_dimension_2d - { - return Err(BitmapError::TooLarge); - } - - let bitmap = bitmap.to_rgba(); - let extent = wgpu::Extent3d { - width: bitmap.width(), - height: bitmap.height(), - depth_or_array_layers: 1, - }; - - let texture_label = create_debug_label!("Bitmap"); - let texture = self - .descriptors - .device - .create_texture(&wgpu::TextureDescriptor { - label: texture_label.as_deref(), - size: extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_SRC, - }); - - self.descriptors.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: Default::default(), - aspect: wgpu::TextureAspect::All, - }, - bitmap.data(), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(4 * extent.width), - rows_per_image: None, - }, - extent, - ); - - let handle = self.next_bitmap_handle; - self.next_bitmap_handle = BitmapHandle(self.next_bitmap_handle.0 + 1); - let width = bitmap.width(); - let height = bitmap.height(); - - // Make bind group for bitmap quad. - let texture_view = texture.create_view(&Default::default()); - let bind_group = self - .descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &target_data!(self).pipelines.bitmap_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &self.quad_tex_transforms, - offset: 0, - size: wgpu::BufferSize::new( - std::mem::size_of::() as u64 - ), - }), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&texture_view), - }, - ], - label: create_debug_label!("Bitmap {} bind group", handle.0).as_deref(), - }); - - if self - .bitmap_registry - .insert( - handle, - RegistryData { - bitmap, - texture_wrapper: Texture { - width, - height, - texture, - bind_group, - texture_offscreen: None, - }, - }, - ) - .is_some() - { - panic!("Overwrote existing bitmap {:?}", handle); - } - - Ok(handle) - } - - fn unregister_bitmap(&mut self, handle: BitmapHandle) { - self.bitmap_registry.remove(&handle); - } - - fn update_texture( - &mut self, - handle: BitmapHandle, - width: u32, - height: u32, - rgba: Vec, - ) -> Result { - let texture = if let Some(entry) = self.bitmap_registry.get(&handle) { - &entry.texture_wrapper.texture - } else { - log::warn!("Tried to replace nonexistent texture"); - return Ok(handle); - }; - - let extent = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; - - self.descriptors.queue.write_texture( - wgpu::ImageCopyTexture { - texture, - mip_level: 0, - origin: Default::default(), - aspect: wgpu::TextureAspect::All, - }, - &rgba, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(4 * extent.width), - rows_per_image: None, - }, - extent, - ); - - Ok(handle) - } -} - -fn create_depth_texture_view( - descriptors: &Descriptors, - target_data: &DescriptorsTargetData, - extent: wgpu::Extent3d, -) -> wgpu::TextureView { - let depth_texture = descriptors.device.create_texture(&wgpu::TextureDescriptor { - label: create_debug_label!("Depth texture").as_deref(), - size: extent, - mip_level_count: 1, - sample_count: target_data.msaa_sample_count, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth24PlusStencil8, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - depth_texture.create_view(&Default::default()) -} - fn create_quad_buffers(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer, wgpu::Buffer) { let vertices = [ Vertex { @@ -1820,49 +295,3 @@ struct TextureOffscreen { buffer: wgpu::Buffer, buffer_dimensions: BufferDimensions, } - -// Based on https://github.com/Sgeo/take_mut -fn take_mut(mut_ref: &mut T, closure: F) -> R -where - F: FnOnce(T) -> (T, R), -{ - unsafe { - let old_t = std::ptr::read(mut_ref); - let (new_t, ret) = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| closure(old_t))) - .unwrap_or_else(|e| { - eprintln!("Caught panic: {:?}", e); - ::std::process::abort() - }); - std::ptr::write(mut_ref, new_t); - ret - } -} - -// We try to request the highest limits we can get away with -async fn request_device( - adapter: &wgpu::Adapter, - trace_path: Option<&Path>, -) -> Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> { - // We start off with the lowest limits we actually need - basically GL-ES 3.0 - let mut limits = wgpu::Limits::downlevel_webgl2_defaults(); - // Then we increase parts of it to the maximum supported by the adapter, to take advantage of - // more powerful hardware or capabilities - limits = limits.using_resolution(adapter.limits()); - limits = limits.using_alignment(adapter.limits()); - - limits.max_storage_buffers_per_shader_stage = - adapter.limits().max_storage_buffers_per_shader_stage; - limits.max_storage_buffer_binding_size = adapter.limits().max_storage_buffer_binding_size; - - adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits, - }, - trace_path, - ) - .await -} diff --git a/tests/tests/regression_tests.rs b/tests/tests/regression_tests.rs index d12a4f6cd..d809dbf59 100644 --- a/tests/tests/regression_tests.rs +++ b/tests/tests/regression_tests.rs @@ -18,7 +18,9 @@ use ruffle_core::{Player, PlayerBuilder, PlayerEvent, ViewportDimensions}; use ruffle_input_format::{AutomatedEvent, InputInjector, MouseButton as InputMouseButton}; #[cfg(feature = "imgtests")] -use ruffle_render_wgpu::{target::TextureTarget, wgpu, WgpuRenderBackend}; +use ruffle_render_wgpu::backend::WgpuRenderBackend; +#[cfg(feature = "imgtests")] +use ruffle_render_wgpu::{target::TextureTarget, wgpu}; use std::cell::RefCell; use std::collections::BTreeMap; use std::path::Path; diff --git a/web/src/lib.rs b/web/src/lib.rs index 4c50e3e29..6db0a16bb 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -1258,7 +1258,7 @@ async fn create_renderer( .dyn_into() .map_err(|_| "Expected HtmlCanvasElement")?; - match ruffle_render_wgpu::WgpuRenderBackend::for_canvas(&canvas).await { + match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas).await { Ok(renderer) => { return Ok((builder.with_renderer(renderer), canvas, "WebGPU")); } @@ -1275,7 +1275,7 @@ async fn create_renderer( .dyn_into() .map_err(|_| "Expected HtmlCanvasElement")?; - match ruffle_render_wgpu::WgpuRenderBackend::for_canvas(&canvas).await { + match ruffle_render_wgpu::backend::WgpuRenderBackend::for_canvas(&canvas).await { Ok(renderer) => { return Ok(( builder.with_renderer(renderer),