Skip to content

Commit 2c9b795

Browse files
authored
Live reloading of shaders (#937)
* Add ShaderLoader, rebuild pipelines for modified shader assets * New example * Add shader_update_system, ShaderError, remove specialization assets * Don't panic on shader compilation failure
1 parent a3bca7e commit 2c9b795

File tree

11 files changed

+329
-52
lines changed

11 files changed

+329
-52
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ path = "examples/reflection/trait_reflection.rs"
274274
name = "scene"
275275
path = "examples/scene/scene.rs"
276276

277+
[[example]]
278+
name = "hot_shader_reloading"
279+
path = "examples/shader/hot_shader_reloading.rs"
280+
277281
[[example]]
278282
name = "mesh_custom_attribute"
279283
path = "examples/shader/mesh_custom_attribute.rs"

assets/shaders/hot.frag

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#version 450
2+
3+
layout(location = 0) out vec4 o_Target;
4+
5+
layout(set = 2, binding = 0) uniform MyMaterial_color {
6+
vec4 color;
7+
};
8+
9+
void main() {
10+
o_Target = color * 0.5;
11+
}

assets/shaders/hot.vert

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#version 450
2+
3+
layout(location = 0) in vec3 Vertex_Position;
4+
5+
layout(set = 0, binding = 0) uniform Camera {
6+
mat4 ViewProj;
7+
};
8+
9+
layout(set = 1, binding = 0) uniform Transform {
10+
mat4 Model;
11+
};
12+
13+
void main() {
14+
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
15+
}

crates/bevy_render/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use render_graph::{
4444
RenderGraph,
4545
};
4646
use renderer::{AssetRenderResourceBindings, RenderResourceBindings};
47+
use shader::ShaderLoader;
4748
#[cfg(feature = "hdr")]
4849
use texture::HdrTextureLoader;
4950
#[cfg(feature = "png")]
@@ -87,6 +88,8 @@ impl Plugin for RenderPlugin {
8788
app.init_asset_loader::<HdrTextureLoader>();
8889
}
8990

91+
app.init_asset_loader::<ShaderLoader>();
92+
9093
if app.resources().get::<ClearColor>().is_none() {
9194
app.resources_mut().insert(ClearColor::default());
9295
}
@@ -134,6 +137,7 @@ impl Plugin for RenderPlugin {
134137
camera::visible_entities_system,
135138
)
136139
// TODO: turn these "resource systems" into graph nodes and remove the RENDER_RESOURCE stage
140+
.add_system_to_stage(stage::RENDER_RESOURCE, shader::shader_update_system)
137141
.add_system_to_stage(stage::RENDER_RESOURCE, mesh::mesh_resource_provider_system)
138142
.add_system_to_stage(stage::RENDER_RESOURCE, Texture::texture_resource_system)
139143
.add_system_to_stage(

crates/bevy_render/src/pipeline/pipeline_compiler.rs

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::{state_descriptors::PrimitiveTopology, IndexFormat, PipelineDescripto
22
use crate::{
33
pipeline::{BindType, InputStepMode, VertexBufferDescriptor},
44
renderer::RenderResourceContext,
5-
shader::{Shader, ShaderSource},
5+
shader::{Shader, ShaderError, ShaderSource},
66
};
77
use bevy_asset::{Assets, Handle};
88
use bevy_reflect::Reflect;
@@ -60,6 +60,7 @@ struct SpecializedPipeline {
6060
#[derive(Debug, Default)]
6161
pub struct PipelineCompiler {
6262
specialized_shaders: HashMap<Handle<Shader>, Vec<SpecializedShader>>,
63+
specialized_shader_pipelines: HashMap<Handle<Shader>, Vec<Handle<PipelineDescriptor>>>,
6364
specialized_pipelines: HashMap<Handle<PipelineDescriptor>, Vec<SpecializedPipeline>>,
6465
}
6566

@@ -70,7 +71,7 @@ impl PipelineCompiler {
7071
shaders: &mut Assets<Shader>,
7172
shader_handle: &Handle<Shader>,
7273
shader_specialization: &ShaderSpecialization,
73-
) -> Handle<Shader> {
74+
) -> Result<Handle<Shader>, ShaderError> {
7475
let specialized_shaders = self
7576
.specialized_shaders
7677
.entry(shader_handle.clone_weak())
@@ -80,7 +81,7 @@ impl PipelineCompiler {
8081

8182
// don't produce new shader if the input source is already spirv
8283
if let ShaderSource::Spirv(_) = shader.source {
83-
return shader_handle.clone_weak();
84+
return Ok(shader_handle.clone_weak());
8485
}
8586

8687
if let Some(specialized_shader) =
@@ -91,7 +92,7 @@ impl PipelineCompiler {
9192
})
9293
{
9394
// if shader has already been compiled with current configuration, use existing shader
94-
specialized_shader.shader.clone_weak()
95+
Ok(specialized_shader.shader.clone_weak())
9596
} else {
9697
// if no shader exists with the current configuration, create new shader and compile
9798
let shader_def_vec = shader_specialization
@@ -100,14 +101,14 @@ impl PipelineCompiler {
100101
.cloned()
101102
.collect::<Vec<String>>();
102103
let compiled_shader =
103-
render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec));
104+
render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec))?;
104105
let specialized_handle = shaders.add(compiled_shader);
105106
let weak_specialized_handle = specialized_handle.clone_weak();
106107
specialized_shaders.push(SpecializedShader {
107108
shader: specialized_handle,
108109
specialization: shader_specialization.clone(),
109110
});
110-
weak_specialized_handle
111+
Ok(weak_specialized_handle)
111112
}
112113
}
113114

@@ -138,23 +139,31 @@ impl PipelineCompiler {
138139
) -> Handle<PipelineDescriptor> {
139140
let source_descriptor = pipelines.get(source_pipeline).unwrap();
140141
let mut specialized_descriptor = source_descriptor.clone();
141-
specialized_descriptor.shader_stages.vertex = self.compile_shader(
142-
render_resource_context,
143-
shaders,
144-
&specialized_descriptor.shader_stages.vertex,
145-
&pipeline_specialization.shader_specialization,
146-
);
142+
let specialized_vertex_shader = self
143+
.compile_shader(
144+
render_resource_context,
145+
shaders,
146+
&specialized_descriptor.shader_stages.vertex,
147+
&pipeline_specialization.shader_specialization,
148+
)
149+
.unwrap();
150+
specialized_descriptor.shader_stages.vertex = specialized_vertex_shader.clone_weak();
151+
let mut specialized_fragment_shader = None;
147152
specialized_descriptor.shader_stages.fragment = specialized_descriptor
148153
.shader_stages
149154
.fragment
150155
.as_ref()
151156
.map(|fragment| {
152-
self.compile_shader(
153-
render_resource_context,
154-
shaders,
155-
fragment,
156-
&pipeline_specialization.shader_specialization,
157-
)
157+
let shader = self
158+
.compile_shader(
159+
render_resource_context,
160+
shaders,
161+
fragment,
162+
&pipeline_specialization.shader_specialization,
163+
)
164+
.unwrap();
165+
specialized_fragment_shader = Some(shader.clone_weak());
166+
shader
158167
});
159168

160169
let mut layout = render_resource_context.reflect_pipeline_layout(
@@ -244,6 +253,18 @@ impl PipelineCompiler {
244253
&shaders,
245254
);
246255

256+
// track specialized shader pipelines
257+
self.specialized_shader_pipelines
258+
.entry(specialized_vertex_shader)
259+
.or_insert_with(Default::default)
260+
.push(source_pipeline.clone_weak());
261+
if let Some(specialized_fragment_shader) = specialized_fragment_shader {
262+
self.specialized_shader_pipelines
263+
.entry(specialized_fragment_shader)
264+
.or_insert_with(Default::default)
265+
.push(source_pipeline.clone_weak());
266+
}
267+
247268
let specialized_pipelines = self
248269
.specialized_pipelines
249270
.entry(source_pipeline.clone_weak())
@@ -282,4 +303,56 @@ impl PipelineCompiler {
282303
})
283304
.flatten()
284305
}
306+
307+
/// Update specialized shaders and remove any related specialized
308+
/// pipelines and assets.
309+
pub fn update_shader(
310+
&mut self,
311+
shader: &Handle<Shader>,
312+
pipelines: &mut Assets<PipelineDescriptor>,
313+
shaders: &mut Assets<Shader>,
314+
render_resource_context: &dyn RenderResourceContext,
315+
) -> Result<(), ShaderError> {
316+
if let Some(specialized_shaders) = self.specialized_shaders.get_mut(shader) {
317+
for specialized_shader in specialized_shaders {
318+
// Recompile specialized shader. If it fails, we bail immediately.
319+
let shader_def_vec = specialized_shader
320+
.specialization
321+
.shader_defs
322+
.iter()
323+
.cloned()
324+
.collect::<Vec<String>>();
325+
let new_handle =
326+
shaders.add(render_resource_context.get_specialized_shader(
327+
shaders.get(shader).unwrap(),
328+
Some(&shader_def_vec),
329+
)?);
330+
331+
// Replace handle and remove old from assets.
332+
let old_handle = std::mem::replace(&mut specialized_shader.shader, new_handle);
333+
shaders.remove(&old_handle);
334+
335+
// Find source pipelines that use the old specialized
336+
// shader, and remove from tracking.
337+
if let Some(source_pipelines) =
338+
self.specialized_shader_pipelines.remove(&old_handle)
339+
{
340+
// Remove all specialized pipelines from tracking
341+
// and asset storage. They will be rebuilt on next
342+
// draw.
343+
for source_pipeline in source_pipelines {
344+
if let Some(specialized_pipelines) =
345+
self.specialized_pipelines.remove(&source_pipeline)
346+
{
347+
for p in specialized_pipelines {
348+
pipelines.remove(p.pipeline);
349+
}
350+
}
351+
}
352+
}
353+
}
354+
}
355+
356+
Ok(())
357+
}
285358
}

