Skip to content

[naga] Support textureSampleBaseClampToEdge() for texture2d #7709

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 6 commits into
base: trunk
Choose a base branch
from

Conversation

jamienicol
Copy link
Contributor

@jamienicol jamienicol commented May 21, 2025

Adds a new flag to the IR indicating when image sample coordinates are to be clamped. Adds wgsl-in support for parsing and lowering to IR. Validation ensures this flag is only used when sampling a 2D non-arrayed sampled texture, without offset, gather, or depth comparison. This matches the WGSL requirements, with the exception of supporting texture_external textures, which will follow in a later patch.

SPIRV, HLSL, and Metal backends are supported so far, with GLSL left for a follow up. (In GLSL the texture will simply be sampled without the coordinates being clamped.)

It may seem unfortunate to have to handle this separately for each backend, and indeed it would have been possible to implement this simply in the WGSL frontend. However, future patches will add support for using textureSampleBaseClampToEdge() with external textures, which will actually have to be handled by each backend. This patch is laying the groundwork for that.

Connections
Part of #4386
Fixes #7539

Testing
Added snapshot test

Makes these CTS tests pass

Squash or Rebase?

Rebase

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.

@jamienicol jamienicol force-pushed the texture-sample-base-clamp-to-edge branch from b25453c to 466e9b8 Compare May 27, 2025 08:34
@jamienicol jamienicol requested review from crowlKats and a team as code owners May 27, 2025 08:34
@jamienicol jamienicol force-pushed the texture-sample-base-clamp-to-edge branch 2 times, most recently from 133ad63 to b27265b Compare May 27, 2025 14:51
@jamienicol jamienicol force-pushed the texture-sample-base-clamp-to-edge branch 2 times, most recently from 6df8c7c to 9103208 Compare June 4, 2025 15:06
@jamienicol jamienicol force-pushed the texture-sample-base-clamp-to-edge branch from 9103208 to 05da8d1 Compare June 10, 2025 07:29
@cwfitzgerald cwfitzgerald changed the title [naga] Support textureSampleBaseClampToEdge() for texture2d [naga] Support textureSampleBaseClampToEdge() for texture2d Jun 11, 2025
jamienicol and others added 4 commits June 11, 2025 12:55
Adds a new flag to the IR indicating when image sample coordinates are
to be clamped. Adds wgsl-in support for parsing and lowering to
IR. Validation ensures this flag is only used when sampling a 2D
non-arrayed sampled texture, without offset, gather, or depth
comparison. This matches the WGSL requirements, with the exception of
supporting `texture_external` textures, which will follow in a later
patch.

SPIRV, HLSL, and Metal backends are supported so far, with GLSL left
for a follow up. (In GLSL the texture will simply be sampled without
the coordinates being clamped.)

It may seem unfortunate to have to handle this separately for each
backend, and indeed it would have been possible to implement this simply
in the WGSL frontend. However, future patches will add support for using
textureSampleBaseClampToEdge() with external textures, which will
actually have to be handled by each backend. This patch is laying the
groundwork for that.
This doesn't do anything yet, but unblocks running some CTS tests.
@jimblandy jimblandy force-pushed the texture-sample-base-clamp-to-edge branch from 05da8d1 to b71a0e2 Compare June 11, 2025 20:09
@jimblandy
Copy link
Member

@jamienicol This is a bit dumb, but does b71a0e2 look okay to you? The MSL backend has no obligations here, since it's a validation error, but I just felt it would be nicer for the MSL backend to die instead of quietly misbehaving.

@jamienicol
Copy link
Contributor Author

@jimblandy I don't mind it at all. I wouldn't want to go hugely out of the way to repeat validation in the backends, but that is simple enough.

Personally I'd prefer an assertion to returning an error: if we hit that case there's a bug somewhere and I'd rather we know about it via crash reports than users just suffering in silence. But I might be in the minority of preferring crashes to errors.

@jamienicol
Copy link
Contributor Author

Any reason to limit that to MSL rather than doing it for all backends? or was that just as an example?

Copy link
Member

@ErichDonGubler ErichDonGubler left a comment

Choose a reason for hiding this comment

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

Looks like @jimblandy is ramping up on review now, so I won't drive a full review myself, but I'll comment on some things I noticed before I left on vacation and now.

