diff --git a/core/src/avm2/object/context3d_object.rs b/core/src/avm2/object/context3d_object.rs index 78254e02bdab..8cda2811da0e 100644 --- a/core/src/avm2/object/context3d_object.rs +++ b/core/src/avm2/object/context3d_object.rs @@ -249,9 +249,8 @@ impl<'gc> Context3DObject<'gc> { .unwrap() .process_command( Context3DCommand::UploadShaders { - vertex_shader: program.vertex_shader_handle(), + module: program.shader_module_handle(), vertex_shader_agal, - fragment_shader: program.fragment_shader_handle(), fragment_shader_agal, }, activation.context.gc_context, @@ -263,15 +262,9 @@ impl<'gc> Context3DObject<'gc> { activation: &mut Activation<'_, 'gc>, program: Option>, ) { - let (vertex_shader, fragment_shader) = match program { - Some(program) => ( - program.vertex_shader_handle(), - program.fragment_shader_handle(), - ), - None => ( - GcCell::allocate(activation.context.gc_context, None), - GcCell::allocate(activation.context.gc_context, None), - ), + let module = match program { + Some(program) => program.shader_module_handle(), + None => GcCell::allocate(activation.context.gc_context, None), }; self.0 @@ -280,10 +273,7 @@ impl<'gc> Context3DObject<'gc> { .as_mut() .unwrap() .process_command( - Context3DCommand::SetShaders { - vertex_shader, - fragment_shader, - }, + Context3DCommand::SetShaders { module }, activation.context.gc_context, ); } diff --git a/core/src/avm2/object/program_3d_object.rs b/core/src/avm2/object/program_3d_object.rs index c0dd181676c2..a0df825e2ea3 100644 --- a/core/src/avm2/object/program_3d_object.rs +++ b/core/src/avm2/object/program_3d_object.rs @@ -33,8 +33,7 @@ impl<'gc> Program3DObject<'gc> { Program3DObjectData { base, context3d, - vertex_shader_handle: GcCell::allocate(activation.context.gc_context, None), - fragment_shader_handle: GcCell::allocate(activation.context.gc_context, None), + shader_module_handle: GcCell::allocate(activation.context.gc_context, None), }, )) .into(); @@ -45,12 +44,8 @@ impl<'gc> Program3DObject<'gc> { Ok(this) } - pub fn vertex_shader_handle(&self) -> GcCell<'gc, Option>> { - self.0.read().vertex_shader_handle - } - - pub fn fragment_shader_handle(&self) -> GcCell<'gc, Option>> { - self.0.read().fragment_shader_handle + pub fn shader_module_handle(&self) -> GcCell<'gc, Option>> { + self.0.read().shader_module_handle } pub fn context3d(&self) -> Context3DObject<'gc> { @@ -66,9 +61,7 @@ pub struct Program3DObjectData<'gc> { context3d: Context3DObject<'gc>, - vertex_shader_handle: GcCell<'gc, Option>>, - - fragment_shader_handle: GcCell<'gc, Option>>, + shader_module_handle: GcCell<'gc, Option>>, } impl<'gc> TObject<'gc> for Program3DObject<'gc> { diff --git a/render/src/backend.rs b/render/src/backend.rs index 463e1bc22382..fccf886527e8 100644 --- a/render/src/backend.rs +++ b/render/src/backend.rs @@ -429,15 +429,13 @@ pub enum Context3DCommand<'a, 'gc> { }, UploadShaders { - vertex_shader: GcCell<'gc, Option>>, + module: GcCell<'gc, Option>>, vertex_shader_agal: Vec, - fragment_shader: GcCell<'gc, Option>>, fragment_shader_agal: Vec, }, SetShaders { - vertex_shader: GcCell<'gc, Option>>, - fragment_shader: GcCell<'gc, Option>>, + module: GcCell<'gc, Option>>, }, SetProgramConstantsFromVector { program_type: ProgramType, diff --git a/render/wgpu/src/context3d/current_pipeline.rs b/render/wgpu/src/context3d/current_pipeline.rs index e5740951ce7b..ac6187a938ae 100644 --- a/render/wgpu/src/context3d/current_pipeline.rs +++ b/render/wgpu/src/context3d/current_pipeline.rs @@ -6,20 +6,21 @@ use ruffle_render::backend::{ }; use wgpu::{ - BindGroupEntry, BindingResource, BufferDescriptor, BufferUsages, FrontFace, SamplerBindingType, - TextureView, + BindGroupEntry, BindingResource, BufferDescriptor, BufferUsages, FrontFace, TextureView, }; use wgpu::{Buffer, DepthStencilState, StencilFaceState}; use wgpu::{ColorTargetState, RenderPipelineDescriptor, TextureFormat, VertexState}; use std::cell::Cell; +use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::rc::Rc; -use crate::context3d::{ShaderCompileData, VertexBufferWrapper}; +use crate::context3d::shader_pair::ShaderCompileData; +use crate::context3d::VertexBufferWrapper; use crate::descriptors::Descriptors; -use super::{ShaderModuleAgal, VertexAttributeInfo, MAX_VERTEX_ATTRIBUTES}; +use super::{ShaderPairAgal, VertexAttributeInfo, MAX_VERTEX_ATTRIBUTES}; const AGAL_NUM_VERTEX_CONSTANTS: u64 = 128; const AGAL_NUM_FRAGMENT_CONSTANTS: u64 = 28; @@ -30,16 +31,16 @@ const VERTEX_SHADER_UNIFORMS_BUFFER_SIZE: u64 = const FRAGMENT_SHADER_UNIFORMS_BUFFER_SIZE: u64 = AGAL_NUM_FRAGMENT_CONSTANTS * AGAL_FLOATS_PER_REGISTER * std::mem::size_of::() as u64; -const SAMPLER_REPEAT_LINEAR: u32 = 2; -const SAMPLER_REPEAT_NEAREST: u32 = 3; -const SAMPLER_CLAMP_LINEAR: u32 = 4; -const SAMPLER_CLAMP_NEAREST: u32 = 5; -const SAMPLER_CLAMP_U_REPEAT_V_LINEAR: u32 = 6; -const SAMPLER_CLAMP_U_REPEAT_V_NEAREST: u32 = 7; -const SAMPLER_REPEAT_U_CLAMP_V_LINEAR: u32 = 8; -const SAMPLER_REPEAT_U_CLAMP_V_NEAREST: u32 = 9; +pub(super) const SAMPLER_REPEAT_LINEAR: u32 = 2; +pub(super) const SAMPLER_REPEAT_NEAREST: u32 = 3; +pub(super) const SAMPLER_CLAMP_LINEAR: u32 = 4; +pub(super) const SAMPLER_CLAMP_NEAREST: u32 = 5; +pub(super) const SAMPLER_CLAMP_U_REPEAT_V_LINEAR: u32 = 6; +pub(super) const SAMPLER_CLAMP_U_REPEAT_V_NEAREST: u32 = 7; +pub(super) const SAMPLER_REPEAT_U_CLAMP_V_LINEAR: u32 = 8; +pub(super) const SAMPLER_REPEAT_U_CLAMP_V_NEAREST: u32 = 9; -const TEXTURE_START_BIND_INDEX: u32 = 10; +pub(super) const TEXTURE_START_BIND_INDEX: u32 = 10; // The flash Context3D API is similar to OpenGL - it has many methods // which modify the current state (`setVertexBufferAt`, `setCulling`, etc.) @@ -58,8 +59,7 @@ const TEXTURE_START_BIND_INDEX: u32 = 10; // we don't actually store the `wgpu::RenderPipeline` in `CurrentPipeline` - it's // instead stored in `WgpuContext3D`. pub struct CurrentPipeline { - vertex_shader: Option>, - fragment_shader: Option>, + shaders: Option>, culling: Context3DTriangleFace, @@ -85,15 +85,42 @@ pub struct CurrentPipeline { sampler_override: [Option; 8], } +#[derive(Clone)] pub struct BoundTextureData { /// This is used to allow us to remove a bound texture when /// it's used with `setRenderToTexture`. The actual shader binding /// uses `view` pub id: Rc, - pub view: TextureView, + pub view: Rc, pub cube: bool, } +impl Hash for BoundTextureData { + fn hash(&self, state: &mut H) { + // We can't hash 'view', but we can hash the pointer of the 'Rc', + // which is unique to the TextureView + let BoundTextureData { id, cube, view: _ } = self; + (Rc::as_ptr(id) as *const ()).hash(state); + cube.hash(state); + } +} + +impl PartialEq for BoundTextureData { + fn eq(&self, other: &Self) -> bool { + let BoundTextureData { id, cube, view: _ } = self; + let BoundTextureData { + id: other_id, + cube: other_cube, + view: _, + } = other; + std::ptr::eq( + Rc::as_ptr(id) as *const (), + Rc::as_ptr(other_id) as *const (), + ) && cube == other_cube + } +} +impl Eq for BoundTextureData {} + impl CurrentPipeline { pub fn new(descriptors: &Descriptors) -> Self { let vertex_shader_uniforms = descriptors.device.create_buffer(&BufferDescriptor { @@ -111,8 +138,7 @@ impl CurrentPipeline { }); CurrentPipeline { - vertex_shader: None, - fragment_shader: None, + shaders: None, bound_textures: std::array::from_fn(|_| None), vertex_shader_uniforms, fragment_shader_uniforms, @@ -132,14 +158,9 @@ impl CurrentPipeline { sampler_override: [None; 8], } } - pub fn set_vertex_shader(&mut self, shader: Option>) { + pub fn set_shaders(&mut self, shaders: Option>) { self.dirty.set(true); - self.vertex_shader = shader; - } - - pub fn set_fragment_shader(&mut self, shader: Option>) { - self.dirty.set(true); - self.fragment_shader = shader; + self.shaders = shaders; } pub fn update_texture_at(&mut self, index: usize, texture: Option) { @@ -209,111 +230,6 @@ impl CurrentPipeline { self.dirty.set(false); - let mut layout_entries = vec![ - // Vertex shader program constants - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // Fragment shader program constants - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // One sampler per filter/wrapping combination - see BitmapFilters - // An AGAL shader can use any of these samplers, so - // we need to bind them all. - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_REPEAT_LINEAR, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_REPEAT_NEAREST, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_CLAMP_LINEAR, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_CLAMP_NEAREST, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_CLAMP_U_REPEAT_V_LINEAR, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_CLAMP_U_REPEAT_V_NEAREST, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_REPEAT_U_CLAMP_V_LINEAR, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: SAMPLER_REPEAT_U_CLAMP_V_NEAREST, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ]; - - for (i, bound_texture) in self.bound_textures.iter().enumerate() { - if let Some(bound_texture) = bound_texture { - let dimension = if bound_texture.cube { - wgpu::TextureViewDimension::Cube - } else { - wgpu::TextureViewDimension::D2 - }; - layout_entries.push(wgpu::BindGroupLayoutEntry { - binding: TEXTURE_START_BIND_INDEX + i as u32, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: dimension, - multisampled: false, - }, - count: None, - }); - } - } - - let globals_layout_label = create_debug_label!("Globals bind group layout"); - let bind_group_layout = - descriptors - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: globals_layout_label.as_deref(), - entries: &layout_entries, - }); - let bind_group_label = create_debug_label!("Bind group"); let mut bind_group_entries = vec![ @@ -384,24 +300,6 @@ impl CurrentPipeline { } } - let bind_group = descriptors - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: bind_group_label.as_deref(), - layout: &bind_group_layout, - entries: &bind_group_entries, - }); - - let pipeline_layout_label = create_debug_label!("Pipeline layout"); - let pipeline_layout = - descriptors - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: pipeline_layout_label.as_deref(), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - let agal_attributes = vertex_attributes.clone().map(|attr| { attr.map(|attr| match attr.format { Context3DVertexBufferFormat::Float4 => naga_agal::VertexAttributeFormat::Float4, @@ -412,31 +310,32 @@ impl CurrentPipeline { }) }); - 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 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, - }, - ); + let compiled_shaders = self.shaders.as_ref().expect("Missing shaders!").compile( + descriptors, + ShaderCompileData { + vertex_attributes: agal_attributes, + sampler_overrides: self.sampler_override, + bound_textures: self.bound_textures.clone(), + }, + ); + + let pipeline_layout_label = create_debug_label!("Pipeline layout"); + let pipeline_layout = + descriptors + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: pipeline_layout_label.as_deref(), + bind_group_layouts: &[&compiled_shaders.bind_group_layout], + push_constant_ranges: &[], + }); + + let bind_group = descriptors + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: bind_group_label.as_deref(), + layout: &compiled_shaders.bind_group_layout, + entries: &bind_group_entries, + }); struct BufferData { buffer: Rc, @@ -557,12 +456,12 @@ impl CurrentPipeline { label: create_debug_label!("RenderPipeline").as_deref(), layout: Some(&pipeline_layout), vertex: VertexState { - module: &vertex_module, + module: &compiled_shaders.vertex_module, entry_point: naga_agal::SHADER_ENTRY_POINT, buffers: &wgpu_vertex_buffers, }, fragment: Some(wgpu::FragmentState { - module: &fragment_module, + module: &compiled_shaders.fragment_module, entry_point: naga_agal::SHADER_ENTRY_POINT, targets: &[Some(ColorTargetState { format: TextureFormat::Rgba8Unorm, diff --git a/render/wgpu/src/context3d/mod.rs b/render/wgpu/src/context3d/mod.rs index 4598937ec8cf..60bf10bc1768 100644 --- a/render/wgpu/src/context3d/mod.rs +++ b/render/wgpu/src/context3d/mod.rs @@ -1,14 +1,11 @@ -use lru::LruCache; -use naga_agal::{SamplerOverride, VertexAttributeFormat}; use ruffle_render::backend::{ Context3D, Context3DBlendFactor, Context3DCommand, Context3DCompareMode, - Context3DTextureFormat, Context3DVertexBufferFormat, IndexBuffer, ProgramType, ShaderModule, - VertexBuffer, + Context3DTextureFormat, Context3DVertexBufferFormat, IndexBuffer, ProgramType, VertexBuffer, }; use ruffle_render::bitmap::{BitmapFormat, BitmapHandle}; use ruffle_render::error::Error; use std::borrow::Cow; -use std::cell::{Cell, RefCell, RefMut}; +use std::cell::Cell; use swf::{Rectangle, Twips}; use wgpu::util::StagingBelt; @@ -23,14 +20,17 @@ use crate::descriptors::Descriptors; use crate::Texture; use gc_arena::{Collect, MutationContext}; -use std::num::{NonZeroU64, NonZeroUsize}; +use std::num::NonZeroU64; use std::rc::Rc; use std::sync::Arc; mod current_pipeline; +mod shader_pair; use current_pipeline::CurrentPipeline; +use self::shader_pair::ShaderPairAgal; + const COLOR_MASK: u32 = 1 << 0; const DEPTH_MASK: u32 = 1 << 1; const STENCIL_MASK: u32 = 1 << 2; @@ -351,56 +351,6 @@ pub struct VertexBufferWrapper { pub data_32_per_vertex: u8, } -#[derive(Collect)] -#[collect(require_static)] -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)] pub struct TextureWrapper { @@ -409,7 +359,6 @@ pub struct TextureWrapper { impl IndexBuffer for IndexBufferWrapper {} impl VertexBuffer for VertexBufferWrapper {} -impl ShaderModule for ShaderModuleAgal {} impl ruffle_render::backend::Texture for TextureWrapper {} // Context3D.setVertexBufferAt supports up to 8 vertex buffer attributes @@ -965,32 +914,23 @@ impl Context3D for WgpuContext3D { } Context3DCommand::UploadShaders { - vertex_shader, + module, vertex_shader_agal, - fragment_shader, 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))); + *module.write(mc) = Some(Rc::new(ShaderPairAgal::new( + vertex_shader_agal, + fragment_shader_agal, + ))); } - Context3DCommand::SetShaders { - vertex_shader, - fragment_shader, - } => { - let vertex_module = vertex_shader - .read() - .clone() - .map(|shader| shader.into_any_rc().downcast::().unwrap()); - - let fragment_module = fragment_shader + Context3DCommand::SetShaders { module } => { + let shaders = module .read() .clone() - .map(|shader| shader.into_any_rc().downcast::().unwrap()); + .map(|shader| shader.into_any_rc().downcast::().unwrap()); - self.current_pipeline.set_vertex_shader(vertex_module); - self.current_pipeline.set_fragment_shader(fragment_module); + self.current_pipeline.set_shaders(shaders) } Context3DCommand::SetProgramConstantsFromVector { program_type, @@ -1120,7 +1060,7 @@ impl Context3D for WgpuContext3D { Some(BoundTextureData { id: texture.clone(), - view: texture_wrapper.texture.create_view(&view), + view: Rc::new(texture_wrapper.texture.create_view(&view)), cube, }) } else { diff --git a/render/wgpu/src/context3d/shader_pair.rs b/render/wgpu/src/context3d/shader_pair.rs new file mode 100644 index 000000000000..2bd8d64f2b7e --- /dev/null +++ b/render/wgpu/src/context3d/shader_pair.rs @@ -0,0 +1,209 @@ +use gc_arena::Collect; +use lru::LruCache; +use naga_agal::{SamplerOverride, VertexAttributeFormat}; +use ruffle_render::backend::ShaderModule; +use std::{ + borrow::Cow, + cell::{RefCell, RefMut}, + num::NonZeroUsize, +}; +use wgpu::SamplerBindingType; + +use super::{ + current_pipeline::{ + BoundTextureData, SAMPLER_CLAMP_LINEAR, SAMPLER_CLAMP_NEAREST, + SAMPLER_CLAMP_U_REPEAT_V_LINEAR, SAMPLER_CLAMP_U_REPEAT_V_NEAREST, SAMPLER_REPEAT_LINEAR, + SAMPLER_REPEAT_NEAREST, SAMPLER_REPEAT_U_CLAMP_V_LINEAR, SAMPLER_REPEAT_U_CLAMP_V_NEAREST, + TEXTURE_START_BIND_INDEX, + }, + MAX_VERTEX_ATTRIBUTES, +}; + +use crate::descriptors::Descriptors; + +#[derive(Collect)] +#[collect(require_static)] +pub struct ShaderPairAgal { + vertex_bytecode: Vec, + fragment_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 ShaderModule for ShaderPairAgal {} + +pub struct CompiledShaderProgram { + pub vertex_module: wgpu::ShaderModule, + pub fragment_module: wgpu::ShaderModule, + pub bind_group_layout: wgpu::BindGroupLayout, +} + +impl ShaderPairAgal { + pub fn new(vertex_bytecode: Vec, fragment_bytecode: Vec) -> Self { + Self { + vertex_bytecode, + fragment_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<'_, CompiledShaderProgram> { + 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 vertex_naga_module = naga_agal::agal_to_naga( + &self.vertex_bytecode, + &data.vertex_attributes, + &data.sampler_overrides, + ) + .unwrap(); + let vertex_module = + descriptors + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("AGAL vertex shader"), + source: wgpu::ShaderSource::Naga(Cow::Owned(vertex_naga_module)), + }); + + let fragment_naga_module = naga_agal::agal_to_naga( + &self.fragment_bytecode, + &data.vertex_attributes, + &data.sampler_overrides, + ) + .unwrap(); + let fragment_module = + descriptors + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("AGAL fragment shader"), + source: wgpu::ShaderSource::Naga(Cow::Owned(fragment_naga_module)), + }); + + let mut layout_entries = vec![ + // Vertex shader program constants + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Fragment shader program constants + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // One sampler per filter/wrapping combination - see BitmapFilters + // An AGAL shader can use any of these samplers, so + // we need to bind them all. + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_REPEAT_LINEAR, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_REPEAT_NEAREST, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_CLAMP_LINEAR, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_CLAMP_NEAREST, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_CLAMP_U_REPEAT_V_LINEAR, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_CLAMP_U_REPEAT_V_NEAREST, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_REPEAT_U_CLAMP_V_LINEAR, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: SAMPLER_REPEAT_U_CLAMP_V_NEAREST, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + ]; + + for (i, bound_texture) in data.bound_textures.iter().enumerate() { + if let Some(bound_texture) = bound_texture { + let dimension = if bound_texture.cube { + wgpu::TextureViewDimension::Cube + } else { + wgpu::TextureViewDimension::D2 + }; + layout_entries.push(wgpu::BindGroupLayoutEntry { + binding: TEXTURE_START_BIND_INDEX + i as u32, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: dimension, + multisampled: false, + }, + count: None, + }); + } + } + + let globals_layout_label = create_debug_label!("Globals bind group layout"); + let bind_group_layout = + descriptors + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: globals_layout_label.as_deref(), + entries: &layout_entries, + }); + + CompiledShaderProgram { + vertex_module, + fragment_module, + bind_group_layout, + } + }) + }) + } +} + +#[derive(Hash, Eq, PartialEq, Clone)] +pub struct ShaderCompileData { + pub sampler_overrides: [Option; 8], + pub vertex_attributes: [Option; MAX_VERTEX_ATTRIBUTES], + pub bound_textures: [Option; 8], +}