Skip to content

Commit 18471d8

Browse files
atlv24cwfitzgeraldteoxoy
authored
Image atomics support (#6706)
* Image atomics support * Address feedback * fix merge * Fixes * Add a couple tests * Update wgpu-types/src/lib.rs Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com> * feedback * feedback * glsl * glsl fix * fix glsl * fix fix * fix fix fic * fix? * fix --------- Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com> Co-authored-by: Teodor Tanasoaia <28601907+teoxoy@users.noreply.github.com>
1 parent a5704d9 commit 18471d8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1294
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ By @wumpf in [#6849](https://github.com/gfx-rs/wgpu/pull/6849).
164164
- `DeviceType` and `AdapterInfo` now impl `Hash` by @cwfitzgerald in [#6868](https://github.com/gfx-rs/wgpu/pull/6868)
165165
- Add build support for Apple Vision Pro. By @guusw in [#6611](https://github.com/gfx-rs/wgpu/pull/6611).
166166
- Add `wgsl_language_features` for obtaining available WGSL language feature by @sagudev in [#6814](https://github.com/gfx-rs/wgpu/pull/6814)
167+
- Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs/wgpu/pull/6706)
167168
- Add `no_std` support to `wgpu-types`. By @bushrat011899 in [#6892](https://github.com/gfx-rs/wgpu/pull/6892).
168169

169170
##### Vulkan

naga/src/back/dot/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,21 @@ impl StatementGraph {
254254
}
255255
"Atomic"
256256
}
257+
S::ImageAtomic {
258+
image,
259+
coordinate,
260+
array_index,
261+
fun: _,
262+
value,
263+
} => {
264+
self.dependencies.push((id, image, "image"));
265+
self.dependencies.push((id, coordinate, "coordinate"));
266+
if let Some(expr) = array_index {
267+
self.dependencies.push((id, expr, "array_index"));
268+
}
269+
self.dependencies.push((id, value, "value"));
270+
"ImageAtomic"
271+
}
257272
S::WorkGroupUniformLoad { pointer, result } => {
258273
self.emits.push((id, result));
259274
self.dependencies.push((id, pointer, "pointer"));

naga/src/back/glsl/features.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ bitflags::bitflags! {
5252
const TEXTURE_SHADOW_LOD = 1 << 23;
5353
/// Subgroup operations
5454
const SUBGROUP_OPERATIONS = 1 << 24;
55+
/// Image atomics
56+
const TEXTURE_ATOMICS = 1 << 25;
5557
}
5658
}
5759

@@ -120,6 +122,7 @@ impl FeaturesManager {
120122
check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
121123
check_feature!(DUAL_SOURCE_BLENDING, 330, 300 /* with extension */);
122124
check_feature!(SUBGROUP_OPERATIONS, 430, 310);
125+
check_feature!(TEXTURE_ATOMICS, 420, 310);
123126
match version {
124127
Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
125128
_ => check_feature!(MULTI_VIEW, 140, 310),
@@ -278,6 +281,11 @@ impl FeaturesManager {
278281
)?;
279282
}
280283

284+
if self.0.contains(Features::TEXTURE_ATOMICS) {
285+
// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_shader_image_atomic.txt
286+
writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
287+
}
288+
281289
Ok(())
282290
}
283291
}
@@ -546,6 +554,22 @@ impl<W> Writer<'_, W> {
546554
}
547555
}
548556

557+
for blocks in module
558+
.functions
559+
.iter()
560+
.map(|(_, f)| &f.body)
561+
.chain(std::iter::once(&entry_point.function.body))
562+
{
563+
for (stmt, _) in blocks.span_iter() {
564+
match *stmt {
565+
crate::Statement::ImageAtomic { .. } => {
566+
features.request(Features::TEXTURE_ATOMICS)
567+
}
568+
_ => {}
569+
}
570+
}
571+
}
572+
549573
self.features.check_availability(self.options.version)
550574
}
551575

