Skip to content

Commit f9cbe75

Browse files
jamienicoljimblandy
authored andcommitted
[naga hlsl-out] Implement external texture support
This adds HLSL backend support for `ImageClass::External` (ie WGSL's `external_texture` texture type). For each external texture global variable in the IR, we declare 3 `Texture2D` globals as well as a `cbuffer` for the params. The additional bindings required by these are found in the newly added `external_texture_binding_map`. Unique names for each can be obtained using `NameKey::ExternalTextureGlobalVariable`. For functions that contain ImageQuery::Size, ImageLoad, or ImageSample expressions for external textures, ensure we have generated wrapper functions for those expressions. When emitting code for the expressions themselves, simply insert a call to the wrapper function. For size queries, we return the value provided in the params struct. If that value is [0, 0] then we query the size of the plane 0 texture and return that. For load and sample, we sample the textures based on the number of planes specified in the params struct. If there is more than one plane we additionally perform YUV to RGB conversion using the provided matrix. Unfortunately HLSL does not allow structs to contain textures, meaning we are unable to wrap the 3 textures and params struct variables in a single variable that can be passed around. For our wrapper functions we therefore ensure they take the three textures and the params as consecutive arguments. Likewise, when declaring user-defined functions with external texture arguments, we expand the single external texture argument into 4 consecutive arguments. (Using NameKey::ExternalTextureFunctionArgument to ensure unique names for each.) Thankfully external textures can only be used as either global variables or function arguments. This means we only have to handle the `Expression::GlobalVariable` and `Expression::FunctionArgument` cases of `write_expr()`. Since in both cases we know the external texture can only be an argument to either a user-defined function or one of our wrapper functions, we can simply emit the names of the variables for each three textures and the params struct in a comma-separated list.
1 parent 26e1224 commit f9cbe75

File tree

9 files changed

+802
-120
lines changed

9 files changed

+802
-120
lines changed

naga-cli/src/bin/naga.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ fn run() -> anyhow::Result<()> {
539539
let missing = match Path::new(path).extension().and_then(|ex| ex.to_str()) {
540540
Some("wgsl") => C::CLIP_DISTANCE | C::CULL_DISTANCE,
541541
Some("metal") => C::CULL_DISTANCE | C::TEXTURE_EXTERNAL,
542+
Some("hlsl") => C::empty(),
542543
_ => C::TEXTURE_EXTERNAL,
543544
};
544545
caps & !missing

naga/src/back/hlsl/help.rs

Lines changed: 369 additions & 90 deletions
Large diffs are not rendered by default.

naga/src/back/hlsl/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ pub const RESERVED: &[&str] = &[
826826
super::writer::INSERT_BITS_FUNCTION,
827827
super::writer::SAMPLER_HEAP_VAR,
828828
super::writer::COMPARISON_SAMPLER_HEAP_VAR,
829+
super::writer::SAMPLE_EXTERNAL_TEXTURE_FUNCTION,
829830
super::writer::ABS_FUNCTION,
830831
super::writer::DIV_FUNCTION,
831832
super::writer::MOD_FUNCTION,
@@ -834,6 +835,7 @@ pub const RESERVED: &[&str] = &[
834835
super::writer::F2U32_FUNCTION,
835836
super::writer::F2I64_FUNCTION,
836837
super::writer::F2U64_FUNCTION,
838+
super::writer::IMAGE_LOAD_EXTERNAL_FUNCTION,
837839
super::writer::IMAGE_SAMPLE_BASE_CLAMP_TO_EDGE_FUNCTION,
838840
];
839841

naga/src/back/hlsl/mod.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,41 @@ where
335335

336336
pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap<u32, OffsetsBindTarget>;
337337

338+
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
339+
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
340+
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
341+
pub struct ExternalTextureBindTarget {
342+
pub planes: [BindTarget; 3],
343+
pub params: BindTarget,
344+
}
345+
346+
#[cfg(any(feature = "serialize", feature = "deserialize"))]
347+
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
348+
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
349+
struct ExternalTextureBindingMapSerialization {
350+
resource_binding: crate::ResourceBinding,
351+
bind_target: ExternalTextureBindTarget,
352+
}
353+
354+
#[cfg(feature = "deserialize")]
355+
fn deserialize_external_texture_binding_map<'de, D>(
356+
deserializer: D,
357+
) -> Result<ExternalTextureBindingMap, D::Error>
358+
where
359+
D: serde::Deserializer<'de>,
360+
{
361+
use serde::Deserialize;
362+
363+
let vec = Vec::<ExternalTextureBindingMapSerialization>::deserialize(deserializer)?;
364+
let mut map = ExternalTextureBindingMap::default();
365+
for item in vec {
366+
map.insert(item.resource_binding, item.bind_target);
367+
}
368+
Ok(map)
369+
}
370+
pub type ExternalTextureBindingMap =
371+
alloc::collections::BTreeMap<crate::ResourceBinding, ExternalTextureBindTarget>;
372+
338373
/// Shorthand result used internally by the backend
339374
type BackendResult = Result<(), Error>;
340375

@@ -381,6 +416,11 @@ pub struct Options {
381416
serde(deserialize_with = "deserialize_storage_buffer_offsets")
382417
)]
383418
pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets,
419+
#[cfg_attr(
420+
feature = "deserialize",
421+
serde(deserialize_with = "deserialize_external_texture_binding_map")
422+
)]
423+
pub external_texture_binding_map: ExternalTextureBindingMap,
384424
/// Should workgroup variables be zero initialized (by polyfilling)?
385425
pub zero_initialize_workgroup_memory: bool,
386426
/// Should we restrict indexing of vectors, matrices and arrays?
@@ -401,6 +441,7 @@ impl Default for Options {
401441
sampler_buffer_binding_map: alloc::collections::BTreeMap::default(),
402442
push_constants_target: None,
403443
dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(),
444+
external_texture_binding_map: ExternalTextureBindingMap::default(),
404445
zero_initialize_workgroup_memory: true,
405446
restrict_indexing: true,
406447
force_loop_bounding: true,
@@ -425,6 +466,29 @@ impl Options {
425466
None => Err(EntryPointError::MissingBinding(*res_binding)),
426467
}
427468
}
469+
470+
fn resolve_external_texture_resource_binding(
471+
&self,
472+
res_binding: &crate::ResourceBinding,
473+
) -> Result<ExternalTextureBindTarget, EntryPointError> {
474+
match self.external_texture_binding_map.get(res_binding) {
475+
Some(target) => Ok(*target),
476+
None if self.fake_missing_bindings => {
477+
let fake = BindTarget {
478+
space: res_binding.group as u8,
479+
register: res_binding.binding,
480+
binding_array_size: None,
481+
dynamic_storage_buffer_offsets_index: None,
482+
restrict_indexing: false,
483+
};
484+
Ok(ExternalTextureBindTarget {
485+
planes: [fake, fake, fake],
486+
params: fake,
487+
})
488+
}
489+
None => Err(EntryPointError::MissingBinding(*res_binding)),
490+
}
491+
}
428492
}
429493

430494
/// Reflection info for entry point names.
@@ -479,6 +543,7 @@ enum WrappedType {
479543
ArrayLength(help::WrappedArrayLength),
480544
ImageSample(help::WrappedImageSample),
481545
ImageQuery(help::WrappedImageQuery),
546+
ImageLoad(help::WrappedImageLoad),
482547
ImageLoadScalar(crate::Scalar),
483548
Constructor(help::WrappedConstructor),
484549
StructMatrixAccess(help::WrappedStructMatrixAccess),

0 commit comments

Comments
 (0)