Skip to main content

pipeline_constants/
pipeline_constants.rs

1//! Demonstrates pipeline-overridable constants using posterization.
2//!
3//! The same shader is compiled into three distinct pipeline variants by setting the `LEVELS`
4//! `override` constant to different values at pipeline creation time. Each quad on screen uses a
5//! different variant, producing 2, 4, and 8 discrete color steps from the same smooth gradient.
6//!
7//! This is similar to `shader_defs` but operates at the GPU compiler level: constants are
8//! substituted into the shader source before compilation, allowing the driver to optimize each
9//! variant independently. Unlike uniforms, pipeline constants cannot change at draw time.
10
11use bevy::{
12    mesh::MeshVertexBufferLayoutRef,
13    prelude::*,
14    reflect::TypePath,
15    render::render_resource::{
16        AsBindGroup, RenderPipelineDescriptor, SpecializedMeshPipelineError,
17    },
18    shader::ShaderRef,
19    sprite_render::{Material2d, Material2dKey, Material2dPipeline, Material2dPlugin},
20};
21
22const SHADER_ASSET_PATH: &str = "shaders/pipeline_constants.wesl";
23
24fn main() {
25    App::new()
26        .add_plugins((
27            DefaultPlugins,
28            Material2dPlugin::<PosterizeMaterial>::default(),
29        ))
30        .add_systems(Startup, setup)
31        .run();
32}
33
34fn setup(
35    mut commands: Commands,
36    mut meshes: ResMut<Assets<Mesh>>,
37    mut materials: ResMut<Assets<PosterizeMaterial>>,
38) {
39    commands.spawn(Camera2d);
40
41    let quad = meshes.add(Rectangle::new(200.0, 150.0));
42
43    for (i, levels) in [2u32, 4, 8].into_iter().enumerate() {
44        let x = (i as f32 - 1.0) * 220.0;
45
46        commands.spawn((
47            Mesh2d(quad.clone()),
48            MeshMaterial2d(materials.add(PosterizeMaterial { levels })),
49            Transform::from_xyz(x, 20.0, 0.0),
50        ));
51
52        commands.spawn((
53            Text2d::new(format!("{levels} levels")),
54            Transform::from_xyz(x, -65.0, 0.0),
55        ));
56    }
57}
58
59/// A material that posterizes a color gradient using a pipeline-overridable constant.
60///
61/// Each distinct `levels` value produces a separate compiled pipeline variant.
62#[derive(Asset, TypePath, AsBindGroup, Clone)]
63#[bind_group_data(PosterizeMaterialKey)]
64struct PosterizeMaterial {
65    /// Number of discrete color steps. Compiled into the shader as a pipeline constant.
66    levels: u32,
67}
68
69#[derive(Clone, PartialEq, Eq, Hash)]
70struct PosterizeMaterialKey {
71    levels: u32,
72}
73
74impl From<&PosterizeMaterial> for PosterizeMaterialKey {
75    fn from(m: &PosterizeMaterial) -> Self {
76        Self { levels: m.levels }
77    }
78}
79
80impl Material2d for PosterizeMaterial {
81    fn fragment_shader() -> ShaderRef {
82        SHADER_ASSET_PATH.into()
83    }
84
85    fn specialize(
86        _pipeline: &Material2dPipeline,
87        descriptor: &mut RenderPipelineDescriptor,
88        _layout: &MeshVertexBufferLayoutRef,
89        key: Material2dKey<Self>,
90    ) -> Result<(), SpecializedMeshPipelineError> {
91        descriptor
92            .fragment
93            .as_mut()
94            .unwrap()
95            .constants
96            .push(("LEVELS".into(), key.bind_group_data.levels as f64));
97        Ok(())
98    }
99}