wgpu: Cache compiled naga-agal shader module

We use an `lru::LruCache` inside `ShaderModuleAgal`. This automatically
gives us the proper garbage-collection behavior (when the Flash
Program3D instance is garbage collected, we'll drop the
`ShaderModuleAgal` and the cache).

The cache is keyed on the data needed to compile the shader (vertex
attributes and sampler overrides). This lets us avoid shader
recompilations when a Stage3D program repeatedly uses the same
Program3D with different sampler overrides / vertex attribute formats.
This commit is contained in:
Aaron Hill 2023-05-25 16:00:38 -05:00
parent 455b96710e
commit d44c9cceb1
7 changed files with 97 additions and 49 deletions

11
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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]

View File

@ -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<VertexBufferWrapper>,

View File

@ -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<u8>);
pub struct ShaderModuleAgal {
bytecode: Vec<u8>,
// 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<LruCache<ShaderCompileData, wgpu::ShaderModule>>,
}
impl ShaderModuleAgal {
pub fn new(bytecode: Vec<u8>) -> 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<SamplerOverride>; 8],
vertex_attributes: [Option<VertexAttributeFormat>; 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 {