Skip to content

Commit 08fa65b

Browse files
committed
[update] blending to be opt-in and default the pipeline color target to opaque
1 parent a4b5f9e commit 08fa65b

File tree

5 files changed

+124
-3
lines changed

5 files changed

+124
-3
lines changed

crates/lambda-rs-platform/src/wgpu/pipeline.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,52 @@ impl CullingMode {
7575
}
7676
}
7777

78+
/// Blend mode for the (single) color target.
79+
///
80+
/// Notes
81+
/// - Most pipelines are opaque and should use `BlendMode::None` for best
82+
/// performance, especially on tile-based GPUs.
83+
/// - This is currently a single blend state for a single color attachment.
84+
/// Per-attachment blending for MRT is future work.
85+
#[derive(Clone, Copy, Debug)]
86+
pub enum BlendMode {
87+
/// No blending; replace destination (default).
88+
None,
89+
/// Standard alpha blending (`src_alpha`, `one_minus_src_alpha`).
90+
AlphaBlending,
91+
/// Premultiplied alpha blending.
92+
PremultipliedAlpha,
93+
/// Additive blending (`one`, `one`).
94+
Additive,
95+
/// Custom blend state.
96+
Custom(wgpu::BlendState),
97+
}
98+
99+
impl BlendMode {
100+
fn to_wgpu(self) -> Option<wgpu::BlendState> {
101+
return match self {
102+
BlendMode::None => None,
103+
BlendMode::AlphaBlending => Some(wgpu::BlendState::ALPHA_BLENDING),
104+
BlendMode::PremultipliedAlpha => {
105+
Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING)
106+
}
107+
BlendMode::Additive => Some(wgpu::BlendState {
108+
color: wgpu::BlendComponent {
109+
src_factor: wgpu::BlendFactor::One,
110+
dst_factor: wgpu::BlendFactor::One,
111+
operation: wgpu::BlendOperation::Add,
112+
},
113+
alpha: wgpu::BlendComponent {
114+
src_factor: wgpu::BlendFactor::One,
115+
dst_factor: wgpu::BlendFactor::One,
116+
operation: wgpu::BlendOperation::Add,
117+
},
118+
}),
119+
BlendMode::Custom(state) => Some(state),
120+
};
121+
}
122+
}
123+
78124
/// Description of a single vertex attribute used by a pipeline.
79125
#[derive(Clone, Copy, Debug)]
80126
pub struct VertexAttributeDesc {
@@ -488,6 +534,7 @@ pub struct RenderPipelineBuilder<'a> {
488534
vertex_buffers: Vec<VertexBufferLayoutDesc>,
489535
cull_mode: CullingMode,
490536
color_target_format: Option<wgpu::TextureFormat>,
537+
blend_mode: BlendMode,
491538
depth_stencil: Option<wgpu::DepthStencilState>,
492539
sample_count: u32,
493540
}
@@ -507,6 +554,7 @@ impl<'a> RenderPipelineBuilder<'a> {
507554
vertex_buffers: Vec::new(),
508555
cull_mode: CullingMode::Back,
509556
color_target_format: None,
557+
blend_mode: BlendMode::None,
510558
depth_stencil: None,
511559
sample_count: 1,
512560
};
@@ -565,6 +613,12 @@ impl<'a> RenderPipelineBuilder<'a> {
565613
return self;
566614
}
567615

616+
/// Set the blend mode for the color target. Defaults to `BlendMode::None`.
617+
pub fn with_blend(mut self, mode: BlendMode) -> Self {
618+
self.blend_mode = mode;
619+
return self;
620+
}
621+
568622
/// Enable depth testing/writes using the provided depth format and default compare/write settings.
569623
///
570624
/// Defaults: compare Less, depth writes enabled, no stencil.
@@ -672,7 +726,7 @@ impl<'a> RenderPipelineBuilder<'a> {
672726
match &self.color_target_format {
673727
Some(fmt) => vec![Some(wgpu::ColorTargetState {
674728
format: *fmt,
675-
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
729+
blend: self.blend_mode.to_wgpu(),
676730
write_mask: wgpu::ColorWrites::ALL,
677731
})],
678732
None => Vec::new(),
@@ -755,4 +809,23 @@ mod tests {
755809
VertexStepMode::Vertex
756810
));
757811
}
812+
813+
#[test]
814+
fn render_pipeline_builder_defaults_to_no_blending() {
815+
let builder = RenderPipelineBuilder::new();
816+
assert!(matches!(builder.blend_mode, BlendMode::None));
817+
}
818+
819+
#[test]
820+
fn blend_mode_maps_to_wgpu_blend_state() {
821+
assert_eq!(
822+
BlendMode::AlphaBlending.to_wgpu(),
823+
Some(wgpu::BlendState::ALPHA_BLENDING)
824+
);
825+
assert_eq!(
826+
BlendMode::PremultipliedAlpha.to_wgpu(),
827+
Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING)
828+
);
829+
assert_eq!(BlendMode::None.to_wgpu(), None);
830+
}
758831
}

