Skip to content

Commit 55b3368

Browse files
committed
[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 101ea38 commit 55b3368

File tree

9 files changed

+805
-120
lines changed

9 files changed

+805
-120
lines changed

naga-cli/src/bin/naga.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ fn run() -> anyhow::Result<()> {
509509
let missing = match Path::new(path).extension().and_then(|ex| ex.to_str()) {
510510
Some("wgsl") => C::CLIP_DISTANCE | C::CULL_DISTANCE,
511511
Some("metal") => C::CULL_DISTANCE | C::TEXTURE_EXTERNAL,
512+
Some("hlsl") => C::empty(),
512513
_ => C::TEXTURE_EXTERNAL,
513514
};
514515
caps & !missing

naga/src/back/hlsl/help.rs

Lines changed: 368 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
@@ -330,6 +330,41 @@ where
330330

331331
pub type DynamicStorageBufferOffsetsTargets = alloc::collections::BTreeMap<u32, OffsetsBindTarget>;
332332

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

@@ -376,6 +411,11 @@ pub struct Options {
376411
serde(deserialize_with = "deserialize_storage_buffer_offsets")
377412
)]
378413
pub dynamic_storage_buffer_offsets_targets: DynamicStorageBufferOffsetsTargets,
414+
#[cfg_attr(
415+
feature = "deserialize",
416+
serde(deserialize_with = "deserialize_external_texture_binding_map")
417+
)]
418+
pub external_texture_binding_map: ExternalTextureBindingMap,
379419
/// Should workgroup variables be zero initialized (by polyfilling)?
380420
pub zero_initialize_workgroup_memory: bool,
381421
/// Should we restrict indexing of vectors, matrices and arrays?
@@ -396,6 +436,7 @@ impl Default for Options {
396436
sampler_buffer_binding_map: alloc::collections::BTreeMap::default(),
397437
push_constants_target: None,
398438
dynamic_storage_buffer_offsets_targets: alloc::collections::BTreeMap::new(),
439+
external_texture_binding_map: ExternalTextureBindingMap::default(),
399440
zero_initialize_workgroup_memory: true,
400441
restrict_indexing: true,
401442
force_loop_bounding: true,
@@ -420,6 +461,29 @@ impl Options {
420461
None => Err(EntryPointError::MissingBinding(*res_binding)),
421462
}
422463
}
464+
465+
fn resolve_external_texture_resource_binding(
466+
&self,
467+
res_binding: &crate::ResourceBinding,
468+
) -> Result<ExternalTextureBindTarget, EntryPointError> {
469+
match self.external_texture_binding_map.get(res_binding) {
470+
Some(target) => Ok(*target),
471+
None if self.fake_missing_bindings => {
472+
let fake = BindTarget {
473+
space: res_binding.group as u8,
474+
register: res_binding.binding,
475+
binding_array_size: None,
476+
dynamic_storage_buffer_offsets_index: None,
477+
restrict_indexing: false,
478+
};
479+
Ok(ExternalTextureBindTarget {
480+
planes: [fake, fake, fake],
481+
params: fake,
482+
})
483+
}
484+
None => Err(EntryPointError::MissingBinding(*res_binding)),
485+
}
486+
}
423487
}
424488

425489
/// Reflection info for entry point names.
@@ -474,6 +538,7 @@ enum WrappedType {
474538
ArrayLength(help::WrappedArrayLength),
475539
ImageSample(help::WrappedImageSample),
476540
ImageQuery(help::WrappedImageQuery),
541+
ImageLoad(help::WrappedImageLoad),
477542
ImageLoadScalar(crate::Scalar),
478543
Constructor(help::WrappedConstructor),
479544
StructMatrixAccess(help::WrappedStructMatrixAccess),

0 commit comments

Comments
 (0)