Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 11 additions & 22 deletions crates/bevy_render/src/render_resource/pipeline_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use bevy_ecs::{
};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_shader::{
CachedPipelineId, PipelineCacheError, Shader, ShaderCache, ShaderCacheSource, ShaderDefVal,
CachedPipelineId, Shader, ShaderCache, ShaderCacheError, ShaderCacheSource, ShaderDefVal,
ValidateShader,
};
use bevy_tasks::Task;
Expand Down Expand Up @@ -74,23 +74,16 @@ pub struct CachedPipeline {
}

/// State of a cached pipeline inserted into a [`PipelineCache`].
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::large_enum_variant,
reason = "See https://github.com/bevyengine/bevy/issues/19220"
)
)]
#[derive(Debug)]
pub enum CachedPipelineState {
/// The pipeline GPU object is queued for creation.
Queued,
/// The pipeline GPU object is being created.
Creating(Task<Result<Pipeline, PipelineCacheError>>),
Creating(Task<Result<Pipeline, ShaderCacheError>>),
/// The pipeline GPU object was created successfully and is available (allocated on the GPU).
Ok(Pipeline),
/// An error occurred while trying to create the pipeline GPU object.
Err(PipelineCacheError),
Err(ShaderCacheError),
}

impl CachedPipelineState {
Expand Down Expand Up @@ -151,15 +144,11 @@ impl LayoutCache {
}
}

