use std::collections::BTreeMap;
use std::num::NonZeroU32;
use iridia::ResourceId;
use iridia::build::{Kind, Val, Width, component};
use naga::{
AddressSpace, ArraySize, BinaryOperator as Bo, Expression as E, GlobalVariable, Handle, ImageClass,
ImageQuery, ResourceBinding, SampleLevel, StorageAccess, TypeInner,
};
use super::Lift;
use super::operands::OperandAt;
use super::setup::{Declaration, Shared, Texture, UavBuffer, UavKind, View};
use crate::Result;
use dxbc_reader::decode::{Instruction, Operand, Selection};
#[derive(Clone, Copy)]
pub(super) enum Buffer {
Typed,
Words { stride: u32 },
}
#[derive(Default)]
pub(super) struct Resources {
pub bound: Vec<(ResourceId, Handle<GlobalVariable>)>,
pub constant_buffers: BTreeMap<u32, (Handle<GlobalVariable>, u32)>,
pub textures: BTreeMap<u32, (Handle<GlobalVariable>, Texture)>,
pub samplers: BTreeMap<u32, Handle<GlobalVariable>>,
pub buffers: BTreeMap<u32, (Handle<GlobalVariable>, Buffer)>,
pub views: BTreeMap<u32, (Handle<GlobalVariable>, View)>,
pub uav_buffers: BTreeMap<u32, (Handle<GlobalVariable>, UavBuffer)>,
pub shared: BTreeMap<u32, (Handle<GlobalVariable>, Shared)>,
pub immediate: Option<(Handle<GlobalVariable>, u32)>,
}
pub(super) fn lanes_of(mask: u8) -> Vec<u8> {
(0..4).filter(|i| mask >> i & 1 != 0).collect()
}
impl Lift<'_> {
pub(super) fn declare_resources(&mut self) -> Result<()> {
let u4 = self.u4();
let u1 = self.b.ty_of(Kind::U, Width::One);
let declarations = self.setup.declarations.clone();
for (i, d) in declarations.iter().enumerate() {
let Ok(id) = u32::try_from(i).map(ResourceId) else {
return self.refuse("more than 4294967295 resources");
};
let (name, space, group, slot, ty) = match d {
Declaration::ConstantBuffer { slot, rows } => {
let rows = NonZeroU32::new((*rows).max(1)).expect("at least one row");
let ty = self.b.ty(TypeInner::Array {
base: u4,
size: ArraySize::Constant(rows),
stride: 16,
});
(format!("cb{slot}"), AddressSpace::Uniform, 0, *slot, ty)
}
Declaration::Texture(t) => {
let class = if t.depth {
ImageClass::Depth { multi: t.multi }
} else {
ImageClass::Sampled {
kind: t.kind.kind(),
multi: t.multi,
}
};
let ty = self.b.ty(TypeInner::Image {
dim: t.dim,
arrayed: t.arrayed,
class,
});
(format!("t{}", t.slot), AddressSpace::Handle, 1, t.slot, ty)
}
Declaration::Sampler { slot, comparison } => {
let ty = self.b.ty(TypeInner::Sampler {
comparison: *comparison,
});
(format!("s{slot}"), AddressSpace::Handle, 2, *slot, ty)
}
Declaration::View(v) => {
let format = match (v.kind, v.atomic) {
(Kind::I, true) => naga::StorageFormat::R32Sint,
(_, true) => naga::StorageFormat::R32Uint,
(Kind::I, _) => naga::StorageFormat::Rgba32Sint,
(Kind::U, _) => naga::StorageFormat::Rgba32Uint,
_ => naga::StorageFormat::Rgba32Float,
};
let mut access = StorageAccess::empty();
access.set(StorageAccess::LOAD, v.load || !v.store);
access.set(StorageAccess::STORE, v.store);
access.set(StorageAccess::ATOMIC, v.atomic);
let ty = self.b.ty(TypeInner::Image {
dim: v.dim,
arrayed: v.arrayed,
class: ImageClass::Storage { format, access },
});
self.notes.insert(
"a typed unordered-access view is carried as a storage image of four 32-bit lanes: its \
format is the binding's"
.into(),
);
(format!("u{}", v.slot), AddressSpace::Handle, 3, v.slot, ty)
}
Declaration::UavBuffer(b) => {
let typed = matches!(b.kind, UavKind::Typed(_));
let (base, stride) = match (typed, b.atomic) {
(true, Some(scalar)) => (self.b.atomic_quad(scalar), 16),
(true, None) => (u4, 16),
(false, Some(scalar)) => (self.b.ty(TypeInner::Atomic(scalar)), 4),
(false, None) => (u1, 4),
};
let ty = self.b.ty(TypeInner::Array {
base,
size: ArraySize::Dynamic,
stride,
});
let mut access = StorageAccess::LOAD;
access.set(StorageAccess::STORE, b.store);
(
format!("u{}", b.slot),
AddressSpace::Storage { access },
3,
b.slot,
ty,
)
}
Declaration::TypedBuffer { slot } => {
let ty = self.b.ty(TypeInner::Array {
base: u4,
size: ArraySize::Dynamic,
stride: 16,
});
(
format!("tb{slot}"),
AddressSpace::Storage {
access: StorageAccess::LOAD,
},
1,
*slot,
ty,
)
}
Declaration::RawBuffer { slot } | Declaration::StructuredBuffer { slot, .. } => {
let ty = self.b.ty(TypeInner::Array {
base: u1,
size: ArraySize::Dynamic,
stride: 4,
});
(
format!("sb{slot}"),
AddressSpace::Storage {
access: StorageAccess::LOAD,
},
1,
*slot,
ty,
)
}
};
let global = self.b.global(GlobalVariable {
name: Some(name),
space,
binding: Some(ResourceBinding { group, binding: slot }),
ty,
init: None,
memory_decorations: Default::default(),
});
self.resources.bound.push((id, global));
match d {
Declaration::ConstantBuffer { slot, rows } => {
self.resources
.constant_buffers
.insert(*slot, (global, (*rows).max(1)));
}
Declaration::Texture(t) => {
self.resources.textures.insert(t.slot, (global, *t));
}
Declaration::Sampler { slot, .. } => {
self.resources.samplers.insert(*slot, global);
}
Declaration::View(v) => {
self.resources.views.insert(v.slot, (global, *v));
}
Declaration::UavBuffer(b) => {
self.resources.uav_buffers.insert(b.slot, (global, *b));
}
Declaration::TypedBuffer { slot } => {
self.resources.buffers.insert(*slot, (global, Buffer::Typed));
}
Declaration::RawBuffer { slot } => {
self.resources
.buffers
.insert(*slot, (global, Buffer::Words { stride: 4 }));
}
Declaration::StructuredBuffer { slot, stride } => {
self.resources.buffers.insert(
*slot,
(
global,
Buffer::Words {
stride: (*stride).max(4),
},
),
);
}
}
}
for g in self.setup.shared.clone() {
let Some(len) = NonZeroU32::new(g.words) else {
return self.refuse(format!("g{}: no thread-group memory", g.register));
};
let base = match g.atomic {
Some(scalar) => self.b.ty(TypeInner::Atomic(scalar)),
None => u1,
};
let ty = self.b.ty(TypeInner::Array {
base,
size: ArraySize::Constant(len),
stride: 4,
});
let global = self.b.global(GlobalVariable {
name: Some(format!("g{}", g.register)),
space: AddressSpace::WorkGroup,
binding: None,
ty,
init: None,
memory_decorations: Default::default(),
});
self.resources.shared.insert(g.register, (global, g));
}
if !self.setup.immediates.is_empty() {
let Ok(rows) = u32::try_from(self.setup.immediates.len()) else {
return self.refuse("more than 4294967295 immediate rows");
};
let Some(size) = NonZeroU32::new(rows) else {
return self.refuse("an immediate constant buffer of no rows");
};
let ty = self.b.ty(TypeInner::Array {
base: u4,
size: ArraySize::Constant(size),
stride: 16,
});
let immediates = self.setup.immediates.clone();
let mut components = Vec::new();
for row in &immediates {
let lanes: Vec<_> = row
.iter()
.map(|w| self.b.global_expr(E::Literal(naga::Literal::U32(*w))))
.collect();
components.push(self.b.global_expr(E::Compose {
ty: u4,
components: lanes,
}));
}
let init = self.b.global_expr(E::Compose { ty, components });
let global = self.b.global(GlobalVariable {
name: Some("icb".into()),
space: AddressSpace::Private,
binding: None,
ty,
init: Some(init),
memory_decorations: Default::default(),
});
self.resources.immediate = Some((global, rows));
}
Ok(())
}
pub(super) fn guarded_load(
&mut self,
g: Handle<GlobalVariable>,
index: Val,
len: Option<u32>,
kind: Kind,
width: Width,
) -> Val {
let pointer = self.b.global_ptr(g);
let length = match len {
Some(n) => self.b.lit_u(n),
None => Val {
expr: self.b.app(E::ArrayLength(pointer)),
kind: Kind::U,
width: Width::One,
},
};
let inside = self.b.binary(Bo::Less, index, length);
let zero = self.b.lit_u(0);
let safe = self.b.select(inside, index, zero);
let pointer = self.b.global_ptr(g);
let element = self.b.app(E::Access {
base: pointer,
index: safe.expr,
});
let value = self.b.load(element, kind, width);
let none = if width == Width::One {
self.b.lit_u(0)
} else {
let z = self.b.lit_u(0);
self.b.splat(z, width)
};
self.b.select(inside, value, none)
}
fn texture(&self, slot: u32) -> Result<(Handle<GlobalVariable>, Texture)> {
match self.resources.textures.get(&slot) {
Some(t) => Ok(*t),
None if self.resources.buffers.contains_key(&slot) => {
self.refuse(format!("t{slot} is a buffer, used as a texture"))
}
None => self.refuse(format!("t{slot} is used but not declared")),
}
}
fn sampler(&self, slot: u32) -> Result<Handle<GlobalVariable>> {
self.resources
.samplers
.get(&slot)
.copied()
.map_or_else(|| self.refuse(format!("s{slot} is used but not declared")), Ok)
}
pub(super) fn pick(&mut self, result: Val, op: &Operand, mask: u8) -> Val {
let lanes: Vec<u8> = lanes_of(mask)
.iter()
.map(|l| match op.selection {
Selection::Swizzle(s) => super::operands::of_four(s, *l),
Selection::Scalar(c) => c,
_ => 0,
})
.collect();
self.b.swizzle(result, &lanes)
}
fn coordinates(t: &Texture) -> (Width, bool) {
let n = match t.dim {
naga::ImageDimension::D1 => Width::One,
naga::ImageDimension::D2 => Width::Two,
naga::ImageDimension::D3 | naga::ImageDimension::Cube => Width::Three,
};
(n, t.arrayed)
}
pub(super) fn slot_of(
&self,
ins: &Instruction,
n: usize,
kinds: &[dxbc_reader::decode::Register],
) -> Result<u32> {
let operand = ins.operand(n)?;
match operand.indices.first() {
Some(index) if kinds.contains(&operand.register) => Ok(index.base),
_ => self.refuse(format!(
"operand {n} is {:?}, where {kinds:?} is taken",
operand.register
)),
}
}
pub(super) fn sample(&mut self, ins: &Instruction) -> Result<()> {
let name = ins.name();
let texture = self.slot_of(ins, 2, &[dxbc_reader::decode::Register::Texture])?;
let sampler = self.slot_of(ins, 3, &[dxbc_reader::decode::Register::Sampler])?;
let (image, t) = self.texture(texture)?;
let sampler = self.sampler(sampler)?;
if t.multi {
return self.refuse("a sample of a multisampled texture");
}
let (n, arrayed) = Self::coordinates(&t);
let mask = self.dest_mask(ins.operand(0)?);
let lanes: Vec<u8> = (0..n.get().saturating_add(u8::from(arrayed))).collect();
let all = self.read_lanes(ins.operand(1)?, &lanes, Kind::F)?;
let coordinate = if n == all.width {
all
} else {
let c: Vec<u8> = n.lanes().collect();
self.b.swizzle(all, &c)
};
let array_index = if arrayed {
let layer = self.b.lane(all, n.get());
let rounded = self.b.math(naga::MathFunction::Round, &[layer]);
let zero = self.b.lit_f(0.0);
let layer = self.b.d3d_min_max(naga::MathFunction::Max, rounded, zero);
Some(self.b.convert(layer, Kind::U).expr)
} else {
None
};
let offset = match ins.sample_offset() {
Some(o) if o != [0, 0, 0] => {
let parts: Vec<_> = o
.iter()
.take(usize::from(n))
.map(|&c| self.b.app(E::Literal(naga::Literal::I32(c))))
.collect();
Some(if let [only] = parts[..] {
only
} else {
let ty = self.b.ty_of(Kind::I, n);
self.b.app(E::Compose {
ty,
components: parts,
})
})
}
_ => None,
};
let component_of = |op: &Operand| match op.selection {
Selection::Scalar(c) => c,
Selection::Swizzle(s) => s[0],
_ => 0,
};
let (gather, depth_ref, level) = match name {
"sample" => (None, None, SampleLevel::Auto),
"sample_l" => (
None,
None,
SampleLevel::Exact(self.read(ins.operand(4)?, 1, Kind::F)?.expr),
),
"sample_b" => (
None,
None,
SampleLevel::Bias(self.read(ins.operand(4)?, 1, Kind::F)?.expr),
),
"sample_d" => {
let g: Vec<u8> = n.lanes().collect();
let x = self.read_lanes(ins.operand(4)?, &g, Kind::F)?;
let y = self.read_lanes(ins.operand(5)?, &g, Kind::F)?;
(None, None, SampleLevel::Gradient { x: x.expr, y: y.expr })
}
"sample_c" => (
None,
Some(self.read(ins.operand(4)?, 1, Kind::F)?.expr),
SampleLevel::Auto,
),
"sample_c_lz" => (
None,
Some(self.read(ins.operand(4)?, 1, Kind::F)?.expr),
SampleLevel::Zero,
),
"gather4" => (
Some(component(component_of(ins.operand(3)?))),
None,
SampleLevel::Zero,
),
"gather4_c" => (
Some(component(component_of(ins.operand(3)?))),
Some(self.read(ins.operand(4)?, 1, Kind::F)?.expr),
SampleLevel::Zero,
),
other => return self.refuse(format!("{other} is not lifted")),
};
let image = self.b.global_ptr(image);
let sampler = self.b.global_ptr(sampler);
let expr = self.b.app(E::ImageSample {
image,
sampler,
gather,
coordinate: coordinate.expr,
array_index,
offset,
level,
depth_ref,
clamp_to_edge: false,
});
let mut result = Val {
expr,
kind: Kind::F,
width: if t.depth && gather.is_none() {
Width::One
} else {
Width::Four
},
};
if result.width == Width::One {
let zero = self.b.lit_f(0.0);
let one = self.b.lit_f(1.0);
result = if depth_ref.is_some() {
self.b.splat(result, Width::Four)
} else {
self.b.compose(&[result, zero, zero, one])
};
}
let picked = self.pick(result, ins.operand(2)?, mask);
self.write(ins.operand(0)?, picked, ins.saturate)
}
pub(super) fn load(&mut self, ins: &Instruction) -> Result<()> {
let slot = self.slot_of(ins, 2, &[dxbc_reader::decode::Register::Texture])?;
let mask = self.dest_mask(ins.operand(0)?);
if let Some((g, buffer)) = self.resources.buffers.get(&slot).copied() {
let Buffer::Typed = buffer else {
return self.refuse("ld on a raw or structured buffer");
};
self.notes_typed_buffer();
let address = self.read(ins.operand(1)?, 1, Kind::U)?;
let row = self.guarded_load(g, address, None, Kind::U, Width::Four);
let picked = self.pick(row, ins.operand(2)?, mask);
return self.write(ins.operand(0)?, picked, false);
}
let (image, t) = self.texture(slot)?;
let (n, arrayed) = Self::coordinates(&t);
let all = self.read_lanes(ins.operand(1)?, &[0, 1, 2, 3], Kind::I)?;
let cl: Vec<u8> = n.lanes().collect();
let coordinate = self.b.swizzle(all, &cl);
let array_index = arrayed.then(|| self.b.lane(all, n.get()).expr);
let level = (!t.multi).then(|| self.b.lane(all, 3).expr);
let sample = if ins.name() == "ld_ms" {
Some(self.read(ins.operand(3)?, 1, Kind::I)?.expr)
} else {
None
};
let image = self.b.global_ptr(image);
let expr = self.b.app(E::ImageLoad {
image,
coordinate: coordinate.expr,
array_index,
sample,
level,
});
let mut result = Val {
expr,
kind: t.kind,
width: Width::Four,
};
if t.depth {
result.width = Width::One;
let zero = self.b.lit_f(0.0);
let one = self.b.lit_f(1.0);
result.kind = Kind::F;
result = self.b.compose(&[result, zero, zero, one]);
}
let bits = self.b.bitcast(result, Kind::U);
let picked = self.pick(bits, ins.operand(2)?, mask);
self.write(ins.operand(0)?, picked, false)
}
pub(super) fn sample_info(&mut self, ins: &Instruction) -> Result<()> {
if !matches!(ins.operand(1)?.register, dxbc_reader::decode::Register::Texture) {
return self.refuse("the sample count of the rasterizer is the pipeline's, not the program's");
}
let slot = self.index_of(ins.operand(1)?, 0)?.base;
let (image, t) = self.texture(slot)?;
let mask = self.dest_mask(ins.operand(0)?);
let count = if t.multi {
let g = self.b.global_ptr(image);
Val {
expr: self.b.app(E::ImageQuery {
image: g,
query: ImageQuery::NumSamples,
}),
kind: Kind::U,
width: Width::One,
}
} else {
self.b.lit_u(0)
};
let zero = self.b.lit_u(0);
let all = self.b.compose(&[count, zero, zero, zero]);
let result = if ins.control & 1 != 0 {
all
} else {
self.b.convert(all, Kind::F)
};
let bits = self.b.bitcast(result, Kind::U);
let picked = self.pick(bits, ins.operand(1)?, mask);
self.write(ins.operand(0)?, picked, false)
}
pub(super) fn resource_info(&mut self, ins: &Instruction) -> Result<()> {
let slot = self.slot_of(
ins,
2,
&[
dxbc_reader::decode::Register::Texture,
dxbc_reader::decode::Register::Uav,
],
)?;
let (image, t) = if matches!(ins.operand(2)?.register, dxbc_reader::decode::Register::Uav) {
let (g, v) = self.view(slot)?;
(
g,
Texture {
slot,
dim: v.dim,
arrayed: v.arrayed,
multi: true,
kind: v.kind,
depth: false,
},
)
} else {
self.texture(slot)?
};
let mask = self.dest_mask(ins.operand(0)?);
let mip = self.read(ins.operand(1)?, 1, Kind::U)?;
let g = self.b.global_ptr(image);
let dims = match t.dim {
naga::ImageDimension::D1 => Width::One,
naga::ImageDimension::D2 => Width::Two,
_ => Width::Three,
};
let size = Val {
expr: self.b.app(E::ImageQuery {
image: g,
query: ImageQuery::Size {
level: (!t.multi).then_some(mip.expr),
},
}),
kind: Kind::U,
width: dims,
};
let levels = if t.multi {
self.b.lit_u(1)
} else {
let g = self.b.global_ptr(image);
Val {
expr: self.b.app(E::ImageQuery {
image: g,
query: ImageQuery::NumLevels,
}),
kind: Kind::U,
width: Width::One,
}
};
let zero = self.b.lit_u(0);
let mut parts: Vec<Val> = size.width.lanes().map(|i| self.b.lane(size, i)).collect();
if t.arrayed {
let g = self.b.global_ptr(image);
parts.push(Val {
expr: self.b.app(E::ImageQuery {
image: g,
query: ImageQuery::NumLayers,
}),
kind: Kind::U,
width: Width::One,
});
}
while parts.len() < 3 {
parts.push(zero);
}
parts.truncate(3);
parts.push(levels);
let all = self.b.compose(&parts);
let result = match ins.control & 3 {
2 => all,
0 => self.b.convert(all, Kind::F),
_ => {
let f = self.b.convert(all, Kind::F);
let one = self.b.lit_f(1.0);
self.b.binary(Bo::Divide, one, f)
}
};
let picked = self.pick(result, ins.operand(2)?, mask);
self.write(ins.operand(0)?, picked, false)
}
fn load_words(&mut self, g: Handle<GlobalVariable>, word: Val, resource: &Operand, mask: u8) -> Val {
let components: Vec<u8> = lanes_of(mask)
.iter()
.map(|l| match resource.selection {
Selection::Swizzle(s) => super::operands::of_four(s, *l),
Selection::Scalar(c) => c,
_ => 0,
})
.collect();
let mut lanes = Vec::new();
for c in components {
let offset = self.b.lit_u(u32::from(c));
let index = self.b.binary(Bo::Add, word, offset);
lanes.push(self.guarded_load(g, index, None, Kind::U, Width::One));
}
self.b.compose(&lanes)
}
pub(super) fn load_raw(&mut self, ins: &Instruction) -> Result<()> {
if !matches!(ins.operand(2)?.register, dxbc_reader::decode::Register::Texture) {
return self.load_memory(ins, &[ins.operand(1)?], ins.operand(2)?);
}
let slot = self.index_of(ins.operand(2)?, 0)?.base;
let Some((g, Buffer::Words { .. })) = self.resources.buffers.get(&slot).copied() else {
return self.refuse(format!("t{slot} is not a raw buffer"));
};
let mask = self.dest_mask(ins.operand(0)?);
let offset = self.read(ins.operand(1)?, 1, Kind::U)?;
let two = self.b.lit_u(2);
let word = self.b.binary(Bo::ShiftRight, offset, two);
let result = self.load_words(g, word, ins.operand(2)?, mask);
self.write(ins.operand(0)?, result, false)
}
pub(super) fn load_structured(&mut self, ins: &Instruction) -> Result<()> {
if !matches!(ins.operand(3)?.register, dxbc_reader::decode::Register::Texture) {
return self.load_memory(ins, &[ins.operand(1)?, ins.operand(2)?], ins.operand(3)?);
}
let slot = self.index_of(ins.operand(3)?, 0)?.base;
let Some((g, Buffer::Words { stride })) = self.resources.buffers.get(&slot).copied() else {
return self.refuse(format!("t{slot} is not a structured buffer"));
};
let mask = self.dest_mask(ins.operand(0)?);
let element = self.read(ins.operand(1)?, 1, Kind::U)?;
let offset = self.read(ins.operand(2)?, 1, Kind::U)?;
let stride = self.b.lit_u(stride);
let base = self.b.binary(Bo::Multiply, element, stride);
let byte = self.b.binary(Bo::Add, base, offset);
let two = self.b.lit_u(2);
let word = self.b.binary(Bo::ShiftRight, byte, two);
let result = self.load_words(g, word, ins.operand(3)?, mask);
self.write(ins.operand(0)?, result, false)
}
fn view(&self, slot: u32) -> Result<(Handle<GlobalVariable>, View)> {
self.resources
.views
.get(&slot)
.copied()
.map_or_else(|| self.refuse(format!("u{slot} is not a typed texture view")), Ok)
}
pub(super) fn view_address(&mut self, op: &Operand, v: &View) -> Result<(Val, Option<naga::Handle<E>>)> {
let n = match v.dim {
naga::ImageDimension::D1 => Width::One,
naga::ImageDimension::D2 => Width::Two,
_ => Width::Three,
};
let lanes: Vec<u8> = (0..n.get().saturating_add(u8::from(v.arrayed))).collect();
let all = self.read_lanes(op, &lanes, Kind::U)?;
let coordinate = if all.width == n {
all
} else {
let c: Vec<u8> = n.lanes().collect();
self.b.swizzle(all, &c)
};
let layer = v.arrayed.then(|| self.b.lane(all, n.get()).expr);
Ok((coordinate, layer))
}
pub(super) fn store_view(&mut self, ins: &Instruction) -> Result<()> {
let slot = self.slot_of(ins, 0, &[dxbc_reader::decode::Register::Uav])?;
let (global, v) = self.view(slot)?;
if self.dest_mask(ins.operand(0)?) != 15 {
return self.refuse("a store to a view with a partial write mask");
}
let (coordinate, array_index) = self.view_address(ins.operand(1)?, &v)?;
let value = self.read_lanes(ins.operand(2)?, &[0, 1, 2, 3], v.kind)?;
let image = self.b.global_ptr(global);
self.b.push(naga::Statement::ImageStore {
image,
coordinate: coordinate.expr,
array_index,
value: value.expr,
});
Ok(())
}
pub(super) fn load_view(&mut self, ins: &Instruction) -> Result<()> {
let slot = self.slot_of(ins, 2, &[dxbc_reader::decode::Register::Uav])?;
let (global, v) = self.view(slot)?;
let mask = self.dest_mask(ins.operand(0)?);
let (coordinate, array_index) = self.view_address(ins.operand(1)?, &v)?;
let image = self.b.global_ptr(global);
let expr = self.b.app(E::ImageLoad {
image,
coordinate: coordinate.expr,
array_index,
sample: None,
level: None,
});
let result = Val {
expr,
kind: v.kind,
width: Width::Four,
};
let bits = self.b.bitcast(result, Kind::U);
let picked = self.pick(bits, ins.operand(2)?, mask);
self.write(ins.operand(0)?, picked, false)
}
fn notes_typed_buffer(&mut self) {
self.notes.insert(
"a typed buffer view is read as four 32-bit lanes per element: converting from the view's format is the \
binding's"
.into(),
);
}
}