naga/src/back/glsl/mod.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,6 +2475,17 @@ impl<'a, W: Write> Writer<'a, W> {
24752475
self.write_expr(value, ctx)?;
24762476
writeln!(self.out, ");")?;
24772477
}
2478+
// Stores a value into an image.
2479+
Statement::ImageAtomic {
2480+
image,
2481+
coordinate,
2482+
array_index,
2483+
fun,
2484+
value,
2485+
} => {
2486+
write!(self.out, "{level}")?;
2487+
self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)?
2488+
}
24782489
Statement::RayQuery { .. } => unreachable!(),
24792490
Statement::SubgroupBallot { result, predicate } => {
24802491
write!(self.out, "{level}")?;
@@ -4137,6 +4148,56 @@ impl<'a, W: Write> Writer<'a, W> {
41374148
Ok(())
41384149
}
41394150

4151+
/// Helper method to write the `ImageAtomic` statement
4152+
fn write_image_atomic(
4153+
&mut self,
4154+
ctx: &back::FunctionCtx,
4155+
image: Handle<crate::Expression>,
4156+
coordinate: Handle<crate::Expression>,
4157+
array_index: Option<Handle<crate::Expression>>,
4158+
fun: crate::AtomicFunction,
4159+
value: Handle<crate::Expression>,
4160+
) -> Result<(), Error> {
4161+
use crate::ImageDimension as IDim;
4162+
4163+
// NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid
4164+
// so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20)
4165+
4166+
// This will only panic if the module is invalid
4167+
let dim = match *ctx.resolve_type(image, &self.module.types) {
4168+
TypeInner::Image { dim, .. } => dim,
4169+
_ => unreachable!(),
4170+
};
4171+
4172+
// Begin our call to `imageAtomic`
4173+
let fun_str = fun.to_glsl();
4174+
write!(self.out, "imageAtomic{fun_str}(")?;
4175+
self.write_expr(image, ctx)?;
4176+
// Separate the image argument from the coordinates
4177+
write!(self.out, ", ")?;
4178+
4179+
// openGL es doesn't have 1D images so we need workaround it
4180+
let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
4181+
// Write the coordinate vector
4182+
self.write_texture_coord(
4183+
ctx,
4184+
// Get the size of the coordinate vector
4185+
self.get_coordinate_vector_size(dim, false),
4186+
coordinate,
4187+
array_index,
4188+
tex_1d_hack,
4189+
)?;
4190+
4191+
// Separate the coordinate from the value to write and write the expression
4192+
// of the value to write.
4193+
write!(self.out, ", ")?;
4194+
self.write_expr(value, ctx)?;
4195+
// End the call to `imageAtomic` and the statement.
4196+
writeln!(self.out, ");")?;
4197+
4198+
Ok(())
4199+
}
4200+
41404201
/// Helper method for writing an `ImageLoad` expression.
41414202
#[allow(clippy::too_many_arguments)]
41424203
fn write_image_load(
@@ -4533,6 +4594,9 @@ impl<'a, W: Write> Writer<'a, W> {
45334594
/// they can only be used to query information about the resource which isn't what
45344595
/// we want here so when storage access is both `LOAD` and `STORE` add no modifiers
45354596
fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult {
4597+
if storage_access.contains(crate::StorageAccess::ATOMIC) {
4598+
return Ok(());
4599+
}
45364600
if !storage_access.contains(crate::StorageAccess::STORE) {
45374601
write!(self.out, "readonly ")?;
45384602
}

naga/src/back/hlsl/writer.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,6 +2210,32 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
22102210

22112211
writeln!(self.out, ");")?;
22122212
}
2213+
Statement::ImageAtomic {
2214+
image,
2215+
coordinate,
2216+
array_index,
2217+
fun,
2218+
value,
2219+
} => {
2220+
write!(self.out, "{level}")?;
2221+
2222+
let fun_str = fun.to_hlsl_suffix();
2223+
write!(self.out, "Interlocked{fun_str}(")?;
2224+
self.write_expr(module, image, func_ctx)?;
2225+
write!(self.out, "[")?;
2226+
self.write_texture_coordinates(
2227+
"int",
2228+
coordinate,
2229+
array_index,
2230+
None,
2231+
module,
2232+
func_ctx,
2233+
)?;
2234+
write!(self.out, "],")?;
2235+
2236+
self.write_expr(module, value, func_ctx)?;
2237+
writeln!(self.out, ");")?;
2238+
}
22132239
Statement::WorkGroupUniformLoad { pointer, result } => {
22142240
self.write_barrier(crate::Barrier::WORK_GROUP, level)?;
22152241
write!(self.out, "{level}")?;

naga/src/back/msl/writer.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ impl TypedGlobalVariable<'_> {
344344
let (space, access, reference) = match var.space.to_msl_name() {
345345
Some(space) if self.reference => {
346346
let access = if var.space.needs_access_qualifier()
347-
&& !self.usage.contains(valid::GlobalUse::WRITE)
347+
&& !self.usage.intersects(valid::GlobalUse::WRITE)
348348
{
349349
"const"
350350
} else {
@@ -1201,6 +1201,28 @@ impl<W: Write> Writer<W> {
12011201
Ok(())
12021202
}
12031203

1204+
fn put_image_atomic(
1205+
&mut self,
1206+
level: back::Level,
1207+
image: Handle<crate::Expression>,
1208+
address: &TexelAddress,
1209+
fun: crate::AtomicFunction,
1210+
value: Handle<crate::Expression>,
1211+
context: &StatementContext,
1212+
) -> BackendResult {
1213+
write!(self.out, "{level}")?;
1214+
self.put_expression(image, &context.expression, false)?;
1215+
let op = fun.to_msl();
1216+
write!(self.out, ".atomic_{}(", op)?;
1217+
// coordinates in IR are int, but Metal expects uint
1218+
self.put_cast_to_uint_scalar_or_vector(address.coordinate, &context.expression)?;
1219+
write!(self.out, ", ")?;
1220+
self.put_expression(value, &context.expression, true)?;
1221+
writeln!(self.out, ");")?;
1222+
1223+
Ok(())
1224+
}
1225+
12041226
fn put_image_store(
12051227
&mut self,
12061228
level: back::Level,
@@ -3248,6 +3270,21 @@ impl<W: Write> Writer<W> {
32483270
// Done
32493271
writeln!(self.out, ";")?;
32503272
}
3273+
crate::Statement::ImageAtomic {
3274+
image,
3275+
coordinate,
3276+
array_index,
3277+
fun,
3278+
value,
3279+
} => {
3280+
let address = TexelAddress {
3281+
coordinate,
3282+
array_index,
3283+
sample: None,
3284+
level: None,
3285+
};
3286+
self.put_image_atomic(level, image, &address, fun, value, context)?
3287+
}
32513288
crate::Statement::WorkGroupUniformLoad { pointer, result } => {
32523289
self.write_barrier(crate::Barrier::WORK_GROUP, level)?;
32533290

naga/src/back/pipeline_constants.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,20 @@ fn adjust_stmt(new_pos: &HandleVec<Expression, Handle<Expression>>, stmt: &mut S
736736
| crate::AtomicFunction::Exchange { compare: None } => {}
737737
}
738738
}
739+
Statement::ImageAtomic {
740+
ref mut image,
741+
ref mut coordinate,
742+
ref mut array_index,
743+
fun: _,
744+
ref mut value,
745+
} => {
746+
adjust(image);
747+
adjust(coordinate);
748+
if let Some(ref mut array_index) = *array_index {
749+
adjust(array_index);
750+
}
751+
adjust(value);
752+
}
739753
Statement::WorkGroupUniformLoad {
740754
ref mut pointer,
741755
ref mut result,

naga/src/back/spv/block.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2921,6 +2921,22 @@ impl BlockContext<'_> {
29212921

29222922
block.body.push(instruction);
29232923
}
2924+
Statement::ImageAtomic {
2925+
image,
2926+
coordinate,
2927+
array_index,
2928+
fun,
2929+
value,
2930+
} => {
2931+
self.write_image_atomic(
2932+
image,
2933+
coordinate,
2934+
array_index,
2935+
fun,
2936+
value,
2937+
&mut block,
2938+
)?;
2939+
}
29242940
Statement::WorkGroupUniformLoad { pointer, result } => {
29252941
self.writer
29262942
.write_barrier(crate::Barrier::WORK_GROUP, &mut block);

naga/src/back/spv/image.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,4 +1225,77 @@ impl BlockContext<'_> {
12251225

12261226
Ok(())
12271227
}
1228+
1229+
pub(super) fn write_image_atomic(
1230+
&mut self,
1231+
image: Handle<crate::Expression>,
1232+
coordinate: Handle<crate::Expression>,
1233+
array_index: Option<Handle<crate::Expression>>,
1234+
fun: crate::AtomicFunction,
1235+
value: Handle<crate::Expression>,
1236+
block: &mut Block,
1237+
) -> Result<(), Error> {
1238+
let image_id = match self.ir_function.originating_global(image) {
1239+
Some(handle) => self.writer.global_variables[handle].var_id,
1240+
_ => return Err(Error::Validation("Unexpected image type")),
1241+
};
1242+
let crate::TypeInner::Image { class, .. } =
1243+
*self.fun_info[image].ty.inner_with(&self.ir_module.types)
1244+
else {
1245+
return Err(Error::Validation("Invalid image type"));
1246+
};
1247+
let crate::ImageClass::Storage { format, .. } = class else {
1248+
return Err(Error::Validation("Invalid image class"));
1249+
};
1250+
let scalar = format.into();
1251+
let pointer_type_id = self.get_type_id(LookupType::Local(LocalType::LocalPointer {
1252+
base: NumericType::Scalar(scalar),
1253+
class: spirv::StorageClass::Image,
1254+
}));
1255+
let signed = scalar.kind == crate::ScalarKind::Sint;
1256+
let pointer_id = self.gen_id();
1257+
let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
1258+
let sample_id = self.writer.get_constant_scalar(crate::Literal::U32(0));
1259+
block.body.push(Instruction::image_texel_pointer(
1260+
pointer_type_id,
1261+
pointer_id,
1262+
image_id,
1263+
coordinates.value_id,
1264+
sample_id,
1265+
));
1266+
1267+
let op = match fun {
1268+
crate::AtomicFunction::Add => spirv::Op::AtomicIAdd,
1269+
crate::AtomicFunction::Subtract => spirv::Op::AtomicISub,
1270+
crate::AtomicFunction::And => spirv::Op::AtomicAnd,
1271+
crate::AtomicFunction::ExclusiveOr => spirv::Op::AtomicXor,
1272+
crate::AtomicFunction::InclusiveOr => spirv::Op::AtomicOr,
1273+
crate::AtomicFunction::Min if signed => spirv::Op::AtomicSMin,
1274+
crate::AtomicFunction::Min => spirv::Op::AtomicUMin,
1275+
crate::AtomicFunction::Max if signed => spirv::Op::AtomicSMax,
1276+
crate::AtomicFunction::Max => spirv::Op::AtomicUMax,
1277+
crate::AtomicFunction::Exchange { .. } => {
1278+
return Err(Error::Validation("Exchange atomics are not supported yet"))
1279+
}
1280+
};
1281+
let result_type_id = self.get_expression_type_id(&self.fun_info[value].ty);
1282+
let id = self.gen_id();
1283+
let space = crate::AddressSpace::Handle;
1284+
let (semantics, scope) = space.to_spirv_semantics_and_scope();
1285+
let scope_constant_id = self.get_scope_constant(scope as u32);
1286+
let semantics_id = self.get_index_constant(semantics.bits());
1287+
let value_id = self.cached[value];
1288+
1289+
block.body.push(Instruction::image_atomic(
1290+
op,
1291+
result_type_id,
1292+
id,
1293+
pointer_id,
1294+
scope_constant_id,
1295+
semantics_id,
1296+
value_id,
1297+
));
1298+
1299+
Ok(())
1300+
}
12281301
}

0 commit comments

Comments
 (0)