crates/lambda-rs/src/render/pipeline.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,38 @@ impl RenderPipeline {
119119
/// though wgpu v28 immediates no longer use stage-scoped updates.
120120
pub use platform_pipeline::PipelineStage;
121121

122+
/// Blend mode for the pipeline's (single) color target.
123+
///
124+
/// Notes
125+
/// - Defaults to `BlendMode::None` (opaque/replace). Opt in to blending for
126+
/// transparent geometry.
127+
/// - This is currently a single blend state for a single color attachment.
128+
/// Per-attachment blending for MRT is future work.
129+
#[derive(Clone, Copy, Debug)]
130+
pub enum BlendMode {
131+
/// No blending; replace destination (default).
132+
None,
133+
/// Standard alpha blending.
134+
AlphaBlending,
135+
/// Premultiplied alpha blending.
136+
PremultipliedAlpha,
137+
/// Additive blending.
138+
Additive,
139+
}
140+
141+
impl BlendMode {
142+
fn to_platform(self) -> platform_pipeline::BlendMode {
143+
return match self {
144+
BlendMode::None => platform_pipeline::BlendMode::None,
145+
BlendMode::AlphaBlending => platform_pipeline::BlendMode::AlphaBlending,
146+
BlendMode::PremultipliedAlpha => {
147+
platform_pipeline::BlendMode::PremultipliedAlpha
148+
}
149+
BlendMode::Additive => platform_pipeline::BlendMode::Additive,
150+
};
151+
}
152+
}
153+
122154
struct BufferBinding {
123155
buffer: Rc<Buffer>,
124156
layout: VertexBufferLayout,
@@ -260,6 +292,7 @@ pub struct RenderPipelineBuilder {
260292
immediate_data: Vec<std::ops::Range<u32>>,
261293
bindings: Vec<BufferBinding>,
262294
culling: CullingMode,
295+
blend_mode: BlendMode,
263296
bind_group_layouts: Vec<bind::BindGroupLayout>,
264297
label: Option<String>,
265298
use_depth: bool,
@@ -283,6 +316,7 @@ impl RenderPipelineBuilder {
283316
immediate_data: Vec::new(),
284317
bindings: Vec::new(),
285318
culling: CullingMode::Back,
319+
blend_mode: BlendMode::None,
286320
bind_group_layouts: Vec::new(),
287321
label: None,
288322
use_depth: false,
@@ -372,6 +406,14 @@ impl RenderPipelineBuilder {
372406
return self;
373407
}
374408

409+
/// Configure blending for the pipeline's color target.
410+
///
411+
/// Defaults to `BlendMode::None` (opaque).
412+
pub fn with_blend(mut self, mode: BlendMode) -> Self {
413+
self.blend_mode = mode;
414+
return self;
415+
}
416+
375417
/// Provide one or more bind group layouts used to create the pipeline layout.
376418
pub fn with_layouts(mut self, layouts: &[&bind::BindGroupLayout]) -> Self {
377419
self.bind_group_layouts = layouts.iter().map(|l| (*l).clone()).collect();
@@ -548,7 +590,8 @@ impl RenderPipelineBuilder {
548590
let mut rp_builder = platform_pipeline::RenderPipelineBuilder::new()
549591
.with_label(self.label.as_deref().unwrap_or("lambda-render-pipeline"))
550592
.with_layout(&pipeline_layout)
551-
.with_cull_mode(self.culling.to_platform());
593+
.with_cull_mode(self.culling.to_platform())
594+
.with_blend(self.blend_mode.to_platform());
552595

553596
for binding in &self.bindings {
554597
let attributes: Vec<platform_pipeline::VertexAttributeDesc> = binding

demos/render/src/bin/reflective_room.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use lambda::{
3535
MeshBuilder,
3636
},
3737
pipeline::{
38+
BlendMode,
3839
CompareFunction,
3940
CullingMode,
4041
RenderPipelineBuilder,
@@ -722,6 +723,7 @@ impl ReflectiveRoomExample {
722723
let mut floor_builder = RenderPipelineBuilder::new()
723724
.with_label("floor-visual")
724725
.with_culling(CullingMode::Back)
726+
.with_blend(BlendMode::AlphaBlending)
725727
.with_immediate_data(immediate_data_size)
726728
.with_buffer(
727729
BufferBuilder::new()

docs/rendering.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,11 @@ let vbo = BufferBuilder::build_from_mesh(&mesh, &mut render_context)
172172
Create a pipeline, with optional push constants:
173173

174174
```rust
175-
use lambda::render::pipeline::{RenderPipelineBuilder, PipelineStage};
175+
use lambda::render::pipeline::{BlendMode, RenderPipelineBuilder, PipelineStage};
176176

177177
let pipeline = RenderPipelineBuilder::new()
178+
// Pipelines default to opaque (no blending). Opt in for transparent geometry:
179+
// .with_blend(BlendMode::AlphaBlending)
178180
.with_push_constant(PipelineStage::VERTEX, 64) // size in bytes
179181
.with_buffer(vbo, mesh.attributes().to_vec())
180182
.build(&mut render_context, &pass, &vs, Some(&fs));

docs/tutorials/rendering/techniques/reflective-room.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ Draw the floor surface with a translucent tint so the reflection remains visible
300300
```rust
301301
let mut floor_vis = RenderPipelineBuilder::new()
302302
.with_label("floor-visual")
303+
.with_blend(lambda::render::pipeline::BlendMode::AlphaBlending)
303304
.with_immediate_data(std::mem::size_of::<ImmediateData>() as u32)
304305
.with_buffer(floor_vertex_buffer, floor_attributes)
305306
.with_multi_sample(msaa_samples);

0 commit comments

Comments
 (0)