crates/bevy_render/src/renderer/headless_render_resource_context.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::RenderResourceContext;
22
use crate::{
33
pipeline::{BindGroupDescriptorId, PipelineDescriptor},
44
renderer::{BindGroup, BufferId, BufferInfo, RenderResourceId, SamplerId, TextureId},
5-
shader::Shader,
5+
shader::{Shader, ShaderError},
66
texture::{SamplerDescriptor, TextureDescriptor},
77
};
88
use bevy_asset::{Assets, Handle, HandleUntyped};
@@ -149,8 +149,12 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
149149
size
150150
}
151151

152-
fn get_specialized_shader(&self, shader: &Shader, _macros: Option<&[String]>) -> Shader {
153-
shader.clone()
152+
fn get_specialized_shader(
153+
&self,
154+
shader: &Shader,
155+
_macros: Option<&[String]>,
156+
) -> Result<Shader, ShaderError> {
157+
Ok(shader.clone())
154158
}
155159

156160
fn remove_stale_bind_groups(&self) {}

crates/bevy_render/src/renderer/render_resource_context.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
pipeline::{BindGroupDescriptorId, PipelineDescriptor, PipelineLayout},
33
renderer::{BindGroup, BufferId, BufferInfo, RenderResourceId, SamplerId, TextureId},
4-
shader::{Shader, ShaderLayout, ShaderStages},
4+
shader::{Shader, ShaderError, ShaderLayout, ShaderStages},
55
texture::{SamplerDescriptor, TextureDescriptor},
66
};
77
use bevy_asset::{Asset, Assets, Handle, HandleUntyped};
@@ -29,7 +29,11 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
2929
fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId;
3030
fn create_shader_module(&self, shader_handle: &Handle<Shader>, shaders: &Assets<Shader>);
3131
fn create_shader_module_from_source(&self, shader_handle: &Handle<Shader>, shader: &Shader);
32-
fn get_specialized_shader(&self, shader: &Shader, macros: Option<&[String]>) -> Shader;
32+
fn get_specialized_shader(
33+
&self,
34+
shader: &Shader,
35+
macros: Option<&[String]>,
36+
) -> Result<Shader, ShaderError>;
3337
fn remove_buffer(&self, buffer: BufferId);
3438
fn remove_texture(&self, texture: TextureId);
3539
fn remove_sampler(&self, sampler: SamplerId);

0 commit comments

Comments
 (0)