Skip to content

Commit

Permalink
Implement ray query candidate intersection generation and confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
kvark committed Feb 3, 2025
1 parent 4e7d892 commit 3efa0f7
Show file tree
Hide file tree
Showing 19 changed files with 250 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Bottom level categories:
#### Naga

- Support @must_use attribute on function declarations. By @turbocrime in [#6801](https://github.com/gfx-rs/wgpu/pull/6801).
- Support for generating the candidate intersections from AABB geometry, and confirming the hits. By @kvark in [#7047](https://github.com/gfx-rs/wgpu/pull/7047).

### Changes

Expand Down
36 changes: 25 additions & 11 deletions etc/specs/ray_tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

🧪Experimental🧪

`wgpu` supports an experimental version of ray tracing which is subject to change. The extensions allow for acceleration structures to be created and built (with
`wgpu` supports an experimental version of ray tracing which is subject to change. The extensions allow for acceleration structures to be created and built (with
`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE` enabled) and interacted with in shaders. Currently `naga` only supports ray queries
(accessible with `Features::EXPERIMENTAL_RAY_QUERY` enabled in wgpu).

**Note**: The features documented here may have major bugs in them and are expected to be subject
to breaking changes, suggestions for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040).
Large changes may mean that this documentation may be out of date.

***This is not*** an introduction to raytracing, and assumes basic prior knowledge, to look at the fundamentals look at
***This is not*** an introduction to raytracing, and assumes basic prior knowledge, to look at the fundamentals look at
an [introduction](https://developer.nvidia.com/blog/introduction-nvidia-rtx-directx-ray-tracing/).

## `wgpu`'s raytracing API:

The documentation and specific details of the functions and structures provided
can be found with their definitions.
can be found with their definitions.

A [`Blas`] can be created with [`Device::create_blas`].
A [`Blas`] can be created with [`Device::create_blas`].
A [`Tlas`] can be created with [`Device::create_tlas`].

Unless one is planning on using the unsafe building API (not recommended for beginners) a [`Tlas`] should be put inside
Expand Down Expand Up @@ -57,12 +57,21 @@ rayQueryInitialize(rq: ptr<function, ray_query>, acceleration_structure: acceler
// - The hit is considered `Candidate` if this function returns true, and the hit is considered `Committed` if
// this function returns false.
// - A `Candidate` intersection interrupts the ray traversal.
// - A `Candidate` intersection may happen anywhere along the ray, it should not be relied on to give the closest hit. A
// - A `Candidate` intersection may happen anywhere along the ray, it should not be relied on to give the closest hit. A
// `Candidate` intersection is to allow the user themselves to decide if that intersection is valid*. If one wants to get
// the closest hit a `Committed` intersection should be used.
// - Calling this function multiple times will cause the ray traversal to continue if it was interrupted by a `Candidate`
// intersection.
rayQueryProceed(rq: ptr<function, ray_query>) -> bool`
rayQueryProceed(rq: ptr<function, ray_query>) -> bool
// - Generates a hit from procedural geometry at a particular distance.
rayQueryGenerateIntersection(hit_t: f32)
// - Commits a hit from triangular non-opaque geometry.
rayQueryConfirmIntersection()
// - Aborts the query.
rayQueryTerminate()
// - Returns intersection details about a hit considered `Committed`.
rayQueryGetCommittedIntersection(rq: ptr<function, ray_query>) -> RayIntersection
Expand All @@ -71,19 +80,24 @@ rayQueryGetCommittedIntersection(rq: ptr<function, ray_query>) -> RayIntersectio
rayQueryGetCandidateIntersection(rq: ptr<function, ray_query>) -> RayIntersection
```

*The API to commit a candidate intersection is not yet implemented but would be possible to be user implemented.

> [!CAUTION]
>
>
> ### ⚠️Undefined behavior ⚠️:
> - Calling `rayQueryGetCommittedIntersection` or `rayQueryGetCandidateIntersection` when `rayQueryProceed` has not been
> called on this ray query since it was initialized (or if the ray query has not been previously initialized).
> - Calling `rayQueryGetCommittedIntersection` when `rayQueryProceed`'s latest return on this ray query is considered
> `Candidate`.
> `Candidate`.
> - Calling `rayQueryGetCandidateIntersection` when `rayQueryProceed`'s latest return on this ray query is considered
> `Committed`.
> - Calling `rayQueryProceed` when `rayQueryInitialize` has not previously been called on this ray query
>
> - Calling `rayQueryGenerateIntersection` on a query with last intersection kind not being
> `RAY_QUERY_INTERSECTION_AABB`,
> - Calling `rayQueryGenerateIntersection` with `hit_t` outside of `RayDesc::t_min .. RayDesc::t_max` range.
> or when `rayQueryProceed`'s latest return on this ray query is not considered `Candidate`.
> - Calling `rayQueryConfirmIntersection` on a query with last intersection kind not being
> `RAY_QUERY_INTERSECTION_TRIANGLE`,
> or when `rayQueryProceed`'s latest return on this ray query is not considered `Candidate`.
>
> *this is only known undefined behaviour, and will be worked around in the future.
```wgsl
Expand Down
7 changes: 7 additions & 0 deletions naga/src/back/dot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ impl StatementGraph {
self.emits.push((id, result));
"RayQueryProceed"
}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
self.dependencies.push((id, hit_t, "hit_t"));
"RayQueryGenerateIntersection"
}
crate::RayQueryFunction::ConfirmIntersection => {
"RayQueryConfirmIntersection"
}
crate::RayQueryFunction::Terminate => "RayQueryTerminate",
}
}
Expand Down
13 changes: 13 additions & 0 deletions naga/src/back/hlsl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2452,7 +2452,20 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
self.write_expr(module, query, func_ctx)?;
writeln!(self.out, ".Proceed();")?;
}
RayQueryFunction::GenerateIntersection { hit_t } => {
write!(self.out, "{level}")?;
self.write_expr(module, query, func_ctx)?;
write!(self.out, ".CommitProceduralPrimitiveHit(")?;
self.write_expr(module, hit_t, func_ctx)?;
writeln!(self.out, ");")?;
}
RayQueryFunction::ConfirmIntersection => {
write!(self.out, "{level}")?;
self.write_expr(module, query, func_ctx)?;
writeln!(self.out, ".CommitNonOpaqueTriangleHit();")?;
}
RayQueryFunction::Terminate => {
write!(self.out, "{level}")?;
self.write_expr(module, query, func_ctx)?;
writeln!(self.out, ".Abort();")?;
}
Expand Down
38 changes: 32 additions & 6 deletions naga/src/back/msl/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const RT_NAMESPACE: &str = "metal::raytracing";
const RAY_QUERY_TYPE: &str = "_RayQuery";
const RAY_QUERY_FIELD_INTERSECTOR: &str = "intersector";
const RAY_QUERY_FIELD_INTERSECTION: &str = "intersection";
const RAY_QUERY_MODERN_SUPPORT: bool = false; //TODO
const RAY_QUERY_FIELD_READY: &str = "ready";
const RAY_QUERY_FUN_MAP_INTERSECTION: &str = "_map_intersection_type";

Expand Down Expand Up @@ -3506,16 +3507,41 @@ impl<W: Write> Writer<W> {
self.named_expressions.insert(result, name);
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".{RAY_QUERY_FIELD_READY};")?;
//TODO: actually proceed?

write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = false;")?;
if RAY_QUERY_MODERN_SUPPORT {
write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".?.next();")?;
}
}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
if RAY_QUERY_MODERN_SUPPORT {
write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
write!(self.out, ".?.commit_bounding_box_intersection(")?;
self.put_expression(hit_t, &context.expression, true)?;
writeln!(self.out, ");")?;
} else {
log::warn!("Ray Query GenerateIntersection is not yet supported");
}
}
crate::RayQueryFunction::ConfirmIntersection => {
if RAY_QUERY_MODERN_SUPPORT {
write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".?.commit_triangle_intersection();")?;
} else {
log::warn!("Ray Query ConfirmIntersection is not yet supported");
}
}
crate::RayQueryFunction::Terminate => {
if RAY_QUERY_MODERN_SUPPORT {
write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".?.abort();")?;
}
write!(self.out, "{level}")?;
self.put_expression(query, &context.expression, true)?;
writeln!(self.out, ".{RAY_QUERY_FIELD_INTERSECTION}.abort();")?;
writeln!(self.out, ".{RAY_QUERY_FIELD_READY} = false;")?;
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions naga/src/back/pipeline_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,10 @@ fn adjust_stmt(new_pos: &HandleVec<Expression, Handle<Expression>>, stmt: &mut S
crate::RayQueryFunction::Proceed { ref mut result } => {
adjust(result);
}
crate::RayQueryFunction::GenerateIntersection { ref mut hit_t } => {
adjust(hit_t);
}
crate::RayQueryFunction::ConfirmIntersection => {}
crate::RayQueryFunction::Terminate => {}
}
}
Expand Down
13 changes: 13 additions & 0 deletions naga/src/back/spv/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,19 @@ impl super::Instruction {
instruction
}

pub(super) fn ray_query_generate_intersection(query: Word, hit: Word) -> Self {
let mut instruction = Self::new(Op::RayQueryGenerateIntersectionKHR);
instruction.add_operand(query);
instruction.add_operand(hit);
instruction
}

pub(super) fn ray_query_confirm_intersection(query: Word) -> Self {
let mut instruction = Self::new(Op::RayQueryConfirmIntersectionKHR);
instruction.add_operand(query);
instruction
}

pub(super) fn ray_query_get_intersection(
op: Op,
result_type_id: Word,
Expand Down
13 changes: 13 additions & 0 deletions naga/src/back/spv/ray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,19 @@ impl BlockContext<'_> {
.body
.push(Instruction::ray_query_proceed(result_type_id, id, query_id));
}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
let hit_id = self.cached[hit_t];
block
.body
.push(Instruction::ray_query_generate_intersection(
query_id, hit_id,
));
}
crate::RayQueryFunction::ConfirmIntersection => {
block
.body
.push(Instruction::ray_query_confirm_intersection(query_id));
}
crate::RayQueryFunction::Terminate => {}
}
}
Expand Down
8 changes: 8 additions & 0 deletions naga/src/compact/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ impl FunctionTracer<'_> {
Qf::Proceed { result } => {
self.expressions_used.insert(result);
}
Qf::GenerateIntersection { hit_t } => {
self.expressions_used.insert(hit_t);
}
Qf::ConfirmIntersection => {}
Qf::Terminate => {}
}
}
Expand Down Expand Up @@ -393,6 +397,10 @@ impl FunctionMap {
Qf::Proceed { ref mut result } => {
self.expressions.adjust(result);
}
Qf::GenerateIntersection { ref mut hit_t } => {
self.expressions.adjust(hit_t);
}
Qf::ConfirmIntersection => {}
Qf::Terminate => {}
}
}
Expand Down
34 changes: 34 additions & 0 deletions naga/src/front/wgsl/lower/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,40 @@ impl<'source, 'temp> Lowerer<'source, 'temp> {
.push(crate::Statement::RayQuery { query, fun }, span);
return Ok(Some(result));
}
"rayQueryGenerateIntersection" => {
let mut args = ctx.prepare_args(arguments, 2, span);
let query = self.ray_query_pointer(args.next()?, ctx)?;
let hit_t = self.expression(args.next()?, ctx)?;
args.finish()?;

let fun = crate::RayQueryFunction::GenerateIntersection { hit_t };
let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block
.push(crate::Statement::RayQuery { query, fun }, span);
return Ok(None);
}
"rayQueryConfirmIntersection" => {
let mut args = ctx.prepare_args(arguments, 1, span);
let query = self.ray_query_pointer(args.next()?, ctx)?;
args.finish()?;

let fun = crate::RayQueryFunction::ConfirmIntersection;
let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block
.push(crate::Statement::RayQuery { query, fun }, span);
return Ok(None);
}
"rayQueryTerminate" => {
let mut args = ctx.prepare_args(arguments, 1, span);
let query = self.ray_query_pointer(args.next()?, ctx)?;
args.finish()?;

let fun = crate::RayQueryFunction::Terminate;
let rctx = ctx.runtime_expression_ctx(span)?;
rctx.block
.push(crate::Statement::RayQuery { query, fun }, span);
return Ok(None);
}
"rayQueryGetCommittedIntersection" => {
let mut args = ctx.prepare_args(arguments, 1, span);
let query = self.ray_query_pointer(args.next()?, ctx)?;
Expand Down
10 changes: 10 additions & 0 deletions naga/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1779,6 +1779,16 @@ pub enum RayQueryFunction {
result: Handle<Expression>,
},

/// Add a candidate generated intersection to be included
/// in the determination of the closest hit for a ray query.
GenerateIntersection {
hit_t: Handle<Expression>,
},

/// Confirm a triangle intersection to be included in the determination of
/// the closest hit for a ray query.
ConfirmIntersection,

Terminate,
}

Expand Down
21 changes: 14 additions & 7 deletions naga/src/valid/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,13 +1080,20 @@ impl FunctionInfo {
}
S::RayQuery { query, ref fun } => {
let _ = self.add_ref(query);
if let crate::RayQueryFunction::Initialize {
acceleration_structure,
descriptor,
} = *fun
{
let _ = self.add_ref(acceleration_structure);
let _ = self.add_ref(descriptor);
match *fun {
crate::RayQueryFunction::Initialize {
acceleration_structure,
descriptor,
} => {
let _ = self.add_ref(acceleration_structure);
let _ = self.add_ref(descriptor);
}
crate::RayQueryFunction::Proceed { result: _ } => {}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
let _ = self.add_ref(hit_t);
}
crate::RayQueryFunction::ConfirmIntersection => {}
crate::RayQueryFunction::Terminate => {}
}
FunctionUniformity::new()
}
Expand Down
15 changes: 15 additions & 0 deletions naga/src/valid/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ pub enum FunctionError {
InvalidRayDescriptor(Handle<crate::Expression>),
#[error("Ray Query {0:?} does not have a matching type")]
InvalidRayQueryType(Handle<crate::Type>),
#[error("Hit distance {0:?} must be an f32")]
InvalidHitDistanceType(Handle<crate::Expression>),
#[error("Shader requires capability {0:?}")]
MissingCapability(super::Capabilities),
#[error(
Expand Down Expand Up @@ -1487,6 +1489,19 @@ impl super::Validator {
crate::RayQueryFunction::Proceed { result } => {
self.emit_expression(result, context)?;
}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
match *context.resolve_type(hit_t, &self.valid_expression_set)? {
Ti::Scalar(crate::Scalar {
kind: crate::ScalarKind::Float,
width: _,
}) => {}
_ => {
return Err(FunctionError::InvalidHitDistanceType(hit_t)
.with_span_static(span, "invalid hit_t"))
}
}
}
crate::RayQueryFunction::ConfirmIntersection => {}
crate::RayQueryFunction::Terminate => {}
}
}
Expand Down
4 changes: 4 additions & 0 deletions naga/src/valid/handles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,10 @@ impl super::Validator {
crate::RayQueryFunction::Proceed { result } => {
validate_expr(result)?;
}
crate::RayQueryFunction::GenerateIntersection { hit_t } => {
validate_expr(hit_t)?;
}
crate::RayQueryFunction::ConfirmIntersection => {}
crate::RayQueryFunction::Terminate => {}
}
Ok(())
Expand Down
8 changes: 7 additions & 1 deletion naga/tests/in/ray-query.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,11 @@ fn main_candidate() {
var rq: ray_query;
rayQueryInitialize(&rq, acc_struct, RayDesc(RAY_FLAG_TERMINATE_ON_FIRST_HIT, 0xFFu, 0.1, 100.0, pos, dir));
let intersection = rayQueryGetCandidateIntersection(&rq);
output.visible = u32(intersection.kind == RAY_QUERY_INTERSECTION_AABB);
if (intersection.kind == RAY_QUERY_INTERSECTION_AABB) {
rayQueryGenerateIntersection(&rq, 10.0);
} else if (intersection.kind == RAY_QUERY_INTERSECTION_TRIANGLE) {
rayQueryConfirmIntersection(&rq);
} else {
rayQueryTerminate(&rq);
}
}
Loading

0 comments on commit 3efa0f7

Please sign in to comment.