mod commands; pub mod target; use crate::backend::RenderTargetMode; use crate::blend::ComplexBlend; use crate::buffer_pool::TexturePool; use crate::filters::FilterSource; use crate::mesh::Mesh; use crate::pixel_bender::{run_pixelbender_shader_impl, ShaderMode}; use crate::surface::commands::{chunk_blends, Chunk, CommandRenderer}; use crate::utils::{remove_srgb, supported_sample_count}; use crate::{ColorAdjustments, Descriptors, MaskState, Pipelines, Transforms, UniformBuffer}; use ruffle_render::commands::CommandList; use ruffle_render::pixel_bender::{ImageInputTexture, PixelBenderShaderArgument}; use ruffle_render::quality::StageQuality; use std::sync::Arc; use target::CommandTarget; use tracing::instrument; use crate::utils::run_copy_pipeline; pub use crate::surface::commands::LayerRef; use self::commands::ChunkBlendMode; #[derive(Debug)] pub struct Surface { size: wgpu::Extent3d, quality: StageQuality, sample_count: u32, pipelines: Arc, format: wgpu::TextureFormat, actual_surface_format: wgpu::TextureFormat, } impl Surface { pub fn new( descriptors: &Descriptors, quality: StageQuality, width: u32, height: u32, surface_format: wgpu::TextureFormat, ) -> Self { let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1, }; let frame_buffer_format = remove_srgb(surface_format); let sample_count = supported_sample_count(&descriptors.adapter, quality, frame_buffer_format); let pipelines = descriptors.pipelines(sample_count, frame_buffer_format); Self { size, quality, sample_count, pipelines, format: frame_buffer_format, actual_surface_format: surface_format, } } #[allow(clippy::too_many_arguments)] #[instrument(level = "debug", skip_all)] pub fn draw_commands_and_copy_to<'frame, 'global: 'frame>( &mut self, frame_view: &wgpu::TextureView, render_target_mode: RenderTargetMode, descriptors: &'global Descriptors, uniform_buffers: &'frame mut UniformBuffer<'global, Transforms>, color_buffers: &'frame mut UniformBuffer<'global, ColorAdjustments>, uniform_encoder: &'frame mut wgpu::CommandEncoder, draw_encoder: &'frame mut wgpu::CommandEncoder, meshes: &'global Vec, commands: CommandList, layer: LayerRef, texture_pool: &mut TexturePool, ) { let target = self.draw_commands( render_target_mode, descriptors, meshes, commands, uniform_buffers, color_buffers, uniform_encoder, draw_encoder, layer, texture_pool, ); run_copy_pipeline( descriptors, self.format, self.actual_surface_format, self.size, frame_view, target.color_view(), target.whole_frame_bind_group(descriptors), target.globals(), 1, draw_encoder, ); } #[allow(clippy::too_many_arguments)] #[instrument(level = "debug", skip_all)] pub fn draw_commands<'frame, 'global: 'frame>( &mut self, render_target_mode: RenderTargetMode, descriptors: &'global Descriptors, meshes: &'global Vec, commands: CommandList, uniform_buffers: &'frame mut UniformBuffer<'global, Transforms>, color_buffers: &'frame mut UniformBuffer<'global, ColorAdjustments>, uniform_encoder: &'frame mut wgpu::CommandEncoder, draw_encoder: &'frame mut wgpu::CommandEncoder, nearest_layer: LayerRef<'frame>, texture_pool: &mut TexturePool, ) -> CommandTarget { let target = CommandTarget::new( descriptors, texture_pool, self.size, self.format, self.sample_count, render_target_mode, draw_encoder, ); let mut num_masks = 0; let mut mask_state = MaskState::NoMask; let chunks = chunk_blends( commands.commands, descriptors, uniform_buffers, color_buffers, uniform_encoder, draw_encoder, meshes, self.quality, target.width(), target.height(), match nearest_layer { LayerRef::Current => LayerRef::Parent(&target), layer => layer, }, texture_pool, ); for chunk in chunks { match chunk { Chunk::Draw(chunk, needs_depth) => { let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: create_debug_label!( "Chunked draw calls {}", if needs_depth { "(with depth)" } else { "(Depthless)" } ) .as_deref(), color_attachments: &[target.color_attachments()], depth_stencil_attachment: if needs_depth { target.depth_attachment(descriptors, texture_pool) } else { None }, }); render_pass.set_bind_group(0, target.globals().bind_group(), &[]); let mut renderer = CommandRenderer::new( &self.pipelines, descriptors, uniform_buffers, color_buffers, uniform_encoder, render_pass, num_masks, mask_state, needs_depth, ); for command in &chunk { renderer.execute(command); } num_masks = renderer.num_masks(); mask_state = renderer.mask_state(); } Chunk::Blend(texture, ChunkBlendMode::Shader(shader), needs_depth) => { assert!(!needs_depth, "Shader blend should not need depth buffer"); let parent_blend_buffer = target.update_blend_buffer(descriptors, texture_pool, draw_encoder); run_pixelbender_shader_impl( descriptors, shader, ShaderMode::Filter, &[ PixelBenderShaderArgument::ImageInput { index: 0, channels: 0xFF, name: "background".to_string(), texture: Some(ImageInputTexture::TextureRef( parent_blend_buffer.texture(), )), }, PixelBenderShaderArgument::ImageInput { index: 1, channels: 0xff, name: "foreground".to_string(), texture: Some(ImageInputTexture::TextureRef(texture.texture())), }, ], parent_blend_buffer.texture(), draw_encoder, target.color_attachments(), target.sample_count(), &FilterSource::for_entire_texture(texture.texture()), ) .expect("Failed to run PixelBender blend mode"); } Chunk::Blend(texture, ChunkBlendMode::Complex(blend_mode), needs_depth) => { let parent = match blend_mode { ComplexBlend::Alpha | ComplexBlend::Erase => { match nearest_layer { LayerRef::None => { // An Alpha or Erase with no Layer above it should be ignored continue; } LayerRef::Current => &target, LayerRef::Parent(layer) => layer, } } _ => &target, }; let parent_blend_buffer = parent.update_blend_buffer(descriptors, texture_pool, draw_encoder); let blend_bind_group = descriptors .device .create_bind_group(&wgpu::BindGroupDescriptor { label: create_debug_label!( "Complex blend binds {:?} {}", blend_mode, if needs_depth { "(with depth)" } else { "(Depthless)" } ) .as_deref(), layout: &descriptors.bind_layouts.blend, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView( parent_blend_buffer.view(), ), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView( texture.view(), ), }, wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler( descriptors.bitmap_samplers.get_sampler(false, false), ), }, ], }); let mut render_pass = draw_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: create_debug_label!( "Complex blend {:?} {}", blend_mode, if needs_depth { "(with depth)" } else { "(Depthless)" } ) .as_deref(), color_attachments: &[target.color_attachments()], depth_stencil_attachment: if needs_depth { target.depth_attachment(descriptors, texture_pool) } else { None }, }); render_pass.set_bind_group(0, target.globals().bind_group(), &[]); if needs_depth { match mask_state { MaskState::NoMask => {} MaskState::DrawMaskStencil => { render_pass.set_stencil_reference(num_masks - 1); } MaskState::DrawMaskedContent => { render_pass.set_stencil_reference(num_masks); } MaskState::ClearMaskStencil => { render_pass.set_stencil_reference(num_masks); } } render_pass.set_pipeline( self.pipelines.complex_blends[blend_mode].pipeline_for(mask_state), ); } else { render_pass.set_pipeline( self.pipelines.complex_blends[blend_mode].depthless_pipeline(), ); } if descriptors.limits.max_push_constant_size > 0 { render_pass.set_push_constants( wgpu::ShaderStages::VERTEX, 0, bytemuck::cast_slice(&[Transforms { world_matrix: [ [self.size.width as f32, 0.0, 0.0, 0.0], [0.0, self.size.height as f32, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ], }]), ); render_pass.set_bind_group(1, &blend_bind_group, &[]); } else { render_pass.set_bind_group( 1, target.whole_frame_bind_group(descriptors), &[0], ); render_pass.set_bind_group(2, &blend_bind_group, &[]); } render_pass.set_vertex_buffer(0, descriptors.quad.vertices_pos.slice(..)); render_pass.set_index_buffer( descriptors.quad.indices.slice(..), wgpu::IndexFormat::Uint32, ); render_pass.draw_indexed(0..6, 0, 0..1); drop(render_pass); } } } // If nothing happened, ensure it's cleared so we don't operate on garbage data target.ensure_cleared(draw_encoder); target } pub fn quality(&self) -> StageQuality { self.quality } pub fn sample_count(&self) -> u32 { self.sample_count } pub fn size(&self) -> wgpu::Extent3d { self.size } }