@@ -40,22 +40,23 @@ use bevy::{
4040
4141use bevy_naga_reflect:: dynamic_shader:: DynamicShader ;
4242
43+ use bevy:: shader:: Shader as ShaderAsset ;
44+
4345use crate :: error:: { ProcessingError , Result } ;
4446use crate :: material:: MaterialValue ;
4547use crate :: render:: material:: UntypedMaterial ;
4648
4749#[ derive( Asset , TypePath , Clone ) ]
4850pub struct CustomMaterial {
4951 pub shader : DynamicShader ,
50- pub vertex_shader : Option < Handle < Shader > > ,
51- pub fragment_shader : Option < Handle < Shader > > ,
52+ pub vertex_shader : Option < Handle < ShaderAsset > > ,
53+ pub fragment_shader : Option < Handle < ShaderAsset > > ,
5254}
5355
54- /// A compiled shader program that can be used to create multiple material instances.
5556#[ derive( Component ) ]
56- pub struct ShaderProgram {
57+ pub struct Shader {
5758 pub module : naga:: Module ,
58- pub shader_handle : Handle < Shader > ,
59+ pub shader_handle : Handle < ShaderAsset > ,
5960}
6061
6162#[ derive( Component , Clone ) ]
@@ -136,71 +137,30 @@ fn detect_stages(module: &naga::Module) -> (bool, bool) {
136137 ( has_vertex, has_fragment)
137138}
138139
139- /// Create a custom material from WGSL/WESL shader source (convenience: compile + create in one step).
140- pub fn create_custom (
141- In ( source) : In < String > ,
142- mut commands : Commands ,
143- mut shaders : ResMut < Assets < Shader > > ,
144- mut custom_materials : ResMut < Assets < CustomMaterial > > ,
145- ) -> Result < Entity > {
146- let ( compiled_wgsl, module) = compile_shader ( & source) ?;
147- let ( has_vertex, has_fragment) = detect_stages ( & module) ;
148-
149- let mut shader = DynamicShader :: new ( module)
150- . map_err ( |e| ProcessingError :: ShaderCompilationError ( format ! ( "{e}" ) ) ) ?;
151- shader. init ( ) ;
152-
153- let shader_handle = shaders. add ( Shader :: from_wgsl ( compiled_wgsl, "custom_material" ) ) ;
154-
155- let material = CustomMaterial {
156- shader,
157- vertex_shader : if has_vertex {
158- Some ( shader_handle. clone ( ) )
159- } else {
160- None
161- } ,
162- fragment_shader : if has_fragment {
163- Some ( shader_handle)
164- } else {
165- None
166- } ,
167- } ;
168- let handle = custom_materials. add ( material) ;
169- let entity = commands. spawn ( UntypedMaterial ( handle. untyped ( ) ) ) . id ( ) ;
170- Ok ( entity)
171- }
172-
173- /// Compile WGSL source and spawn an entity with ShaderProgram.
174140pub fn create_shader (
175141 In ( source) : In < String > ,
176142 mut commands : Commands ,
177- mut shaders : ResMut < Assets < Shader > > ,
143+ mut shaders : ResMut < Assets < ShaderAsset > > ,
178144) -> Result < Entity > {
179145 let ( compiled_wgsl, module) = compile_shader ( & source) ?;
180- let shader_handle = shaders. add ( Shader :: from_wgsl ( compiled_wgsl, "custom_material" ) ) ;
146+ let shader_handle = shaders. add ( ShaderAsset :: from_wgsl ( compiled_wgsl, "custom_material" ) ) ;
181147 Ok ( commands
182- . spawn ( ShaderProgram {
148+ . spawn ( Shader {
183149 module,
184150 shader_handle,
185151 } )
186152 . id ( ) )
187153}
188154
189- /// Destroy a shader program entity.
190155pub fn destroy_shader ( In ( entity) : In < Entity > , mut commands : Commands ) -> Result < ( ) > {
191156 commands. entity ( entity) . despawn ( ) ;
192157 Ok ( ( ) )
193158}
194159
195- /// Create a CustomMaterial from separate vertex and fragment ShaderPrograms.
196- ///
197- /// At least one of `vertex` or `fragment` must be `Some`. The `DynamicShader`
198- /// (uniform storage) is built from whichever module contains `@group(4)` bindings
199- /// (fragment preferred, falls back to vertex).
200- pub fn create_from_shaders (
160+ pub fn create_custom (
201161 In ( ( vertex_entity, fragment_entity) ) : In < ( Option < Entity > , Option < Entity > ) > ,
202162 mut commands : Commands ,
203- shader_programs : Query < & ShaderProgram > ,
163+ shader_programs : Query < & Shader > ,
204164 mut custom_materials : ResMut < Assets < CustomMaterial > > ,
205165) -> Result < Entity > {
206166 let vertex_program = vertex_entity
@@ -212,15 +172,14 @@ pub fn create_from_shaders(
212172 . transpose ( )
213173 . map_err ( |_| ProcessingError :: ShaderNotFound ) ?;
214174
215- // Build DynamicShader from the module that provides @group(4) bindings.
216- // Prefer fragment, fall back to vertex.
175+ // Prefer fragment module for reflection, fall back to vertex.
217176 let reflection_module = fragment_program
218177 . map ( |p| & p. module )
219178 . or ( vertex_program. map ( |p| & p. module ) )
220179 . ok_or ( ProcessingError :: ShaderNotFound ) ?;
221180
222181 let mut shader = DynamicShader :: new ( reflection_module. clone ( ) )
223- . map_err ( |e| ProcessingError :: ShaderCompilationError ( format ! ( "{e}" ) ) ) ?;
182+ . map_err ( |e| ProcessingError :: ShaderCompilationError ( e . to_string ( ) ) ) ?;
224183 shader. init ( ) ;
225184
226185 let material = CustomMaterial {
@@ -232,20 +191,14 @@ pub fn create_from_shaders(
232191 Ok ( commands. spawn ( UntypedMaterial ( handle. untyped ( ) ) ) . id ( ) )
233192}
234193
235- /// Set a property on a custom material by field name.
236- ///
237- /// Supports top-level parameters (e.g. the uniform var name) and automatic
238- /// search into struct fields (e.g. "color" found inside a uniform struct).
239194pub fn set_property ( material : & mut CustomMaterial , name : & str , value : & MaterialValue ) -> Result < ( ) > {
240195 let reflect_value: Box < dyn PartialReflect > = material_value_to_reflect ( value) ?;
241196
242- // Try top-level parameter first
243197 if let Some ( field) = material. shader . field_mut ( name) {
244198 field. apply ( & * reflect_value) ;
245199 return Ok ( ( ) ) ;
246200 }
247201
248- // Search struct parameters for a matching nested field
249202 let param_name = find_param_containing_field ( & material. shader , name) ;
250203 if let Some ( param_name) = param_name {
251204 if let Some ( param) = material. shader . field_mut ( & param_name) {
@@ -281,7 +234,6 @@ fn material_value_to_reflect(value: &MaterialValue) -> Result<Box<dyn PartialRef
281234 } )
282235}
283236
284- /// Find which top-level parameter (struct) contains a field with the given name.
285237fn find_param_containing_field ( shader : & DynamicShader , field_name : & str ) -> Option < String > {
286238 for i in 0 ..shader. field_len ( ) {
287239 if let Some ( field) = shader. field_at ( i) {
@@ -295,10 +247,6 @@ fn find_param_containing_field(shader: &DynamicShader, field_name: &str) -> Opti
295247 None
296248}
297249
298- // ---------------------------------------------------------------------------
299- // Plugin
300- // ---------------------------------------------------------------------------
301-
302250pub struct CustomMaterialPlugin ;
303251
304252impl Plugin for CustomMaterialPlugin {
@@ -330,16 +278,10 @@ impl Plugin for CustomMaterialPlugin {
330278 }
331279}
332280
333- // ---------------------------------------------------------------------------
334- // Render-world init
335- // ---------------------------------------------------------------------------
336-
337281fn init_custom_material_resources (
338282 mut bind_group_allocators : ResMut < MaterialBindGroupAllocators > ,
339283 render_device : Res < RenderDevice > ,
340284) {
341- // Minimal placeholder layout — actual layouts are provided per-allocation
342- // via allocate_unprepared in non-bindless mode.
343285 let bind_group_layout = BindGroupLayoutDescriptor :: new ( "custom_material_layout" , & [ ] ) ;
344286
345287 bind_group_allocators. insert (
@@ -354,10 +296,6 @@ fn init_custom_material_resources(
354296 ) ;
355297}
356298
357- // ---------------------------------------------------------------------------
358- // ErasedRenderAsset
359- // ---------------------------------------------------------------------------
360-
361299impl ErasedRenderAsset for CustomMaterial {
362300 type SourceAsset = CustomMaterial ;
363301 type ErasedAsset = PreparedMaterial ;
@@ -384,12 +322,10 @@ impl ErasedRenderAsset for CustomMaterial {
384322 ) -> std:: result:: Result < Self :: ErasedAsset , PrepareAssetError < Self :: SourceAsset > > {
385323 let reflection = source_asset. shader . reflection ( ) ;
386324
387- // Build bind group layout from shader reflection (group 3 = material bind group index)
388325 let layout_entries = reflection. bind_group_layout ( 3 ) ;
389326 let bind_group_layout =
390327 BindGroupLayoutDescriptor :: new ( "custom_material_bind_group" , & layout_entries) ;
391328
392- // Create GPU bindings from the DynamicShader's current parameter values
393329 let bindings = reflection. create_bindings ( 3 , & source_asset. shader , render_device, gpu_images) ;
394330
395331 let unprepared = UnpreparedBindGroup {
@@ -400,7 +336,6 @@ impl ErasedRenderAsset for CustomMaterial {
400336 . get_mut ( & TypeId :: of :: < CustomMaterial > ( ) )
401337 . unwrap ( ) ;
402338
403- // Register binding in RenderMaterialBindings so the draw function can find it
404339 let binding = match render_material_bindings. entry ( asset_id. into ( ) ) {
405340 Entry :: Occupied ( mut occupied_entry) => {
406341 bind_group_allocator. free ( * occupied_entry. get ( ) ) ;
@@ -413,7 +348,6 @@ impl ErasedRenderAsset for CustomMaterial {
413348 . insert ( bind_group_allocator. allocate_unprepared ( unprepared, & bind_group_layout) ) ,
414349 } ;
415350
416- // Draw function
417351 let draw_function = opaque_draw_functions
418352 . read ( )
419353 . id :: < DrawMaterial > ( ) ;
@@ -439,10 +373,6 @@ impl ErasedRenderAsset for CustomMaterial {
439373 }
440374}
441375
442- // ---------------------------------------------------------------------------
443- // Extract systems
444- // ---------------------------------------------------------------------------
445-
446376fn extract_custom_materials (
447377 mut material_instances : ResMut < RenderMaterialInstances > ,
448378 changed_query : Extract <
@@ -493,10 +423,6 @@ fn extract_custom_materials_that_need_specializations_removed(
493423 }
494424}
495425
496- // ---------------------------------------------------------------------------
497- // Specialization tracking (main world)
498- // ---------------------------------------------------------------------------
499-
500426fn check_entities_needing_specialization (
501427 needs_specialization : Query <
502428 Entity ,
0 commit comments