Use embedded_asset to load PBR shaders (#19137)

# Objective

- Get in-engine shader hot reloading working

## Solution

- Adopt #12009
- Cut back on everything possible to land an MVP: we only hot-reload PBR
in deferred shading mode. This is to minimize the diff and avoid merge
hell. The rest shall come in followups.

## Testing

- `cargo run --example pbr --features="embedded_watcher"` and edit some
pbr shader code
This commit is contained in:
atlv 2025-05-16 01:47:34 -04:00 committed by GitHub
parent 415ffa5028
commit 139515278c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 177 additions and 188 deletions

View File

@ -8,8 +8,10 @@ use crate::io::{
memory::{Dir, MemoryAssetReader, Value},
AssetSource, AssetSourceBuilders,
};
use crate::AssetServer;
use alloc::boxed::Box;
use bevy_ecs::resource::Resource;
use bevy_app::App;
use bevy_ecs::{resource::Resource, world::World};
use std::path::{Path, PathBuf};
#[cfg(feature = "embedded_watcher")]
@ -132,6 +134,71 @@ impl EmbeddedAssetRegistry {
}
}
/// Trait for the [`load_embedded_asset!`] macro, to access [`AssetServer`]
/// from arbitrary things.
///
/// [`load_embedded_asset!`]: crate::load_embedded_asset
pub trait GetAssetServer {
fn get_asset_server(&self) -> &AssetServer;
}
impl GetAssetServer for App {
fn get_asset_server(&self) -> &AssetServer {
self.world().get_asset_server()
}
}
impl GetAssetServer for World {
fn get_asset_server(&self) -> &AssetServer {
self.resource()
}
}
impl GetAssetServer for AssetServer {
fn get_asset_server(&self) -> &AssetServer {
self
}
}
/// Load an [embedded asset](crate::embedded_asset).
///
/// This is useful if the embedded asset in question is not publicly exposed, but
/// you need to use it internally.
///
/// # Syntax
///
/// This macro takes two arguments and an optional third one:
/// 1. The asset source. It may be `AssetServer`, `World` or `App`.
/// 2. The path to the asset to embed, as a string literal.
/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`].
/// Consider explicitly typing the closure argument in case of type error.
///
/// # Usage
///
/// The advantage compared to using directly [`AssetServer::load`] is:
/// - This also accepts [`World`] and [`App`] arguments.
/// - This uses the exact same path as `embedded_asset!`, so you can keep it
/// consistent.
///
/// As a rule of thumb:
/// - If the asset in used in the same module as it is declared using `embedded_asset!`,
/// use this macro.
/// - Otherwise, use `AssetServer::load`.
#[macro_export]
macro_rules! load_embedded_asset {
(@get: $path: literal, $provider: expr) => {{
let path = $crate::embedded_path!($path);
let path = $crate::AssetPath::from_path_buf(path).with_source("embedded");
let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider);
(path, asset_server)
}};
($provider: expr, $path: literal, $settings: expr) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load_with_settings(path, $settings)
}};
($provider: expr, $path: literal) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load(path)
}};
}
/// Returns the [`Path`] for a given `embedded` asset.
/// This is used internally by [`embedded_asset`] and can be used to get a [`Path`]
/// that matches the [`AssetPath`](crate::AssetPath) used by that asset.
@ -140,7 +207,7 @@ impl EmbeddedAssetRegistry {
#[macro_export]
macro_rules! embedded_path {
($path_str: expr) => {{
embedded_path!("src", $path_str)
$crate::embedded_path!("src", $path_str)
}};
($source_path: expr, $path_str: expr) => {{
@ -192,7 +259,7 @@ pub fn _embedded_asset_path(
/// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary
/// and registering those bytes with the `embedded` [`AssetSource`].
///
/// This accepts the current [`App`](bevy_app::App) as the first parameter and a path `&str` (relative to the current file) as the second.
/// This accepts the current [`App`] as the first parameter and a path `&str` (relative to the current file) as the second.
///
/// By default this will generate an [`AssetPath`] using the following rules:
///
@ -217,14 +284,19 @@ pub fn _embedded_asset_path(
///
/// `embedded_asset!(app, "rock.wgsl")`
///
/// `rock.wgsl` can now be loaded by the [`AssetServer`](crate::AssetServer) with the following path:
/// `rock.wgsl` can now be loaded by the [`AssetServer`] as follows:
///
/// ```no_run
/// # use bevy_asset::{Asset, AssetServer};
/// # use bevy_asset::{Asset, AssetServer, load_embedded_asset};
/// # use bevy_reflect::TypePath;
/// # let asset_server: AssetServer = panic!();
/// # #[derive(Asset, TypePath)]
/// # struct Shader;
/// // If we are loading the shader in the same module we used `embedded_asset!`:
/// let shader = load_embedded_asset!(&asset_server, "rock.wgsl");
/// # let _: bevy_asset::Handle<Shader> = shader;
///
/// // If the goal is to expose the asset **to the end user**:
/// let shader = asset_server.load::<Shader>("embedded://bevy_rock/render/rock.wgsl");
/// ```
///
@ -258,11 +330,11 @@ pub fn _embedded_asset_path(
/// [`embedded_path`]: crate::embedded_path
#[macro_export]
macro_rules! embedded_asset {
($app: ident, $path: expr) => {{
($app: expr, $path: expr) => {{
$crate::embedded_asset!($app, "src", $path)
}};
($app: ident, $source_path: expr, $path: expr) => {{
($app: expr, $source_path: expr, $path: expr) => {{
let mut embedded = $app
.world_mut()
.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();

View File

@ -223,6 +223,16 @@ impl<'a> AssetPath<'a> {
Ok((source, path, label))
}
/// Creates a new [`AssetPath`] from a [`PathBuf`].
#[inline]
pub fn from_path_buf(path_buf: PathBuf) -> AssetPath<'a> {
AssetPath {
path: CowArc::Owned(path_buf.into()),
source: AssetSourceId::Default,
label: None,
}
}
/// Creates a new [`AssetPath`] from a [`Path`].
#[inline]
pub fn from_path(path: &'a Path) -> AssetPath<'a> {

View File

@ -10,7 +10,7 @@ use crate::{
ViewLightsUniformOffset,
};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, weak_handle, Handle};
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
use bevy_core_pipeline::{
core_3d::graph::{Core3d, Node3d},
deferred::{
@ -34,9 +34,6 @@ use bevy_render::{
pub struct DeferredPbrLightingPlugin;
pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle<Shader> =
weak_handle!("f4295279-8890-4748-b654-ca4d2183df1c");
pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1;
/// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass.
@ -100,12 +97,7 @@ impl Plugin for DeferredPbrLightingPlugin {
))
.add_systems(PostUpdate, insert_deferred_lighting_pass_id_component);
load_internal_asset!(
app,
DEFERRED_LIGHTING_SHADER_HANDLE,
"deferred_lighting.wgsl",
Shader::from_wgsl
);
embedded_asset!(app, "deferred_lighting.wgsl");
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
@ -237,6 +229,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
pub struct DeferredLightingLayout {
mesh_pipeline: MeshPipeline,
bind_group_layout_1: BindGroupLayout,
deferred_lighting_shader: Handle<Shader>,
}
#[derive(Component)]
@ -360,13 +353,13 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
self.bind_group_layout_1.clone(),
],
vertex: VertexState {
shader: DEFERRED_LIGHTING_SHADER_HANDLE,
shader: self.deferred_lighting_shader.clone(),
shader_defs: shader_defs.clone(),
entry_point: "vertex".into(),
buffers: Vec::new(),
},
fragment: Some(FragmentState {
shader: DEFERRED_LIGHTING_SHADER_HANDLE,
shader: self.deferred_lighting_shader.clone(),
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
@ -416,6 +409,7 @@ impl FromWorld for DeferredLightingLayout {
Self {
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
bind_group_layout_1: layout,
deferred_lighting_shader: load_embedded_asset!(world, "deferred_lighting.wgsl"),
}
}
}

View File

@ -124,7 +124,7 @@ pub mod graph {
use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, weak_handle, AssetApp, Assets, Handle};
use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetPath, Assets, Handle};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_ecs::prelude::*;
use bevy_image::Image;
@ -133,8 +133,9 @@ use bevy_render::{
camera::{sort_cameras, CameraUpdateSystems, Projection},
extract_component::ExtractComponentPlugin,
extract_resource::ExtractResourcePlugin,
load_shader_library,
render_graph::RenderGraph,
render_resource::Shader,
render_resource::{Shader, ShaderRef},
sync_component::SyncComponentPlugin,
view::VisibilitySystems,
ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSystems,
@ -142,40 +143,12 @@ use bevy_render::{
use bevy_transform::TransformSystems;
pub const PBR_TYPES_SHADER_HANDLE: Handle<Shader> =
weak_handle!("b0330585-2335-4268-9032-a6c4c2d932f6");
pub const PBR_BINDINGS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("13834c18-c7ec-4c4b-bbbd-432c3ba4cace");
pub const UTILS_HANDLE: Handle<Shader> = weak_handle!("0a32978f-2744-4608-98b6-4c3000a0638d");
pub const CLUSTERED_FORWARD_HANDLE: Handle<Shader> =
weak_handle!("f8e3b4c6-60b7-4b23-8b2e-a6b27bb4ddce");
pub const PBR_LIGHTING_HANDLE: Handle<Shader> =
weak_handle!("de0cf697-2876-49a0-aa0f-f015216f70c2");
pub const PBR_TRANSMISSION_HANDLE: Handle<Shader> =
weak_handle!("22482185-36bb-4c16-9b93-a20e6d4a2725");
pub const SHADOWS_HANDLE: Handle<Shader> = weak_handle!("ff758c5a-3927-4a15-94c3-3fbdfc362590");
pub const SHADOW_SAMPLING_HANDLE: Handle<Shader> =
weak_handle!("f6bf5843-54bc-4e39-bd9d-56bfcd77b033");
pub const PBR_FRAGMENT_HANDLE: Handle<Shader> =
weak_handle!("1bd3c10d-851b-400c-934a-db489d99cc50");
pub const PBR_SHADER_HANDLE: Handle<Shader> = weak_handle!("0eba65ed-3e5b-4752-93ed-e8097e7b0c84");
pub const PBR_PREPASS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("9afeaeab-7c45-43ce-b322-4b97799eaeb9");
pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> =
weak_handle!("815b8618-f557-4a96-91a5-a2fb7e249fb0");
pub const PBR_AMBIENT_HANDLE: Handle<Shader> = weak_handle!("4a90b95b-112a-4a10-9145-7590d6f14260");
pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle<Shader> =
weak_handle!("6cf57d9f-222a-429a-bba4-55ba9586e1d4");
pub const VIEW_TRANSFORMATIONS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("ec047703-cde3-4876-94df-fed121544abb");
pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("77b1bd3a-877c-4b2c-981b-b9c68d1b774a");
pub const PBR_DEFERRED_TYPES_HANDLE: Handle<Shader> =
weak_handle!("43060da7-a717-4240-80a8-dbddd92bd25d");
pub const PBR_DEFERRED_FUNCTIONS_HANDLE: Handle<Shader> =
weak_handle!("9dc46746-c51d-45e3-a321-6a50c3963420");
pub const RGB9E5_FUNCTIONS_HANDLE: Handle<Shader> =
weak_handle!("90c19aa3-6a11-4252-8586-d9299352e94f");
use std::path::PathBuf;
fn shader_ref(path: PathBuf) -> ShaderRef {
ShaderRef::Path(AssetPath::from_path_buf(path).with_source("embedded"))
}
const MESHLET_VISIBILITY_BUFFER_RESOLVE_SHADER_HANDLE: Handle<Shader> =
weak_handle!("69187376-3dea-4d0f-b3f5-185bde63d6a2");
@ -211,110 +184,26 @@ impl Default for PbrPlugin {
impl Plugin for PbrPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
PBR_TYPES_SHADER_HANDLE,
"render/pbr_types.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_BINDINGS_SHADER_HANDLE,
"render/pbr_bindings.wgsl",
Shader::from_wgsl
);
load_internal_asset!(app, UTILS_HANDLE, "render/utils.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
CLUSTERED_FORWARD_HANDLE,
"render/clustered_forward.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_LIGHTING_HANDLE,
"render/pbr_lighting.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_TRANSMISSION_HANDLE,
"render/pbr_transmission.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
SHADOWS_HANDLE,
"render/shadows.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_DEFERRED_TYPES_HANDLE,
"deferred/pbr_deferred_types.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_DEFERRED_FUNCTIONS_HANDLE,
"deferred/pbr_deferred_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
SHADOW_SAMPLING_HANDLE,
"render/shadow_sampling.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_FUNCTIONS_HANDLE,
"render/pbr_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
RGB9E5_FUNCTIONS_HANDLE,
"render/rgb9e5.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_AMBIENT_HANDLE,
"render/pbr_ambient.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_FRAGMENT_HANDLE,
"render/pbr_fragment.wgsl",
Shader::from_wgsl
);
load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
PBR_PREPASS_FUNCTIONS_SHADER_HANDLE,
"render/pbr_prepass_functions.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PBR_PREPASS_SHADER_HANDLE,
"render/pbr_prepass.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
PARALLAX_MAPPING_SHADER_HANDLE,
"render/parallax_mapping.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
VIEW_TRANSFORMATIONS_SHADER_HANDLE,
"render/view_transformations.wgsl",
Shader::from_wgsl
);
load_shader_library!(app, "render/pbr_types.wgsl");
load_shader_library!(app, "render/pbr_bindings.wgsl");
load_shader_library!(app, "render/utils.wgsl");
load_shader_library!(app, "render/clustered_forward.wgsl");
load_shader_library!(app, "render/pbr_lighting.wgsl");
load_shader_library!(app, "render/pbr_transmission.wgsl");
load_shader_library!(app, "render/shadows.wgsl");
load_shader_library!(app, "deferred/pbr_deferred_types.wgsl");
load_shader_library!(app, "deferred/pbr_deferred_functions.wgsl");
load_shader_library!(app, "render/shadow_sampling.wgsl");
load_shader_library!(app, "render/pbr_functions.wgsl");
load_shader_library!(app, "render/rgb9e5.wgsl");
load_shader_library!(app, "render/pbr_ambient.wgsl");
load_shader_library!(app, "render/pbr_fragment.wgsl");
load_shader_library!(app, "render/pbr.wgsl");
load_shader_library!(app, "render/pbr_prepass_functions.wgsl");
load_shader_library!(app, "render/pbr_prepass.wgsl");
load_shader_library!(app, "render/parallax_mapping.wgsl");
load_shader_library!(app, "render/view_transformations.wgsl");
// Setup dummy shaders for when MeshletPlugin is not used to prevent shader import errors.
load_internal_asset!(
app,

View File

@ -1345,7 +1345,7 @@ impl From<&StandardMaterial> for StandardMaterialKey {
impl Material for StandardMaterial {
fn fragment_shader() -> ShaderRef {
PBR_SHADER_HANDLE.into()
shader_ref(bevy_asset::embedded_path!("render/pbr.wgsl"))
}
#[inline]
@ -1381,11 +1381,11 @@ impl Material for StandardMaterial {
}
fn prepass_fragment_shader() -> ShaderRef {
PBR_PREPASS_SHADER_HANDLE.into()
shader_ref(bevy_asset::embedded_path!("render/pbr_prepass.wgsl"))
}
fn deferred_fragment_shader() -> ShaderRef {
PBR_SHADER_HANDLE.into()
shader_ref(bevy_asset::embedded_path!("render/pbr.wgsl"))
}
#[cfg(feature = "meshlet")]

View File

@ -1,6 +1,6 @@
use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot};
use allocator::MeshAllocator;
use bevy_asset::{load_internal_asset, AssetId};
use bevy_asset::{load_internal_asset, weak_handle, AssetId};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT},
deferred::{AlphaMask3dDeferred, Opaque3dDeferred},

View File

@ -72,6 +72,12 @@ pub mod prelude {
};
}
use batching::gpu_preprocessing::BatchingPlugin;
#[doc(hidden)]
pub mod _macro {
pub use bevy_asset;
}
use bevy_ecs::schedule::ScheduleBuildSettings;
use bevy_utils::prelude::default;
pub use extract_param::Extract;
@ -102,13 +108,31 @@ use crate::{
};
use alloc::sync::Arc;
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetServer, Handle};
use bevy_asset::{AssetApp, AssetServer};
use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
use bitflags::bitflags;
use core::ops::{Deref, DerefMut};
use std::sync::Mutex;
use tracing::debug;
/// Inline shader as an `embedded_asset` and load it permanently.
///
/// This works around a limitation of the shader loader not properly loading
/// dependencies of shaders.
#[macro_export]
macro_rules! load_shader_library {
($asset_server_provider: expr, $path: literal $(, $settings: expr)?) => {
$crate::_macro::bevy_asset::embedded_asset!($asset_server_provider, $path);
let handle: $crate::_macro::bevy_asset::prelude::Handle<$crate::prelude::Shader> =
$crate::_macro::bevy_asset::load_embedded_asset!(
$asset_server_provider,
$path
$(,$settings)?
);
core::mem::forget(handle);
}
}
/// Contains the default Bevy rendering backend based on wgpu.
///
/// Rendering is done in a [`SubApp`], which exchanges data with the main app
@ -289,13 +313,6 @@ struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp;
pub const MATHS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("d94d70d4-746d-49c4-bfc3-27d63f2acda0");
pub const COLOR_OPERATIONS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("33a80b2f-aaf7-4c86-b828-e7ae83b72f1a");
pub const BINDLESS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("13f1baaa-41bf-448e-929e-258f9307a522");
impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderSystems`] and creates the rendering sub-app.
fn build(&self, app: &mut App) {
@ -443,19 +460,9 @@ impl Plugin for RenderPlugin {
}
fn finish(&self, app: &mut App) {
load_internal_asset!(app, MATHS_SHADER_HANDLE, "maths.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
COLOR_OPERATIONS_SHADER_HANDLE,
"color_operations.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
BINDLESS_SHADER_HANDLE,
"bindless.wgsl",
Shader::from_wgsl
);
load_shader_library!(app, "maths.wgsl");
load_shader_library!(app, "color_operations.wgsl");
load_shader_library!(app, "bindless.wgsl");
if let Some(future_render_resources) =
app.world_mut().remove_resource::<FutureRenderResources>()
{

View File

@ -139,7 +139,7 @@ struct ShaderCache {
composer: naga_oil::compose::Composer,
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq, Debug, Hash)]
pub enum ShaderDefVal {
Bool(String, bool),
Int(String, i32),

View File

@ -324,14 +324,21 @@ pub enum ShaderLoaderError {
Parse(#[from] alloc::string::FromUtf8Error),
}
/// Settings for loading shaders.
#[derive(serde::Serialize, serde::Deserialize, Debug, Default)]
pub struct ShaderSettings {
/// The `#define` specified for this shader.
pub shader_defs: Vec<ShaderDefVal>,
}
impl AssetLoader for ShaderLoader {
type Asset = Shader;
type Settings = ();
type Settings = ShaderSettings;
type Error = ShaderLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
settings: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Shader, Self::Error> {
let ext = load_context.path().extension().unwrap().to_str().unwrap();
@ -341,9 +348,19 @@ impl AssetLoader for ShaderLoader {
let path = path.replace(std::path::MAIN_SEPARATOR, "/");
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
if ext != "wgsl" && !settings.shader_defs.is_empty() {
tracing::warn!(
"Tried to load a non-wgsl shader with shader defs, this isn't supported: \
The shader defs will be ignored."
);
}
let mut shader = match ext {
"spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()),
"wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path),
"wgsl" => Shader::from_wgsl_with_defs(
String::from_utf8(bytes)?,
path,
settings.shader_defs.clone(),
),
"vert" => Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path),
"frag" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path)