Skip to content

Pass

The Pass object is a collection of Shader objects that are rendered to a Target by the Renderer.

While the Shader represents a single Render Pipeline or a Compute Pipeline, the Pass can be used to draw multiple Shaders in sequence, for example when you have multiple objects in a scene with different materials.

The Pass represents a single RenderPass or a ComputePass in the WebGPU API.

The constructor creates a RenderPass by default. To create a ComputePass, call Pass::compute().

After creation, it will only accept a compatible Shader object. If you try to add a Compute Shader to a Render Pass or vice-versa, it won’t add the shader to its internal list and log a warning message in the console.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{ Shader, Pass, Renderer };
let renderer = Renderer::new();
let window = fragmentcolor::headless_window([100, 100]);
let target = renderer.create_target(window).await?;
let shader = Shader::default();
let mut pass = Pass::new("First Pass");
pass.add_shader(&shader);
let mut pass2 = Pass::new("Second Pass");
pass2.add_shader(&shader);
// standalone
renderer.render(&pass, &target)?;
// vector of passes rendered in order (any iterable of Pass is renderable)
renderer.render(&vec![pass, pass2], &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

The name property is optional and is used for debugging purposes.

use fragmentcolor::Pass;
let pass = Pass::new("first pass");
1 collapsed line
_ = pass;

Creates a new Pass configured for compute workloads.

Only Shader objects that compile to compute pipelines can be added.

use fragmentcolor::{Pass, Shader};
let cs = Shader::new("@compute @workgroup_size(8,8,1) fn cs_main() {}").unwrap();
let pass = Pass::from_shader("compute", &cs);

Pass::from_shader(name: &str, shader: Shader) -> Pass

Section titled “Pass::from_shader(name: &str, shader: Shader) -> Pass”

Creates a new Pass from a single Shader.

The created Pass inherits the render/compute type from the provided Shader.

use fragmentcolor::{Pass, Shader};
let shader = Shader::default();
let pass = Pass::from_shader("single", &shader);

Read the pass’s name — the string passed to Pass::new (or the label a loader gave it). Names are how you address a pass by something other than its index: pair this with Scene::find_pass to locate a pass in a scene’s graph, and it’s the label graphics debuggers (RenderDoc, Xcode GPU capture) show for the pass.

Names aren’t required to be unique. If two passes share a name, name lookup returns the first one.

1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Pass;
let shadow = Pass::new("shadow");
let label = shadow.name();
3 collapsed lines
assert_eq!(label, "shadow");
Ok(())
}

Configures this Pass to load the previous contents of the Target instead of clearing it.

