Skip to content

Commit 3926130

Browse files
committed
LoadOp::DontCare
1 parent b599252 commit 3926130

File tree

18 files changed

+274
-44
lines changed

18 files changed

+274
-44
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ By @R-Cramer4 in [#8230](https://github.com/gfx-rs/wgpu/pull/8230)
6161

6262
[kek]: https://web.archive.org/web/20250923122958/https://knowyourmeme.com/memes/kek
6363

64+
#### New `LoadOp::DontCare`
65+
66+
In the case where a renderpass unconditionally writes to all pixels in the rendertarget,
67+
`Load` can cause unnecessary memory traffic, and `Clear` can spend time unnecessarily
68+
clearing the rendertargets. `DontCare` is a new `LoadOp` which will leave the contents
69+
of the rendertarget undefined. Because this could lead to undefined behavior, this API
70+
requires that the user gives an unsafe token to use the api.
71+
72+
While you can use this unconditionally, on platforms where `DontCare` is not available,
73+
it will internally use a different load op.
74+
75+
```rust
76+
load: LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() })
77+
```
78+
79+
By @cwfitzgerald in [#8549](https://github.com/gfx-rs/wgpu/pull/8549)
80+
6481
#### `MipmapFilterMode` is split from `FilterMode`
6582

6683
This is a breaking change that aligns wgpu with spec.

tests/tests/wgpu-gpu/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ mod multiview;
4040
mod occlusion_query;
4141
mod oob_indexing;
4242
mod oom;
43+
mod pass_ops;
4344
mod pipeline;
4445
mod pipeline_cache;
4546
mod planar_texture;
@@ -104,6 +105,7 @@ fn all_tests() -> Vec<wgpu_test::GpuTestInitializer> {
104105
occlusion_query::all_tests(&mut tests);
105106
oob_indexing::all_tests(&mut tests);
106107
oom::all_tests(&mut tests);
108+
pass_ops::all_tests(&mut tests);
107109
pipeline_cache::all_tests(&mut tests);
108110
pipeline::all_tests(&mut tests);
109111
planar_texture::all_tests(&mut tests);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use wgpu_test::{
2+
gpu_test, image::ReadbackBuffers, GpuTestConfiguration, GpuTestInitializer, TestParameters,
3+
TestingContext,
4+
};
5+
6+
pub fn all_tests(vec: &mut Vec<GpuTestInitializer>) {
7+
vec.push(DONT_CARE);
8+
}
9+
10+
#[gpu_test]
11+
static DONT_CARE: GpuTestConfiguration = GpuTestConfiguration::new()
12+
.parameters(TestParameters::default())
13+
.run_async(run_test);
14+
15+
async fn run_test(ctx: TestingContext) {
16+
let shader_src = "
17+
const triangles = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
18+
19+
@vertex
20+
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {
21+
return vec4f(triangles[vertex_index], 0.0, 1.0);
22+
}
23+
24+
@fragment
25+
fn fs_main() -> @location(0) vec4f {
26+
return vec4f(127.0 / 255.0);
27+
}
28+
";
29+
30+
let shader = ctx
31+
.device
32+
.create_shader_module(wgpu::ShaderModuleDescriptor {
33+
label: None,
34+
source: wgpu::ShaderSource::Wgsl(shader_src.into()),
35+
});
36+
37+
let pipeline_desc = wgpu::RenderPipelineDescriptor {
38+
label: None,
39+
layout: None,
40+
vertex: wgpu::VertexState {
41+
buffers: &[],
42+
module: &shader,
43+
entry_point: Some("vs_main"),
44+
compilation_options: Default::default(),
45+
},
46+
primitive: wgpu::PrimitiveState::default(),
47+
depth_stencil: None,
48+
multisample: wgpu::MultisampleState::default(),
49+
fragment: Some(wgpu::FragmentState {
50+
module: &shader,
51+
entry_point: Some("fs_main"),
52+
compilation_options: Default::default(),
53+
targets: &[Some(wgpu::ColorTargetState {
54+
format: wgpu::TextureFormat::Rgba8Unorm,
55+
blend: None,
56+
write_mask: wgpu::ColorWrites::ALL,
57+
})],
58+
}),
59+
multiview_mask: None,
60+
cache: None,
61+
};
62+
let pipeline = ctx.device.create_render_pipeline(&pipeline_desc);
63+
64+
let out_texture = ctx.device.create_texture(&wgpu::TextureDescriptor {
65+
label: None,
66+
size: wgpu::Extent3d {
67+
width: 1,
68+
height: 1,
69+
depth_or_array_layers: 1,
70+
},
71+
mip_level_count: 1,
72+
sample_count: 1,
73+
dimension: wgpu::TextureDimension::D2,
74+
format: wgpu::TextureFormat::Rgba8Unorm,
75+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
76+
view_formats: &[],
77+
});
78+
79+
let readbacks = ReadbackBuffers::new(&ctx.device, &out_texture);
80+
81+
let mut encoder = ctx
82+
.device
83+
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
84+
85+
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
86+
label: None,
87+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
88+
view: &out_texture.create_view(&wgpu::TextureViewDescriptor::default()),
89+
depth_slice: None,
90+
resolve_target: None,
91+
ops: wgpu::Operations {
92+
load: wgpu::LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() }),
93+
store: wgpu::StoreOp::Store,
94+
},
95+
})],
96+
depth_stencil_attachment: None,
97+
timestamp_writes: None,
98+
occlusion_query_set: None,
99+
multiview_mask: None,
100+
});
101+
rpass.set_pipeline(&pipeline);
102+
rpass.draw(0..3, 0..1);
103+
104+
drop(rpass);
105+
106+
readbacks.copy_from(&ctx.device, &mut encoder, &out_texture);
107+
108+
ctx.queue.submit([encoder.finish()]);
109+
110+
// Assert that DONT_CARE load op was fully overridden by the draw.
111+
readbacks
112+
.assert_buffer_contents(&ctx, &[127, 127, 127, 127])
113+
.await;
114+
}