#[expect(
clippy::result_large_err,
reason = "See https://github.com/bevyengine/bevy/issues/19220"
)]
fn load_module(
render_device: &RenderDevice,
shader_source: ShaderCacheSource,
validate_shader: &ValidateShader,
) -> Result<WgpuWrapper<ShaderModule>, PipelineCacheError> {
) -> Result<WgpuWrapper<ShaderModule>, ShaderCacheError> {
let shader_source = match shader_source {
#[cfg(feature = "shader_format_spirv")]
ShaderCacheSource::SpirV(data) => wgpu::util::make_spirv(data),
Expand Down Expand Up @@ -201,7 +190,7 @@ fn load_module(
if let Some(Some(wgpu::Error::Validation { description, .. })) =
bevy_tasks::futures::now_or_never(error)
{
return Err(PipelineCacheError::CreateShaderModule(description));
return Err(ShaderCacheError::CreateShaderModule(description));
}

Ok(shader_module)
Expand Down Expand Up @@ -739,13 +728,13 @@ impl PipelineCache {

CachedPipelineState::Err(err) => match err {
// Retry
PipelineCacheError::ShaderNotLoaded(_)
| PipelineCacheError::ShaderImportNotYetAvailable => {
ShaderCacheError::ShaderNotLoaded(_)
| ShaderCacheError::ShaderImportNotYetAvailable => {
cached_pipeline.state = CachedPipelineState::Queued;
}

// Shader could not be processed ... retrying won't help
PipelineCacheError::ProcessShaderError(err) => {
ShaderCacheError::ProcessShaderError(err) => {
let error_detail =
err.emit_to_string(&self.shader_cache.lock().unwrap().composer);
if std::env::var("VERBOSE_SHADER_ERROR")
Expand All @@ -756,7 +745,7 @@ impl PipelineCache {
error!("failed to process shader error:\n{}", error_detail);
return;
}
PipelineCacheError::CreateShaderModule(description) => {
ShaderCacheError::CreateShaderModule(description) => {
error!("failed to create shader module: {}", description);
return;
}
Expand Down Expand Up @@ -851,7 +840,7 @@ fn pipeline_error_context(cached_pipeline: &CachedPipeline) -> String {
feature = "multi_threaded"
))]
fn create_pipeline_task(
task: impl Future<Output = Result<Pipeline, PipelineCacheError>> + Send + 'static,
task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
sync: bool,
) -> CachedPipelineState {
if !sync {
Expand All @@ -870,7 +859,7 @@ fn create_pipeline_task(
not(feature = "multi_threaded")
))]
fn create_pipeline_task(
task: impl Future<Output = Result<Pipeline, PipelineCacheError>> + Send + 'static,
task: impl Future<Output = Result<Pipeline, ShaderCacheError>> + Send + 'static,
_sync: bool,
) -> CachedPipelineState {
match bevy_tasks::block_on(task) {
Expand Down
46 changes: 17 additions & 29 deletions crates/bevy_shader/src/shader_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub struct ShaderCache<ShaderModule, RenderDevice> {
&RenderDevice,
ShaderCacheSource,
&ValidateShader,
) -> Result<ShaderModule, PipelineCacheError>,
) -> Result<ShaderModule, ShaderCacheError>,
#[cfg(feature = "shader_format_wesl")]
module_path_to_asset_id: HashMap<wesl::syntax::ModulePath, AssetId<Shader>>,
shaders: HashMap<AssetId<Shader>, Shader>,
Expand Down Expand Up @@ -109,7 +109,7 @@ impl<ShaderModule, RenderDevice> ShaderCache<ShaderModule, RenderDevice> {
&RenderDevice,
ShaderCacheSource,
&ValidateShader,
) -> Result<ShaderModule, PipelineCacheError>,
) -> Result<ShaderModule, ShaderCacheError>,
) -> Self {
let capabilities = get_capabilities(features, downlevel);
#[cfg(debug_assertions)]
Expand All @@ -131,16 +131,12 @@ impl<ShaderModule, RenderDevice> ShaderCache<ShaderModule, RenderDevice> {
}
}

#[expect(
clippy::result_large_err,
reason = "See https://github.com/bevyengine/bevy/issues/19220"
)]
fn add_import_to_composer(
composer: &mut naga_oil::compose::Composer,
import_path_shaders: &HashMap<ShaderImport, AssetId<Shader>>,
shaders: &HashMap<AssetId<Shader>, Shader>,
import: &ShaderImport,
) -> Result<(), PipelineCacheError> {
) -> Result<(), ShaderCacheError> {
// Early out if we've already imported this module
if composer.contains_module(&import.module_name()) {
return Ok(());
Expand All @@ -150,34 +146,32 @@ impl<ShaderModule, RenderDevice> ShaderCache<ShaderModule, RenderDevice> {
let shader = import_path_shaders
.get(import)
.and_then(|handle| shaders.get(handle))
.ok_or(PipelineCacheError::ShaderImportNotYetAvailable)?;
.ok_or(ShaderCacheError::ShaderImportNotYetAvailable)?;

// Recurse down to ensure all import dependencies are met
for import in &shader.imports {
Self::add_import_to_composer(composer, import_path_shaders, shaders, import)?;
}

composer.add_composable_module(shader.into())?;
composer
.add_composable_module(shader.into())
.map_err(Box::new)?;
// if we fail to add a module the composer will tell us what is missing

Ok(())
}

#[expect(
clippy::result_large_err,
reason = "See https://github.com/bevyengine/bevy/issues/19220"
)]
pub fn get(
&mut self,
render_device: &RenderDevice,
pipeline: CachedPipelineId,
id: AssetId<Shader>,
shader_defs: &[ShaderDefVal],
) -> Result<Arc<ShaderModule>, PipelineCacheError> {
) -> Result<Arc<ShaderModule>, ShaderCacheError> {
let shader = self
.shaders
.get(&id)
.ok_or(PipelineCacheError::ShaderNotLoaded(id))?;
.ok_or(ShaderCacheError::ShaderNotLoaded(id))?;

let data = self.data.entry(id).or_default();
let n_asset_imports = shader
Expand All @@ -190,7 +184,7 @@ impl<ShaderModule, RenderDevice> ShaderCache<ShaderModule, RenderDevice> {
.filter(|import| matches!(import, ShaderImport::AssetPath(_)))
.count();
if n_asset_imports != n_resolved_asset_imports {
return Err(PipelineCacheError::ShaderImportNotYetAvailable);
return Err(ShaderCacheError::ShaderImportNotYetAvailable);
}

data.pipelines.insert(pipeline);
Expand Down Expand Up @@ -268,12 +262,13 @@ impl<ShaderModule, RenderDevice> ShaderCache<ShaderModule, RenderDevice> {
})
.collect::<std::collections::HashMap<_, _>>();

let naga = self.composer.make_naga_module(
naga_oil::compose::NagaModuleDescriptor {
let naga = self
.composer
.make_naga_module(naga_oil::compose::NagaModuleDescriptor {
shader_defs,
..shader.into()
},
)?;
})
.map_err(Box::new)?;

#[cfg(not(feature = "decoupled_naga"))]
{
Expand Down Expand Up @@ -418,21 +413,14 @@ impl<'a> wesl::Resolver for ShaderResolver<'a> {
}

/// Type of error returned by a `PipelineCache` when the creation of a GPU pipeline object failed.
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::large_enum_variant,
reason = "See https://github.com/bevyengine/bevy/issues/19220"
)
)]
#[derive(Error, Debug)]
pub enum PipelineCacheError {
pub enum ShaderCacheError {
#[error(
"Pipeline could not be compiled because the following shader could not be loaded: {0:?}"
)]
ShaderNotLoaded(AssetId<Shader>),
#[error(transparent)]
ProcessShaderError(#[from] naga_oil::compose::ComposerError),
ProcessShaderError(#[from] Box<naga_oil::compose::ComposerError>),
#[error("Shader import not yet available.")]
ShaderImportNotYetAvailable,
#[error("Could not create shader module: {0}")]
Expand Down
4 changes: 2 additions & 2 deletions examples/shader/compute_shader_game_of_life.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use bevy::{
texture::GpuImage,
Render, RenderApp, RenderStartup, RenderSystems,
},
shader::PipelineCacheError,
shader::ShaderCacheError,
};
use std::borrow::Cow;

Expand Down Expand Up @@ -243,7 +243,7 @@ impl render_graph::Node for GameOfLifeNode {
self.state = GameOfLifeState::Init;
}
// If the shader hasn't loaded yet, just wait.
CachedPipelineState::Err(PipelineCacheError::ShaderNotLoaded(_)) => {}
CachedPipelineState::Err(ShaderCacheError::ShaderNotLoaded(_)) => {}
CachedPipelineState::Err(err) => {
panic!("Initializing assets/{SHADER_ASSET_PATH}:\n{err}")
}
Expand Down
6 changes: 6 additions & 0 deletions release-content/migration-guides/shader_cache_error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: "`PipelineCacheError` renamed to `ShaderCacheError`"
pull_requests: [22362]
---

`PipelineCacheError` has been renamed to `ShaderCacheError` and the `ProcessShaderError` variant has been `Box`ed.