use crate::commands::CommandRenderer; use crate::frame::Frame; use crate::surface::Surface; use crate::target::RenderTargetFrame; use crate::target::TextureTarget; use crate::uniform_buffer::BufferStorage; use crate::{ create_buffer_with_data, format_list, get_backend_names, BufferDimensions, Descriptors, Draw, DrawType, Error, Globals, GradientStorage, GradientUniforms, 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::commands::CommandList; use ruffle_render::error::Error as BitmapError; use ruffle_render::shape_utils::DistilledShape; use ruffle_render::tessellator::{DrawType as TessDrawType, ShapeTessellator}; use std::num::NonZeroU32; use std::path::Path; use std::sync::Arc; use swf::Color; const DEFAULT_SAMPLE_COUNT: u32 = 4; pub struct WgpuRenderBackend { descriptors: Arc, globals: Globals, uniform_buffers_storage: BufferStorage, target: T, surface: Surface, meshes: Vec, shape_tessellator: ShapeTessellator, 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, } 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.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.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 = crate::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) } } 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 surface = Surface::new( &descriptors, descriptors.msaa_sample_count, target.width(), target.height(), descriptors.surface_format, descriptors.frame_buffer_format, ); let mut globals = Globals::new(&descriptors.device, &descriptors.bind_layouts.globals); globals.set_resolution(target.width(), target.height()); let uniform_buffers_storage = BufferStorage::new(descriptors.limits.min_uniform_buffer_offset_alignment); Ok(Self { descriptors, globals, uniform_buffers_storage, target, surface, meshes: Vec::new(), shape_tessellator: ShapeTessellator::new(), bitmap_registry: Default::default(), next_bitmap_handle: BitmapHandle(0), viewport_scale_factor: 1.0, }) } 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: &self.descriptors.bind_layouts.gradient, 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: &self.descriptors.bind_layouts.bitmap, 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 } } pub fn descriptors(&self) -> &Arc { &self.descriptors } pub fn target(&self) -> &T { &self.target } pub fn device(&self) -> &wgpu::Device { &self.descriptors.device } } 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); self.surface = Surface::new( &self.descriptors, self.descriptors.msaa_sample_count, width, height, self.descriptors.surface_format, self.descriptors.frame_buffer_format, ); 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 submit_frame(&mut self, clear: Color, commands: CommandList) { let frame_output = match self.target.get_next_texture() { Ok(frame) => frame, Err(e) => { log::warn!("Couldn't begin new render frame: {}", e); // Attempt 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 mut 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 mut uniform_encoder = self.descriptors .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: uniform_encoder_label.as_deref(), }); self.globals .update_uniform(&self.descriptors.device, &mut draw_encoder); let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: self.surface.view(frame_output.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: self.surface.resolve_target(frame_output.view()), })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: self.surface.depth(), 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, }); render_pass.set_bind_group(0, self.globals.bind_group(), &[]); self.uniform_buffers_storage.recall(); let mut frame = Frame::new( &self.descriptors.onscreen.pipelines, &self.descriptors, UniformBuffer::new(&mut self.uniform_buffers_storage), render_pass, &mut uniform_encoder, ); commands.execute(&mut CommandRenderer::new( &mut frame, &self.meshes, &self.bitmap_registry, self.descriptors.quad.vertices.slice(..), self.descriptors.quad.indices.slice(..), )); frame.finish(); let copy_encoder = self.surface.copy_srgb( &frame_output, &self.descriptors, &self.globals, &mut self.uniform_buffers_storage, &mut uniform_encoder, ); let mut command_buffers = vec![uniform_encoder.finish(), draw_encoder.finish()]; if let Some(copy_encoder) = copy_encoder { command_buffers.push(copy_encoder); } self.target.submit( &self.descriptors.device, &self.descriptors.queue, command_buffers, frame_output, ); } 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: &self.descriptors.bind_layouts.bitmap, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &self.descriptors.quad.texture_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 render_offscreen( &mut self, handle: BitmapHandle, width: u32, height: u32, commands: CommandList, clear_color: Color, ) -> Result { // 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, }; // 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 buffer_dimensions = BufferDimensions::new(width as usize, height as usize); let buffer_label = create_debug_label!("Render target buffer"); let buffer = self .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 { buffer, buffer_dimensions, surface: Surface::new( &self.descriptors, self.descriptors.msaa_sample_count, width, height, wgpu::TextureFormat::Rgba8Unorm, wgpu::TextureFormat::Rgba8Unorm, ), } }); let mut 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(); let frame_output = target .get_next_texture() .expect("TextureTargetFrame.get_next_texture is infallible"); let label = create_debug_label!("Draw encoder"); let mut 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 mut uniform_encoder = self.descriptors .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: uniform_encoder_label.as_deref(), }); self.globals.set_resolution(width, height); self.globals .update_uniform(&self.descriptors.device, &mut draw_encoder); let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: texture_offscreen.surface.view(frame_output.view()), ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: f64::from(clear_color.r) / 255.0, g: f64::from(clear_color.g) / 255.0, b: f64::from(clear_color.b) / 255.0, a: f64::from(clear_color.a) / 255.0, }), store: true, }, resolve_target: texture_offscreen .surface .resolve_target(frame_output.view()), })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: texture_offscreen.surface.depth(), 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, }); render_pass.set_bind_group(0, self.globals.bind_group(), &[]); self.uniform_buffers_storage.recall(); let mut frame = Frame::new( &self.descriptors.offscreen.pipelines, &self.descriptors, UniformBuffer::new(&mut self.uniform_buffers_storage), render_pass, &mut uniform_encoder, ); commands.execute(&mut CommandRenderer::new( &mut frame, &self.meshes, &self.bitmap_registry, self.descriptors.quad.vertices.slice(..), self.descriptors.quad.indices.slice(..), )); frame.finish(); target.submit( &self.descriptors.device, &self.descriptors.queue, vec![uniform_encoder.finish(), draw_encoder.finish()], frame_output, ); // Capture with premultiplied alpha, which is what we use for all textures let image = target.capture(&self.descriptors.device, true); let image = image.map(|image| { Bitmap::new( image.dimensions().0, image.dimensions().1, ruffle_render::bitmap::BitmapFormat::Rgba, image.into_raw(), ) }); self.globals.set_resolution(old_width, old_height); texture_offscreen.buffer = target.buffer; texture_offscreen.buffer_dimensions = target.buffer_dimensions; texture.texture_wrapper.texture_offscreen = Some(texture_offscreen); texture.texture_wrapper.texture = target.texture; self.bitmap_registry.insert(handle, texture); Ok(image.unwrap()) } } // 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 }