|
1 |
| -use bevy_asset::{Asset, Assets, Handle}; |
2 |
| - |
3 | 1 | use crate::{pipeline::RenderPipelines, Texture};
|
| 2 | +use bevy_app::{EventReader, Events}; |
| 3 | +use bevy_asset::{Asset, AssetEvent, Assets, Handle, HandleUntyped}; |
4 | 4 | pub use bevy_derive::ShaderDefs;
|
5 |
| -use bevy_ecs::{Changed, Mut, Query, Res}; |
| 5 | +use bevy_ecs::{Changed, Local, Mut, Query, QuerySet, Res}; |
| 6 | +use bevy_reflect::{Reflect, TypeUuid}; |
| 7 | +use bevy_utils::{HashSet, Uuid}; |
6 | 8 |
|
7 | 9 | /// Something that can either be "defined" or "not defined". This is used to determine if a "shader def" should be considered "defined"
|
8 | 10 | pub trait ShaderDef {
|
@@ -51,46 +53,181 @@ impl ShaderDef for Option<Handle<Texture>> {
|
51 | 53 | }
|
52 | 54 | }
|
53 | 55 |
|
| 56 | +#[derive(Debug, Clone, Hash, Eq, PartialEq, Reflect)] |
| 57 | +pub enum ShaderDefSource { |
| 58 | + Component(Uuid), |
| 59 | + Asset(HandleUntyped), |
| 60 | +} |
| 61 | + |
| 62 | +impl<T: Asset> From<&Handle<T>> for ShaderDefSource { |
| 63 | + fn from(h: &Handle<T>) -> Self { |
| 64 | + Self::Asset(h.clone_weak_untyped()) |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +impl From<Uuid> for ShaderDefSource { |
| 69 | + fn from(uuid: Uuid) -> Self { |
| 70 | + Self::Component(uuid) |
| 71 | + } |
| 72 | +} |
| 73 | + |
54 | 74 | /// Updates [RenderPipelines] with the latest [ShaderDefs]
|
55 | 75 | pub fn shader_defs_system<T>(mut query: Query<(&T, &mut RenderPipelines), Changed<T>>)
|
56 | 76 | where
|
57 |
| - T: ShaderDefs + Send + Sync + 'static, |
| 77 | + T: ShaderDefs + TypeUuid + Send + Sync + 'static, |
58 | 78 | {
|
59 |
| - query.iter_mut().for_each(update_render_pipelines) |
| 79 | + query |
| 80 | + .iter_mut() |
| 81 | + .map(|(s, p)| (s, (T::TYPE_UUID).into(), p)) |
| 82 | + .for_each(update_render_pipelines) |
60 | 83 | }
|
61 | 84 |
|
62 |
| -/// Insert defined shader defs and remove undefined ones from render pipelines. |
63 |
| -fn update_render_pipelines<T>(q: (&T, Mut<RenderPipelines>)) |
| 85 | +fn update_render_pipelines<T>(q: (&T, ShaderDefSource, Mut<RenderPipelines>)) |
64 | 86 | where
|
65 | 87 | T: ShaderDefs + Send + Sync + 'static,
|
66 | 88 | {
|
67 |
| - let (shader_defs, mut render_pipelines) = q; |
68 |
| - for (shader_def, is_defined) in shader_defs.iter_shader_defs() { |
69 |
| - for render_pipeline in render_pipelines.pipelines.iter_mut() { |
70 |
| - let shader_defs = &mut render_pipeline |
71 |
| - .specialization |
72 |
| - .shader_specialization |
73 |
| - .shader_defs; |
74 |
| - let s = shader_def.to_string(); |
75 |
| - if is_defined { |
76 |
| - shader_defs.insert(s); |
77 |
| - } else { |
78 |
| - shader_defs.remove(&s); |
79 |
| - } |
| 89 | + let (shader_defs, src, mut render_pipelines) = q; |
| 90 | + |
| 91 | + let new_defs = shader_defs |
| 92 | + .iter_shader_defs() |
| 93 | + // FIX: revert macro |
| 94 | + .filter_map(|(def, defined)| if defined { Some(def.to_string()) } else { None }) |
| 95 | + .collect::<Vec<_>>(); |
| 96 | + render_pipelines.pipelines.iter_mut().for_each(|p| { |
| 97 | + *(p.specialization |
| 98 | + .shader_specialization |
| 99 | + .shader_defs |
| 100 | + .entry(src.clone()) |
| 101 | + .or_default()) = new_defs.clone(); |
| 102 | + }); |
| 103 | +} |
| 104 | + |
| 105 | +// FIX: track entities or clean this up |
| 106 | +//#[derive(Default)] |
| 107 | +pub struct AssetShaderDefsState<T: Asset> { |
| 108 | + event_reader: EventReader<AssetEvent<T>>, |
| 109 | + //entities: HashMap<Handle<T>, HashSet<Entity>>, |
| 110 | +} |
| 111 | + |
| 112 | +impl<T: Asset> Default for AssetShaderDefsState<T> { |
| 113 | + fn default() -> Self { |
| 114 | + Self { |
| 115 | + event_reader: Default::default(), |
| 116 | + //entities: Default::default(), |
80 | 117 | }
|
81 | 118 | }
|
82 | 119 | }
|
83 | 120 |
|
84 | 121 | /// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type
|
85 |
| -pub fn asset_shader_defs_system<T: Asset>( |
| 122 | +pub fn asset_shader_defs_system<T>( |
| 123 | + mut state: Local<AssetShaderDefsState<T>>, |
86 | 124 | assets: Res<Assets<T>>,
|
87 |
| - mut query: Query<(&Handle<T>, &mut RenderPipelines), Changed<Handle<T>>>, |
| 125 | + events: Res<Events<AssetEvent<T>>>, |
| 126 | + mut queries: QuerySet<( |
| 127 | + Query<(&Handle<T>, &mut RenderPipelines)>, |
| 128 | + Query<(&Handle<T>, &mut RenderPipelines), Changed<Handle<T>>>, |
| 129 | + )>, |
88 | 130 | ) where
|
89 |
| - T: ShaderDefs + Send + Sync + 'static, |
| 131 | + T: Default + Asset + ShaderDefs + Send + Sync + 'static, |
90 | 132 | {
|
91 |
| - query |
| 133 | + let changed = state |
| 134 | + .event_reader |
| 135 | + .iter(&events) |
| 136 | + .fold(HashSet::default(), |mut set, event| { |
| 137 | + match event { |
| 138 | + AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { |
| 139 | + set.insert(handle.clone_weak()); |
| 140 | + } |
| 141 | + AssetEvent::Removed { handle } => { |
| 142 | + set.remove(&handle); |
| 143 | + } |
| 144 | + } |
| 145 | + set |
| 146 | + }); |
| 147 | + |
| 148 | + // Update for changed assets. |
| 149 | + if changed.len() > 0 { |
| 150 | + queries |
| 151 | + .q0_mut() |
| 152 | + .iter_mut() |
| 153 | + .filter(|(h, _)| changed.contains(h)) |
| 154 | + .filter_map(|(h, p)| assets.get(h).map(|a| (a, h.into(), p))) |
| 155 | + .for_each(update_render_pipelines); |
| 156 | + } |
| 157 | + |
| 158 | + // Update for changed asset handles. |
| 159 | + queries |
| 160 | + .q1_mut() |
92 | 161 | .iter_mut()
|
93 |
| - // (Handle<T>, _) -> (&T, _) |
94 |
| - .filter_map(|(h, p)| assets.get(h).map(|a| (a, p))) |
| 162 | + // Not worth? |
| 163 | + //.filter(|(h, _)| !changed.contains(h)) |
| 164 | + .filter_map(|(h, p)| assets.get(h).map(|a| (a, h.into(), p))) |
95 | 165 | .for_each(update_render_pipelines);
|
96 | 166 | }
|
| 167 | + |
| 168 | +#[cfg(test)] |
| 169 | +mod tests { |
| 170 | + use super::asset_shader_defs_system; |
| 171 | + use super::ShaderDefs; |
| 172 | + use crate::{self as bevy_render, pipeline::RenderPipeline, prelude::RenderPipelines}; |
| 173 | + use bevy_app::App; |
| 174 | + use bevy_asset::{AddAsset, AssetPlugin, AssetServer, Assets, HandleId}; |
| 175 | + use bevy_core::CorePlugin; |
| 176 | + use bevy_ecs::{Commands, ResMut}; |
| 177 | + use bevy_reflect::{ReflectPlugin, TypeUuid}; |
| 178 | + |
| 179 | + #[derive(Debug, Default, ShaderDefs, TypeUuid)] |
| 180 | + #[uuid = "3130b0bf-46a6-42f2-8556-c1a04da20b7e"] |
| 181 | + struct A { |
| 182 | + #[shader_def] |
| 183 | + d: bool, |
| 184 | + } |
| 185 | + |
| 186 | + fn shader_def_len(app: &App) -> usize { |
| 187 | + app.world |
| 188 | + .query::<&RenderPipelines>() |
| 189 | + .next() |
| 190 | + .unwrap() |
| 191 | + .pipelines[0] |
| 192 | + .specialization |
| 193 | + .shader_specialization |
| 194 | + .shader_defs |
| 195 | + .len() |
| 196 | + } |
| 197 | + |
| 198 | + #[test] |
| 199 | + fn empty_handle() { |
| 200 | + // Insert an empty asset handle, and empty render pipelines. |
| 201 | + let handle_id = HandleId::random::<A>(); |
| 202 | + let setup = move |commands: &mut Commands, asset_server: ResMut<AssetServer>| { |
| 203 | + let h = asset_server.get_handle::<A, HandleId>(handle_id); |
| 204 | + let render_pipelines = RenderPipelines::from_pipelines(vec![RenderPipeline::default()]); |
| 205 | + commands.spawn((h, render_pipelines)); |
| 206 | + }; |
| 207 | + |
| 208 | + App::build() |
| 209 | + .add_plugin(ReflectPlugin::default()) |
| 210 | + .add_plugin(CorePlugin::default()) |
| 211 | + .add_plugin(AssetPlugin::default()) |
| 212 | + .add_asset::<A>() |
| 213 | + .add_system(asset_shader_defs_system::<A>) |
| 214 | + .add_startup_system(setup) |
| 215 | + .set_runner(move |mut app: App| { |
| 216 | + app.initialize(); |
| 217 | + app.update(); |
| 218 | + assert_eq!(shader_def_len(&app), 0); |
| 219 | + { |
| 220 | + let mut asset_server = app.resources.get_mut::<Assets<A>>().unwrap(); |
| 221 | + asset_server.set(handle_id, A { d: true }); |
| 222 | + } |
| 223 | + |
| 224 | + // Asset changed events are sent post-update, so we |
| 225 | + // have to update twice to see the change. |
| 226 | + app.update(); |
| 227 | + app.update(); |
| 228 | + |
| 229 | + assert_eq!(shader_def_len(&app), 1); |
| 230 | + }) |
| 231 | + .run(); |
| 232 | + } |
| 233 | +} |
0 commit comments