wgpu-core/src/command/clear.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ fn clear_texture_via_render_passes(
498498
},
499499
depth_slice: None,
500500
resolve_target: None,
501-
ops: hal::AttachmentOps::STORE,
501+
ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
502502
clear_value: wgt::Color::TRANSPARENT,
503503
})];
504504
(&color_attachments_tmp[..], None)
@@ -515,8 +515,8 @@ fn clear_texture_via_render_passes(
515515
),
516516
usage: wgt::TextureUses::DEPTH_STENCIL_WRITE,
517517
},
518-
depth_ops: hal::AttachmentOps::STORE,
519-
stencil_ops: hal::AttachmentOps::STORE,
518+
depth_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
519+
stencil_ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
520520
clear_value: (0.0, 0),
521521
}),
522522
)

wgpu-core/src/command/render.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,15 @@ pub use wgt::{LoadOp, StoreOp};
6565
fn load_hal_ops<V>(load: LoadOp<V>) -> hal::AttachmentOps {
6666
match load {
6767
LoadOp::Load => hal::AttachmentOps::LOAD,
68-
LoadOp::Clear(_) => hal::AttachmentOps::empty(),
68+
LoadOp::Clear(_) => hal::AttachmentOps::LOAD_CLEAR,
69+
LoadOp::DontCare(_) => hal::AttachmentOps::LOAD_DONT_CARE,
6970
}
7071
}
7172

7273
fn store_hal_ops(store: StoreOp) -> hal::AttachmentOps {
7374
match store {
7475
StoreOp::Store => hal::AttachmentOps::STORE,
75-
StoreOp::Discard => hal::AttachmentOps::empty(),
76+
StoreOp::Discard => hal::AttachmentOps::STORE_DISCARD,
7677
}
7778
}
7879