This is useful when layering multiple passes where the next pass should blend with the prior results.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Pass, Shader};
let renderer = Renderer::new();
let target = renderer.create_texture_target([64, 64]).await?;
let shader = Shader::default();
let mut pass = Pass::new("blend with previous");
pass.add_shader(&shader);
pass.load_previous();
renderer.render(&pass, &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Returns a copy of the current input configuration for this Pass.

It includes the clear/load behavior and clear color.

use fragmentcolor::Pass;
let pass = Pass::new("example");
let input = pass.get_input();
1 collapsed line
_ = input; // Silence unused variable warning

Add a Shader to the Pass. Shaders run in the order they were added, sharing the pass’s targets, viewport, clear color, and load policy.

use fragmentcolor::{Pass, Shader};
let shader = Shader::default();
let pass = Pass::new("p");
pass.add_shader(&shader);

Attach a Mesh to this Pass.

  • The mesh is attached to the last shader previously added to this Pass.
  • Validates compatibility with that shader’s vertex inputs.
  • Returns ResultResult<(), ShaderError>; on error, the mesh is not attached.

If a Shader wasn’t provided earlier, FragmentColor will create a default one.

1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Pass, Shader, Mesh};
let mesh = Mesh::new();
mesh.add_vertex([0.0, 0.0]);
let shader = Shader::new(r#"
struct VOut { @builtin(position) pos: vec4<f32> };
@vertex
fn vs_main(@location(0) pos: vec2<f32>) -> VOut {
var out: VOut;
out.pos = vec4<f32>(pos, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
"#)?;
let pass = Pass::from_shader("pass", &shader);
pass.add_mesh(&mesh)?;
2 collapsed lines
Ok(())
}

Absorb any SceneObject (a Model, Camera, Light, or a user-defined node that implements the trait) into the pass. Each kind brings its own attach behaviour: a Model queues a draw with its own per-instance transform; a Camera or Light wires its uniforms into every shader the pass renders, both the ones already there and the ones added afterwards.

Camera and Light hold Arc-shared state, so subsequent mutations on the same value (camera.look_at(...), light.set_color(...)) propagate to every shader on the pass with no further add call.

Returns Result<&Pass, PassError>. Models can fail at attach time when the Mesh layout doesn’t match the Material’s shader; Cameras and Lights always succeed. Chain with ? between calls.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Camera, Light, Material, Mesh, Model, Pass, Renderer, Vertex};
let renderer = Renderer::new();
let mesh = Mesh::new();
mesh.add_vertex(
Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),
);
let model = Model::new(mesh, Material::pbr());
let camera = Camera::perspective(1.047, 1.0, 0.1, 100.0)
.look_at([0.0, 0.0, 2.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let sun = Light::directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]);
let pass = Pass::new("scene");
pass.add(&model)?;
pass.add(&camera)?;
pass.add(&sun)?;
// Updating the camera later is enough — every Model already on the pass
// picks the new view_proj up at the next render.
camera.look_at([3.0, 1.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
4 collapsed lines
let _ = renderer;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Sets the viewport region for this Pass.

The viewport restricts drawing to a rectangular area of the Target.

The viewport argument accepts anything convertible into a ScreenRegion: an [x, y, w, h] array, a (w, h) tuple, or a ScreenRegion constructed explicitly.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Pass, Shader, ScreenRegion};
let renderer = Renderer::new();
let target = renderer.create_texture_target([64, 64]).await?;
let shader = Shader::default();
let mut pass = Pass::new("clipped");
pass.add_shader(&shader);
pass.set_viewport(ScreenRegion::new((0, 0), (32, 32)));
renderer.render(&pass, &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Sets the clear color for this Pass.

When the pass is configured to clear, the render target is cleared to the given RGBA color before drawing.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Pass, Shader};
let renderer = Renderer::new();
let target = renderer.create_texture_target([64, 64]).await?;
let shader = Shader::default();
let mut pass = Pass::new("solid background");
pass.add_shader(&shader);
pass.set_clear_color([0.1, 0.2, 0.3, 1.0]);
renderer.render(&pass, &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Set the workgroup-grid size for a compute pass. The three numbers are the number of workgroups to dispatch in each dimension; total invocations are x * y * z * workgroup_size_x * workgroup_size_y * workgroup_size_z, where the per-workgroup size comes from the WGSL @workgroup_size attribute. Has no effect on render passes.

use fragmentcolor::{Pass, Shader};
let cs = Shader::new("@compute @workgroup_size(8,8,1) fn cs_main() {}").unwrap();
let pass = Pass::from_shader("compute", &cs);
pass.set_compute_dispatch(64, 64, 1);

Attach a per-pass color render target. When set, this pass renders into the provided texture instead of the final Target.

A Pass has at most one color target. Call set_target again to swap it. Use this to render intermediate results (e.g., a shadow map) that later passes can sample.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Pass, TextureFormat};
let r = Renderer::new();
let tex_target = r.create_texture_target([512, 512]).await?;
let p = Pass::new("shadow");
p.set_target(&tex_target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Attach a depth texture to this pass. Once attached, the renderer builds the pipeline with a matching depth-stencil state and depth-test is enabled: fragments behind the current contents of the depth buffer are discarded, and the pass writes to the depth buffer as it draws. That’s what you want for 3D meshes that occlude each other.

A Pass has at most one depth attachment. Call set_depth_target again to swap it.

The target must be a depth texture (Depth32Float is the canonical format) created by the same Renderer via create_depth_texture. The depth attachment’s sample count must match the color attachments’. Mixing 1× and 4× MSAA in the same pass returns RendererError::DepthSampleCountMismatch.

To opt out of depth testing, simply don’t call set_depth_target. The pass then renders without depth-state, painter’s-algorithm style (later draws win).

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Pass, Shader, Mesh};
let renderer = Renderer::new();
let target = renderer.create_texture_target([64, 64]).await?;
// One depth attachment shared across the 3D-content pass.
let depth = renderer.create_depth_texture([64, 64]).await?;
let mut mesh = Mesh::new();
mesh.add_vertex([0.0, 0.0, 0.0]);
mesh.add_vertex([1.0, 0.0, 0.0]);
mesh.add_vertex([0.0, 1.0, 0.0]);
mesh.add_vertex([1.0, 1.0, 0.0]);
let shader = Shader::from_mesh(&mesh);
let pass = Pass::from_shader("blobs", &shader);
// Depth-test on — closer fragments win, the pass writes to the depth
// buffer so subsequent draws within the same pass see the depth.
pass.set_depth_target(&depth)?;
renderer.render(&pass, &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Returns true if this Pass is a compute pass (has only compute shaders).

1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Shader, Pass};
let shader = Shader::new(r#"
@compute @workgroup_size(1)
fn cs_main() { }
"#)?;
let pass = Pass::from_shader("p", &shader);
// Call the method
let is_compute = pass.is_compute();
4 collapsed lines
_ = is_compute;
assert!(pass.is_compute());
Ok(())
}

Declare that this pass depends on one or more other renderables (Pass, Shader, Mesh). All dependencies will render before this Pass.

  • Ok(()) on success
  • Err(PassError::SelfDependency) if a pass requires itself
  • Err(PassError::DuplicateDependency(name)) if the dependency is already present
  • Err(PassError::DependencyCycle { via }) if adding the dependency would create a cycle

require establishes a dependency: the given dependencies must render before self.

This allows you to build DAG render graphs directly from passes. The graph is validated at build time and does not perform cycle checks at render time.

  • Dependencies are stored in insertion order.
  • Traversal is dependencies-first, then the current pass, with deduplication.
  • Creation order of passes does not matter.
1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Pass, Renderer};
let renderer = Renderer::new();
let target = renderer.create_texture_target([100,100]).await?;
let color = Pass::new("color");
let blurx = Pass::new("blur_x");
blurx.require(&color)?; // color before blur_x
let blury = Pass::new("blur_y");
blury.require(&blurx)?; // blur_x before blur_y
let compose = Pass::new("compose");
compose.require(&color)?;
compose.require(&blury)?; // fan-in; color and blur_y before compose
renderer.render(&compose, &target)?; // compose renders last
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }