Skip to content

[naga hlsl-out] HLSL implemention of external textures #7826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: trunk
Choose a base branch
from

Conversation

jamienicol
Copy link
Contributor

Connections
Depends on #7822
Part of #4386

Description
HLSL implementation of external textures

Testing
Snapshot tests. Manual testing.

Squash or Rebase?
Rebase after manually squashing any fixup commits that arise from review

Checklist

  • Run cargo fmt.
  • Run taplo format.
  • Run cargo clippy --tests. If applicable, add:
    • --target wasm32-unknown-unknown
  • Run cargo xtask test to run tests.
  • If this contains user-facing changes, add a CHANGELOG.md entry.

Still not changelog worthy but getting close!

@jamienicol jamienicol requested a review from a team as a code owner June 18, 2025 16:30
@jamienicol jamienicol force-pushed the hlsl-texture-external branch 4 times, most recently from 636c40c to 78d8574 Compare June 23, 2025 12:23
@jamienicol jamienicol force-pushed the hlsl-texture-external branch from 78d8574 to 681f342 Compare June 27, 2025 13:16
@jamienicol
Copy link
Contributor Author

Rebased and updated textureLoad implementation to use load_transform parameter, as described here

@jamienicol jamienicol force-pushed the hlsl-texture-external branch from 681f342 to bb06952 Compare July 18, 2025 09:10
@jimblandy jimblandy force-pushed the hlsl-texture-external branch from bb06952 to f9cbe75 Compare July 22, 2025 22:26
@jimblandy
Copy link
Member

Rebased on trunk since #7822 is landed.

During wgsl lowering, if we encounter an external texture type then
generate the `ExternalTextureParams` struct. This will be required by
most Naga backends to implement external textures.

This type is not actually used by wgsl-in or the IR. However,
generating it in Naga IR ensures tricky details such as member
alignment are handled for us.

wgsl-out must ensure it does *not* generate code for this type, as it
handles external textures natively.
…ture

Adds new `NameKey` variants `ExternalTextureGlobalVariable` and
`ExternalTextureFunctionArgument`, like their non-external-texture
cousins but additionally keyed by either being a specific plane index
or params buffer.

For each external texture global variable or function argument reserve
additional names for 3 planes and the params buffer. For Naga backends
which must represent external textures as multiple variables/arguments,
this will allow them to uniquely name each one.
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.
@jimblandy jimblandy force-pushed the hlsl-texture-external branch from f9cbe75 to 2084c01 Compare July 22, 2025 22:59
@jimblandy
Copy link
Member

jimblandy commented Jul 22, 2025

Rebased on trunk since #7823 has landed, and regenerated snapshot output.

@jimblandy
Copy link
Member

jimblandy commented Jul 23, 2025

Not a request for a change, just some thoughts:

Does NagaExternalTextureParams really need to be in the IR? It's pretty strange to have a type in there that the code never uses, and that is used only by some backends and must be ignored by others. This seems like something that's going to break easily or confuse people, because it certainly isn't a typical use of IR types. For example, it might seem reasonable to someone that compaction should not treat special types as axiomatically rooted - but changing compaction to remove apparently unused special types would break whoever's using external_texture_params.

In the long term, I'm wondering if Naga doesn't need two IRs, one designed around front-end needs, and a second around backend needs, with a lowering pass between them. The lowering code could be shared by backends, but it would take parameters indicating which transformations the backends needed. One such transformation might be, "lower external textures to ordinary textures and parameter blocks". Others might be the HLSL access rewriting, or the unrestricted pointer parameters rewrites.

@jimblandy
Copy link
Member

jimblandy commented Jul 23, 2025

@jamienicol Do the doc changes in 7741fba 611c3c9 look correct?

module
.special_types
.predeclared_types
.values()
.any(|t| *t == handle)
.any(|t| *t == ty)
|| Some(ty) == module.special_types.external_texture_params
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, not requesting a change, but this is weird. People will wonder why all the other special_types aren't listed here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the other special_types should in fact be listed here too? Looking at the input wgsl snapshot tests, eg here, the special types aren't actually declared in the source (they are in a comment just to show what they would look like). The respective TOML files specify not to run WGSL-out on them, but presumably if they did we would indeed want to skip emitting the declarations. Otherwise when parsing again with wgsl-in we would have duplicated declarations?

@jimblandy jimblandy force-pushed the hlsl-texture-external branch from 7741fba to 611c3c9 Compare July 23, 2025 02:12
Copy link
Member

@jimblandy jimblandy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first commit, "[naga] Generate special type for external texture params buffer", looks good to me.

@jimblandy
Copy link
Member

@jamienicol In 17f8bf8 I was trying to reduce the repetition in Namer::reset, but I'm not sure I succeeded. If it doesn't seem like a clear improvement to you, we can drop that fixup!.

Copy link
Member

@jimblandy jimblandy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second commit, "[naga] Reserve names for each plane and params buffer of external texture", looks good to me.

Copy link
Member

@jimblandy jimblandy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message for the third commit - the long one - is superb, but it belongs in the code, not in the commit message.

@jimblandy
Copy link
Member

Sorry - still reviewing that final commit.

@jamienicol
Copy link
Contributor Author

jamienicol commented Jul 23, 2025

@jamienicol Do the doc changes in 7741fba 611c3c9 look correct?

They look great, modulo a typo which I've fixed in 5f90c8e

@jamienicol
Copy link
Contributor Author

The log message for the third commit - the long one - is superb, but it belongs in the code, not in the commit message.

I believe the important contents of it is all in the code, spread around various places as appropriate. But a single high-level overview of how external textures are implemented for HLSL in the code somewhere doesn't sound like a bad thing. Any suggestions for where to put this?

@jamienicol
Copy link
Contributor Author

jamienicol commented Jul 23, 2025

Does NagaExternalTextureParams really need to be in the IR? It's pretty strange to have a type in there that the code never uses, and that is used only by some backends and must be ignored by others. This seems like something that's going to break easily or confuse people, because it certainly isn't a typical use of IR types. For example, it might seem reasonable to someone that compaction should not treat special types as axiomatically rooted - but changing compaction to remove apparently unused special types would break whoever's using external_texture_params.

No, it doesn't need to be in the IR. The benefit of it being there is that we don't have to duplicate its declaration in each backend, and it takes care of fiddly bits such as adding padding, packing a mat3x2 as 3x vec2 in HLSL, etc, for us. When I stumbled across the existing special_types for ray queries etc it seemed conceptually similar-ish to me.

(You're right to point out a confusing difference in how the code as-is handles my params struct vs existing special types, which I've replied to above.)

I'm afraid I don't know what "axiomatically rooted" means. But FWIW our tests would explode if somebody made that change to compaction and it would be quickly caught in CI.

None of this is required, and if you think the weirdness outweighs the benefits I am more than happy to emit the declarations in the backends instead.

@jamienicol
Copy link
Contributor Author

Also pushed 84d4f0e which makes your as_deref() change for NameKey::ExternalTextureFunctionArgument to NameKey::ExternalTextureGlobalVariable as well.

@jamienicol
Copy link
Contributor Author

jamienicol commented Jul 23, 2025

Pushed 678de10 to add a comment saying why we don't want to compact away the params special type

And d5a4687 adding a module-level comment

@jamienicol jamienicol force-pushed the hlsl-texture-external branch from 16c971e to d5a4687 Compare July 23, 2025 16:10
Change `unimplemented!` for External images to `unreachable!`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants