lavapipe/rt: Fix watertightness for real this time

Reviewed-by: Mike Blumenkrantz <michael.blumenkrantz@gmail.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36197>
This commit is contained in:
Konstantin Seurer 2025-07-15 22:10:05 +02:00 committed by Marge Bot
parent 9313a16e24
commit a8cc143044
3 changed files with 96 additions and 17 deletions

View file

@ -11,7 +11,10 @@ test_planar_video_formats,Fail
# The test expect lod=0.5 to load level 1 with nearest sampling
test_sample_instructions,Fail
# The test messes up creating quad primitives and lavapipe misses some primitives
# because of reduced instance transform precision.
test_rayquery,Fail
test_raytracing,Fail
test_raytracing_mismatch_global_rs_link,Fail
test_sampler_rounding,Fail

View file

@ -509,6 +509,13 @@ lvp_encode_as(struct vk_acceleration_structure *dst, VkDeviceAddress intermediat
const struct vk_ir_node *ir_child = (const void *)(ir_bvh + ir_child_offset);
output_box->bounds[child_index] = ir_child->aabb;
/* Increase the bounding box size a bit for watertightness. */
output_box->bounds[child_index].min.x -= MAX2(fabsf(output_box->bounds[child_index].min.x), 1.0) * FLT_EPSILON;
output_box->bounds[child_index].min.y -= MAX2(fabsf(output_box->bounds[child_index].min.y), 1.0) * FLT_EPSILON;
output_box->bounds[child_index].min.z -= MAX2(fabsf(output_box->bounds[child_index].min.z), 1.0) * FLT_EPSILON;
output_box->bounds[child_index].max.x += MAX2(fabsf(output_box->bounds[child_index].max.x), 1.0) * FLT_EPSILON;
output_box->bounds[child_index].max.y += MAX2(fabsf(output_box->bounds[child_index].max.y), 1.0) * FLT_EPSILON;
output_box->bounds[child_index].max.z += MAX2(fabsf(output_box->bounds[child_index].max.z), 1.0) * FLT_EPSILON;
if (ir_child_offset < root_offset) {
output_box->children[child_index] =

View file

@ -150,6 +150,27 @@ lvp_build_intersect_ray_box(nir_builder *b, nir_def **node_data, nir_def *ray_tm
return nir_load_var(b, child_indices);
}
static nir_def *
lvp_build_intersect_edge(nir_builder *b, nir_def *v0_x, nir_def *v0_y, nir_def *v1_x, nir_def *v1_y)
{
/* Test (1 0 0) direction: t = <v1-v0, (1 0 0)> */
nir_def *t_x = nir_fsub(b, v1_x, v0_x);
nir_def *test_y = nir_feq_imm(b, t_x, 0.0);
/* Test (0 1 0) direction: t = <v1-v0, (0 1 0)> */
nir_def *t_y = nir_fsub(b, v1_y, v0_y);
return nir_bcsel(b, test_y, nir_flt_imm(b, t_y, 0.0), nir_flt_imm(b, t_x, 0.0));
}
static nir_def *
lvp_build_intersect_vertex(nir_builder *b, nir_def *v0_x, nir_def *v1_x, nir_def *v2_x)
{
/* Choose n=(1 0 0) to simplify the dot product. */
nir_def *edge0 = nir_fsub(b, v1_x, v0_x);
nir_def *edge1 = nir_fsub(b, v2_x, v0_x);
return nir_iand(b, nir_fle_imm(b, edge0, 0.0), nir_fgt_imm(b, edge1, 0.0));
}
static nir_def *
lvp_build_intersect_ray_tri(nir_builder *b, nir_def **node_data, nir_def *ray_tmax,
nir_def *origin, nir_def *dir, nir_def *inv_dir)
@ -237,13 +258,6 @@ lvp_build_intersect_ray_tri(nir_builder *b, nir_def **node_data, nir_def *ray_tm
nir_def *cy =
nir_fsub(b, nir_vector_extract(b, v_c, ky), nir_fmul(b, sy, nir_vector_extract(b, v_c, kz)));
ax = nir_f2f64(b, ax);
ay = nir_f2f64(b, ay);
bx = nir_f2f64(b, bx);
by = nir_f2f64(b, by);
cx = nir_f2f64(b, cx);
cy = nir_f2f64(b, cy);
nir_def *u = nir_fsub(b, nir_fmul(b, cx, by), nir_fmul(b, cy, bx));
nir_def *v = nir_fsub(b, nir_fmul(b, ax, cy), nir_fmul(b, ay, cx));
nir_def *w = nir_fsub(b, nir_fmul(b, bx, ay), nir_fmul(b, by, ax));
@ -257,16 +271,72 @@ lvp_build_intersect_ray_tri(nir_builder *b, nir_def **node_data, nir_def *ray_tm
nir_def *cond = nir_inot(b, nir_iand(b, cond_back, cond_front));
/* When an edge is hit, we have to ensure that it is not hit twice in case it is shared.
*
* Vulkan 1.4.322, Section 40.1.1 Watertightness:
*
* Any set of two triangles with two shared vertices that were specified in the same
* winding order in each triangle have a shared edge defined by those vertices.
*
* This means we can decide which triangle should intersect by comparing the shared edge
* to two arbitrary directions because the shared edges are antiparallel. The triangle
* vertices are transformed so the ray direction is (0 0 1). Therefore it makes sense to
* choose (1 0 0) and (0 1 0) as reference directions.
*
* Hitting edges is extremely rare so an if should be worth.
*/
nir_def *is_edge_a = nir_feq_imm(b, u, 0.0f);
nir_def *is_edge_b = nir_feq_imm(b, v, 0.0f);
nir_def *is_edge_c = nir_feq_imm(b, w, 0.0f);
nir_def *cond_edge = nir_ior(b, is_edge_a, nir_ior(b, is_edge_b, is_edge_c));
nir_def *intersect_edge = cond;
nir_push_if(b, cond_edge);
{
nir_def *intersect_edge_a = nir_iand(b, is_edge_a, lvp_build_intersect_edge(b, bx, by, cx, cy));
nir_def *intersect_edge_b = nir_iand(b, is_edge_b, lvp_build_intersect_edge(b, cx, cy, ax, ay));
nir_def *intersect_edge_c = nir_iand(b, is_edge_c, lvp_build_intersect_edge(b, ax, ay, bx, by));
intersect_edge = nir_iand(b, intersect_edge, nir_ior(b, nir_ior(b, intersect_edge_a, intersect_edge_b), intersect_edge_c));
/* For vertices, special handling is needed to avoid double hits. The spec defines
* shared vertices as follows (Vulkan 1.4.322, Section 40.1.1 Watertightness):
*
* Any set of two or more triangles where all triangles have one vertex with an
* identical position value, that vertex is a shared vertex.
*
* Since the no double hit/miss requirement of a shared vertex is only formulated for
* closed fans
*
* Implementations should not double-hit or miss when a ray intersects a shared edge,
* or a shared vertex of a closed fan.
*
* it is possible to choose an arbitrary direction n that defines which triangle in the
* closed fan should intersect the shared vertex with the ray.
*
* All edges that include the above vertex are shared edges.
*
* Implies that all triangles have the same winding order. It is therefore sufficiant
* to choose the triangle where the other vertices are on both sides of a plane
* perpendicular to n (relying on winding order to get one instead of two triangles
* that meet said condition).
*/
nir_def *is_vertex_a = nir_iand(b, is_edge_b, is_edge_c);
nir_def *is_vertex_b = nir_iand(b, is_edge_a, is_edge_c);
nir_def *is_vertex_c = nir_iand(b, is_edge_a, is_edge_b);
nir_def *intersect_vertex_a = nir_iand(b, is_vertex_a, lvp_build_intersect_vertex(b, ax, bx, cx));
nir_def *intersect_vertex_b = nir_iand(b, is_vertex_b, lvp_build_intersect_vertex(b, bx, cx, ax));
nir_def *intersect_vertex_c = nir_iand(b, is_vertex_c, lvp_build_intersect_vertex(b, cx, ax, bx));
nir_def *is_vertex = nir_ior(b, nir_ior(b, is_vertex_a, is_vertex_b), is_vertex_c);
nir_def *intersect_vertex = nir_ior(b, nir_ior(b, intersect_vertex_a, intersect_vertex_b), intersect_vertex_c);
intersect_vertex = nir_ior(b, nir_inot(b, is_vertex), intersect_vertex);
intersect_edge = nir_iand(b, intersect_edge, intersect_vertex);
}
nir_pop_if(b, NULL);
cond = nir_if_phi(b, intersect_edge, cond);
nir_push_if(b, cond);
{
nir_def *det = nir_fadd(b, u, nir_fadd(b, v, w));
sz = nir_f2f64(b, sz);
v_a = nir_f2f64(b, v_a);
v_b = nir_f2f64(b, v_b);
v_c = nir_f2f64(b, v_c);
nir_def *az = nir_fmul(b, sz, nir_vector_extract(b, v_a, kz));
nir_def *bz = nir_fmul(b, sz, nir_vector_extract(b, v_b, kz));
nir_def *cz = nir_fmul(b, sz, nir_vector_extract(b, v_c, kz));
@ -280,10 +350,9 @@ lvp_build_intersect_ray_tri(nir_builder *b, nir_def **node_data, nir_def *ray_tm
nir_push_if(b, det_cond_front);
{
t = nir_f2f32(b, nir_fdiv(b, t, det));
det = nir_f2f32(b, det);
v = nir_fdiv(b, nir_f2f32(b, v), det);
w = nir_fdiv(b, nir_f2f32(b, w), det);
t = nir_fdiv(b, t, det);
v = nir_fdiv(b, v, det);
w = nir_fdiv(b, w, det);
nir_def *indices[4] = {t, det, v, w};
nir_store_var(b, result, nir_vec(b, indices, 4), 0xf);