diff --git a/Cargo.lock b/Cargo.lock index cd0f4e200..fa226f763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2572,6 +2572,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "lyon" version = "1.0.1" @@ -3775,6 +3784,7 @@ dependencies = [ "gif", "h263-rs-yuv", "jpeg-decoder", + "lru", "lyon", "png", "ruffle_wstr", @@ -3832,6 +3842,7 @@ dependencies = [ "futures", "gc-arena", "image", + "lru", "naga", "naga-agal", "naga_oil", diff --git a/render/Cargo.toml b/render/Cargo.toml index b615570c2..16d209225 100644 --- a/render/Cargo.toml +++ b/render/Cargo.toml @@ -24,6 +24,7 @@ enum-map = "2.5.0" serde = { version = "1.0.163", features = ["derive"] } clap = { version = "4.3.1", features = ["derive"], optional = true } h263-rs-yuv = { git = "https://github.com/ruffle-rs/h263-rs", rev = "d5d78eb251c1ce1f1da57c63db14f0fdc77a4b36"} +lru = "0.10.0" [dependencies.jpeg-decoder] version = "0.3.0" diff --git a/render/naga-agal/src/lib.rs b/render/naga-agal/src/lib.rs index a9a9b058c..6c70695cd 100644 --- a/render/naga-agal/src/lib.rs +++ b/render/naga-agal/src/lib.rs @@ -10,7 +10,7 @@ pub const SHADER_ENTRY_POINT: &str = "main"; pub const MAX_VERTEX_ATTRIBUTES: usize = 8; pub const MAX_TEXTURES: usize = 8; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum VertexAttributeFormat { Float1, Float2, diff --git a/render/naga-agal/src/types.rs b/render/naga-agal/src/types.rs index 26beeb95e..033b18aa1 100644 --- a/render/naga-agal/src/types.rs +++ b/render/naga-agal/src/types.rs @@ -127,20 +127,20 @@ impl SourceField { } } -#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Filter { Nearest = 0, Linear = 1, } -#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Mipmap { Disable = 0, Nearest = 1, Linear = 2, } -#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Wrapping { Clamp = 0, Repeat = 1, @@ -181,7 +181,7 @@ pub struct SamplerField { pub reg_type: RegisterType, } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SamplerOverride { pub wrapping: Wrapping, pub filter: Filter, diff --git a/render/wgpu/Cargo.toml b/render/wgpu/Cargo.toml index d7912ba88..f556f845d 100644 --- a/render/wgpu/Cargo.toml +++ b/render/wgpu/Cargo.toml @@ -26,6 +26,7 @@ naga-agal = { path = "../naga-agal" } downcast-rs = "1.2.0" profiling = { version = "1.0", default-features = false, optional = true } naga = { version = "0.12.2", features = ["validate", "wgsl-out"] } +lru = "0.10.0" # desktop [target.'cfg(not(target_family = "wasm"))'.dependencies.futures] diff --git a/render/wgpu/src/context3d/current_pipeline.rs b/render/wgpu/src/context3d/current_pipeline.rs index 3be4bbdbb..e5740951c 100644 --- a/render/wgpu/src/context3d/current_pipeline.rs +++ b/render/wgpu/src/context3d/current_pipeline.rs @@ -12,12 +12,11 @@ use wgpu::{ use wgpu::{Buffer, DepthStencilState, StencilFaceState}; use wgpu::{ColorTargetState, RenderPipelineDescriptor, TextureFormat, VertexState}; -use std::borrow::Cow; use std::cell::Cell; use std::num::NonZeroU64; use std::rc::Rc; -use crate::context3d::VertexBufferWrapper; +use crate::context3d::{ShaderCompileData, VertexBufferWrapper}; use crate::descriptors::Descriptors; use super::{ShaderModuleAgal, VertexAttributeInfo, MAX_VERTEX_ATTRIBUTES}; @@ -413,44 +412,31 @@ impl CurrentPipeline { }) }); - let sampler_overrides = &self.sampler_override; + let vertex_module = self + .vertex_shader + .as_ref() + .expect("Missing vertex shader!") + .compile( + descriptors, + ShaderCompileData { + vertex_attributes: agal_attributes, + // Vertex shaders do not use sampler overrides + sampler_overrides: [None; 8], + }, + ); - let vertex_naga = naga_agal::agal_to_naga( - &self - .vertex_shader - .as_ref() - .expect("Missing vertex shader!") - .0, - &agal_attributes, - sampler_overrides, - ) - .expect("Vertex shader failed to compile"); - - let fragment_naga = naga_agal::agal_to_naga( - &self - .fragment_shader - .as_ref() - .expect("Missing fragment shader") - .0, - &[None; 8], - sampler_overrides, - ) - .expect("Fragment shader failed to compile"); - - let vertex_module = descriptors - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Vertex shader"), - source: wgpu::ShaderSource::Naga(Cow::Owned(vertex_naga)), - }); - - let fragment_module = - descriptors - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Fragment shader"), - source: wgpu::ShaderSource::Naga(Cow::Owned(fragment_naga)), - }); + let fragment_module = self + .fragment_shader + .as_ref() + .expect("Missing fragment shader!") + .compile( + descriptors, + ShaderCompileData { + // Fragment shaders do not use vertex attributes + vertex_attributes: [None; 8], + sampler_overrides: self.sampler_override, + }, + ); struct BufferData { buffer: Rc, diff --git a/render/wgpu/src/context3d/mod.rs b/render/wgpu/src/context3d/mod.rs index b85fb76dc..6ff263022 100644 --- a/render/wgpu/src/context3d/mod.rs +++ b/render/wgpu/src/context3d/mod.rs @@ -1,3 +1,5 @@ +use lru::LruCache; +use naga_agal::{SamplerOverride, VertexAttributeFormat}; use ruffle_render::backend::{ Context3D, Context3DBlendFactor, Context3DCommand, Context3DCompareMode, Context3DTextureFormat, Context3DVertexBufferFormat, IndexBuffer, ProgramType, ShaderModule, @@ -6,7 +8,7 @@ use ruffle_render::backend::{ use ruffle_render::bitmap::{BitmapFormat, BitmapHandle}; use ruffle_render::error::Error; use std::borrow::Cow; -use std::cell::Cell; +use std::cell::{Cell, RefCell, RefMut}; use swf::{Rectangle, Twips}; use wgpu::util::StagingBelt; @@ -21,7 +23,7 @@ use crate::descriptors::Descriptors; use crate::Texture; use gc_arena::{Collect, MutationContext}; -use std::num::NonZeroU64; +use std::num::{NonZeroU64, NonZeroUsize}; use std::rc::Rc; use std::sync::Arc; @@ -353,7 +355,53 @@ pub struct VertexBufferWrapper { #[derive(Collect)] #[collect(require_static)] -pub struct ShaderModuleAgal(Vec); +pub struct ShaderModuleAgal { + bytecode: Vec, + // Caches compiled wgpu shader modules. The cache key represents all of the data + // that we need to pass to `naga_agal::agal_to_naga` to compile a shader. + compiled: RefCell>, +} + +impl ShaderModuleAgal { + pub fn new(bytecode: Vec) -> Self { + Self { + bytecode, + // TODO - figure out a good size for this cache. + compiled: RefCell::new(LruCache::new(NonZeroUsize::new(2).unwrap())), + } + } + + pub fn compile( + &self, + descriptors: &Descriptors, + data: ShaderCompileData, + ) -> RefMut<'_, wgpu::ShaderModule> { + let compiled = self.compiled.borrow_mut(); + RefMut::map(compiled, |compiled| { + // TODO: Figure out a way to avoid the clone when we have a cache hit + compiled.get_or_insert_mut(data.clone(), || { + let naga_module = naga_agal::agal_to_naga( + &self.bytecode, + &data.vertex_attributes, + &data.sampler_overrides, + ) + .unwrap(); + descriptors + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("AGAL shader"), + source: wgpu::ShaderSource::Naga(Cow::Owned(naga_module)), + }) + }) + }) + } +} + +#[derive(Hash, Eq, PartialEq, Clone)] +pub struct ShaderCompileData { + sampler_overrides: [Option; 8], + vertex_attributes: [Option; MAX_VERTEX_ATTRIBUTES], +} #[derive(Collect)] #[collect(require_static)] @@ -933,8 +981,9 @@ impl Context3D for WgpuContext3D { fragment_shader, fragment_shader_agal, } => { - *vertex_shader.write(mc) = Some(Rc::new(ShaderModuleAgal(vertex_shader_agal))); - *fragment_shader.write(mc) = Some(Rc::new(ShaderModuleAgal(fragment_shader_agal))); + *vertex_shader.write(mc) = Some(Rc::new(ShaderModuleAgal::new(vertex_shader_agal))); + *fragment_shader.write(mc) = + Some(Rc::new(ShaderModuleAgal::new(fragment_shader_agal))); } Context3DCommand::SetShaders {