Comment on lines 3170 to 3191
Expression::ImageSample {
coordinate:
crate::SampleCoordinate {
expr: coordinate,
clamp_to_edge: true,
},
image,
sampler,
..
} => {
Copy link
Member

Choose a reason for hiding this comment

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

thought: It's very unfortunate that we simply don't use the other fields in the case where clamp_to_edge is true. This feels like an appropriate place to use an enum, but I can only imagine this will rewrite quite a bit of code for a questionable benefit.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, this is the same thing I was bringing up here: #7709 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ErichDonGubler Jim and I had previously discussed how to enforce valid combinations of ImageSample flags using the type system. (eg using enums). It's a problem that goes beyond just clamp_to_edge. We didn't think it was worth it in the end. I think Jim's suggestion above is a happy middle ground.

@@ -3042,9 +3042,11 @@ impl<'a, W: Write> Writer<'a, W> {
// The space here isn't required but it helps with readability
write!(self.out, ", ")?;

// TODO: handle coordinate.clamp_to_edge
Copy link
Member

Choose a reason for hiding this comment

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

issue: This shouldn't remain as a TODO item; this should graduate to an issue before getting merged, if it's intended to outlast this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed #7791. Linked to it in a comment.

Comment on lines 666 to 669
impl Drop for GPUExternalTexture {
fn drop(&mut self) {}
}

Copy link
Member

Choose a reason for hiding this comment

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

nitpick(non-blocking): This Drop implementation shouldn't be necessary at all, AFAIK, with the current behavior you have. I might be wrong in the case of Deno's expected trait implementations, but I don't think so?

If you're going to fill it out later, though, then 🤷🏻‍♂️.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I swear I thought I saw other empty drop impls and just copied that. But looking now I can't find them.

I think long term we would expect GPUExternalTexture to impl Drop. And to call into wgpu's (yet to be added) Global::external_texture_drop(). But actually implementing external texture in deno is something I wasn't planning to do so in the short term.

I will remove the impl for now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in cd4d2d9

Comment on lines +261 to +263
WrappedImageSample {
clamp_to_edge: true,
} => {
Copy link
Member

Choose a reason for hiding this comment

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

nitpick: Is there a reason not to destructure and then do if clamp_to_edge?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A subsequent patch will take ImageClass in to account to. So it'll look like:

match sample {
    WrappedImageSample {
        clamp_to_edge: true,
        class: ImageClass::External,
    } => {
        ...
    }
    WrappedImageSample {
        clamp_to_edge: true,
        class: ImageClass::Sampled { kind: ScalarKind::Float, multi: false},
    } => {
        ...
    }
    _ => {}
}

would you instead propose something like this?

let WrappedImageSample { clamp_to_edge, class } = sample;
match (clamp_to_edge, class) {
    (true, ImageClass::External) => {
        ...
    }
    (true, ImageClass::Sampled { kind: ScalarKind::Float, multi: false}) => {
        ...
    }
    _ => {}
}

If so, no complaints from me if you think that's more conventional

Copy link
Member

Choose a reason for hiding this comment

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

Ah, if you're going to add another match arm, I don't think this is a worthy nitpick. I was going to suggest that simply use clamp_to_edge, but if you're adding class to the mix, I think it's more a question of taste than what's more idiomatic.

I think it's easier to read what you've speculated as my proposal, but if the match gets particularly big, it might be nice to have the redundant member names. Up to you!

ErichDonGubler

This comment was marked as duplicate.

@jimblandy
Copy link
Member

Any reason to limit that to MSL rather than doing it for all backends? or was that just as an example?

It'd make sense for all backends.

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.

Thoughts so far:

writeln!(self.out, "{l1}{NAMESPACE}::float2 half_texel = 0.5 / {NAMESPACE}::float2(tex.get_width(0u), tex.get_height(0u));")?;
writeln!(
self.out,
"{l1}return tex.sample(samp, {NAMESPACE}::clamp(coords, half_texel, 1.0 - half_texel), 0.0);"
Copy link
Member

Choose a reason for hiding this comment

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

It seems to me this will not always sample the base mip level, as textureSampleBaseClampToEdge requires. Should we also pass {NAMESPACE}::level(0) before the 0.0 offset?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need an offset so the 0.0 is supposed to be the level. But indeed I forgot to wrap it in {NAMESPACE}::level()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 2fb382c

Comment on lines 3170 to 3191
Expression::ImageSample {
coordinate:
crate::SampleCoordinate {
expr: coordinate,
clamp_to_edge: true,
},
image,
sampler,
..
} => {
Copy link
Member

Choose a reason for hiding this comment

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

I agree, this is the same thing I was bringing up here: #7709 (comment)

@jamienicol
Copy link
Contributor Author

@jamienicol This is a bit dumb, but does b71a0e2 look okay to you? The MSL backend has no obligations here, since it's a validation error, but I just felt it would be nicer for the MSL backend to die instead of quietly misbehaving.

Did the same thing for HLSL in 2fb382c. Slightly different approach for SPIRV as the implementation is different. In theory it should be able to handle other sample flags, except for the level which must be zero. So I've added a check for that alone - I don't think the backends should be in the business of repeating validation in its entirety - rather they should make a reasonable effort to ensure their own constraints are satisfied.

@jimblandy
Copy link
Member

I don't think the backends should be in the business of repeating validation in its entirety - rather they should make a reasonable effort to ensure their own constraints are satisfied.

Yes, 100%. Backends are allowed to panic and misbehave when validation is buggy. My wanting to tighten up these matches was just opportunistic: it seemed like a small change might save someone some debugging time in he future.

Copy link
Collaborator

@crowlKats crowlKats left a comment

Choose a reason for hiding this comment

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

Deno part LGTM

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.

[wgsl-in] textureSampleBaseClampToEdge not present as a built-in
5 participants