@@ -115,6 +116,7 @@ impl<V: Copy + Default> PassChannel<Option<V>> {
115116
Ok(ResolvedPassChannel::Operational(wgt::Operations {
116117
load: match self.load_op.ok_or(AttachmentError::NoLoad)? {
117118
LoadOp::Clear(clear_value) => LoadOp::Clear(handle_clear(clear_value)?),
119+
LoadOp::DontCare(token) => LoadOp::DontCare(token),
118120
LoadOp::Load => LoadOp::Load,
119121
},
120122
store: self.store_op.ok_or(AttachmentError::NoStore)?,
@@ -204,7 +206,7 @@ impl ArcRenderPassColorAttachment {
204206
fn clear_value(&self) -> Color {
205207
match self.load_op {
206208
LoadOp::Clear(clear_value) => clear_value,
207-
LoadOp::Load => Color::default(),
209+
LoadOp::DontCare(_) | LoadOp::Load => Color::default(),
208210
}
209211
}
210212
}
@@ -1555,13 +1557,13 @@ impl RenderPassInfo {
15551557
if let Some((aspect, view)) = self.divergent_discarded_depth_stencil_aspect {
15561558
let (depth_ops, stencil_ops) = if aspect == wgt::TextureAspect::DepthOnly {
15571559
(
1558-
hal::AttachmentOps::STORE, // clear depth
1559-
hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil
1560+
hal::AttachmentOps::LOAD_CLEAR | hal::AttachmentOps::STORE, // clear depth
1561+
hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil
15601562
)
15611563
} else {
15621564
(
15631565
hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil
1564-
hal::AttachmentOps::STORE, // clear depth
1566+
hal::AttachmentOps::LOAD_CLEAR | hal::AttachmentOps::STORE, // clear depth
15651567
)
15661568
};
15671569
let desc = hal::RenderPassDescriptor::<'_, _, dyn hal::DynTextureView> {

wgpu-hal/examples/halmark/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ impl<A: hal::Api> Example<A> {
718718
},
719719
depth_slice: None,
720720
resolve_target: None,
721-
ops: hal::AttachmentOps::STORE,
721+
ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
722722
clear_value: wgpu_types::Color {
723723
r: 0.1,
724724
g: 0.2,

wgpu-hal/examples/raw-gles.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter<hal::api::Gles>, width: u32, height
325325
},
326326
depth_slice: None,
327327
resolve_target: None,
328-
ops: hal::AttachmentOps::STORE,
328+
ops: hal::AttachmentOps::STORE | hal::AttachmentOps::LOAD_CLEAR,
329329
clear_value: wgpu_types::Color::BLUE,
330330
})],
331331
depth_stencil_attachment: None,

wgpu-hal/src/dx12/command.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
964964
self.pass.resolves.clear();
965965
for (rtv, cat) in color_views.iter().zip(desc.color_attachments.iter()) {
966966
if let Some(cat) = cat.as_ref() {
967-
if !cat.ops.contains(crate::AttachmentOps::LOAD) {
967+
if cat.ops.contains(crate::AttachmentOps::LOAD_CLEAR) {
968968
let value = [
969969
cat.clear_value.r as f32,
970970
cat.clear_value.g as f32,
@@ -989,12 +989,12 @@ impl crate::CommandEncoder for super::CommandEncoder {
989989
if let Some(ref ds) = desc.depth_stencil_attachment {
990990
let mut flags = Direct3D12::D3D12_CLEAR_FLAGS::default();
991991
let aspects = ds.target.view.aspects;
992-
if !ds.depth_ops.contains(crate::AttachmentOps::LOAD)
992+
if ds.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR)
993993
&& aspects.contains(crate::FormatAspects::DEPTH)
994994
{
995995
flags |= Direct3D12::D3D12_CLEAR_FLAG_DEPTH;
996996
}
997-
if !ds.stencil_ops.contains(crate::AttachmentOps::LOAD)
997+
if ds.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR)
998998
&& aspects.contains(crate::FormatAspects::STENCIL)
999999
{
10001000
flags |= Direct3D12::D3D12_CLEAR_FLAG_STENCIL;

wgpu-hal/src/gles/command.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
567567
.resolve_attachments
568568
.push((attachment, rat.view.clone()));
569569
}
570-
if !cat.ops.contains(crate::AttachmentOps::STORE) {
570+
if cat.ops.contains(crate::AttachmentOps::STORE_DISCARD) {
571571
self.state.invalidate_attachments.push(attachment);
572572
}
573573
}
@@ -585,14 +585,16 @@ impl crate::CommandEncoder for super::CommandEncoder {
585585
depth_slice: None,
586586
});
587587
if aspects.contains(crate::FormatAspects::DEPTH)
588-
&& !dsat.depth_ops.contains(crate::AttachmentOps::STORE)
588+
&& dsat.depth_ops.contains(crate::AttachmentOps::STORE_DISCARD)
589589
{
590590
self.state
591591
.invalidate_attachments
592592
.push(glow::DEPTH_ATTACHMENT);
593593
}
594594
if aspects.contains(crate::FormatAspects::STENCIL)
595-
&& !dsat.stencil_ops.contains(crate::AttachmentOps::STORE)
595+
&& dsat
596+
.stencil_ops
597+
.contains(crate::AttachmentOps::STORE_DISCARD)
596598
{
597599
self.state
598600
.invalidate_attachments
@@ -628,7 +630,7 @@ impl crate::CommandEncoder for super::CommandEncoder {
628630
.filter_map(|at| at.as_ref())
629631
.enumerate()
630632
{
631-
if !cat.ops.contains(crate::AttachmentOps::LOAD) {
633+
if cat.ops.contains(crate::AttachmentOps::LOAD_CLEAR) {
632634
let c = &cat.clear_value;
633635
self.cmd_buffer.commands.push(
634636
match cat.target.view.format.sample_type(None, None).unwrap() {
@@ -652,8 +654,8 @@ impl crate::CommandEncoder for super::CommandEncoder {
652654
}
653655

654656
if let Some(ref dsat) = desc.depth_stencil_attachment {
655-
let clear_depth = !dsat.depth_ops.contains(crate::AttachmentOps::LOAD);
656-
let clear_stencil = !dsat.stencil_ops.contains(crate::AttachmentOps::LOAD);
657+
let clear_depth = dsat.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR);
658+
let clear_stencil = dsat.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR);
657659

658660
if clear_depth && clear_stencil {
659661
self.cmd_buffer.commands.push(C::ClearDepthAndStencil(

wgpu-hal/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1757,7 +1757,10 @@ bitflags!(
17571757
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
17581758
pub struct AttachmentOps: u8 {
17591759
const LOAD = 1 << 0;
1760-
const STORE = 1 << 1;
1760+
const LOAD_CLEAR = 1 << 1;
1761+
const LOAD_DONT_CARE = 1 << 2;
1762+
const STORE = 1 << 3;
1763+
const STORE_DISCARD = 1 << 4;
17611764
}
17621765
);
17631766

0 commit comments

Comments
 (0)