From d58aba6c83eefb117d138cd18b576a0c28526b80 Mon Sep 17 00:00:00 2001 From: Michal Krol Date: Wed, 6 May 2026 14:17:13 +0200 Subject: [PATCH] lavapipe: implement VK_EXT_image_view_min_lod with fractional minLod Plumb VkImageViewMinLodCreateInfoEXT::minLod into the texture descriptor and clamp lambda in the JITted sampling path. The view's minLod lives on the texture side of lp_descriptor (so it is reachable in both combined-image-sampler and separate sampler / sampled image setups). A new lp_static_texture_state::apply_view_min_lod bit is set when the view has a non-zero clamp; this gates the load+max in the shader so unaffected views and non-bindless gallivm consumers pay zero cost. lp_jit_bindless_texture_from_pipe converts to view-relative space (min_lod_clamp - first_level, clamped to >= 0) so the value composes directly with the view-relative lambda lp_build_lod_selector computes. The clamp is applied in both the normal lod path and the min_max_lod_equal fast path, before lod_positive is recomputed, so magnification, lodq and trilinear all behave correctly. For integer texel fetches the Vulkan spec defines an OpImageFetch whose LOD is below the view's minLodInteger as reading zero, so lp_build_fetch_texel applies the same floor(view_min_lod) clamp. Drops test_view_min_lod from the vkd3d fails list. Part-of: --- .../auxiliary/gallivm/lp_bld_jit_types.c | 25 +++++++++++++++++ .../auxiliary/gallivm/lp_bld_jit_types.h | 1 + src/gallium/auxiliary/gallivm/lp_bld_sample.c | 28 +++++++++++++++++++ src/gallium/auxiliary/gallivm/lp_bld_sample.h | 9 ++++++ .../auxiliary/gallivm/lp_bld_sample_soa.c | 25 +++++++++++++++++ src/gallium/drivers/llvmpipe/lp_jit.c | 7 +++++ .../frontends/lavapipe/ci/lvp-vkd3d-fails.txt | 1 - src/gallium/frontends/lavapipe/lvp_device.c | 4 +++ src/gallium/frontends/lavapipe/lvp_image.c | 1 + 9 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/gallium/auxiliary/gallivm/lp_bld_jit_types.c b/src/gallium/auxiliary/gallivm/lp_bld_jit_types.c index 6b4f6b42719..94159b97f4b 100644 --- a/src/gallium/auxiliary/gallivm/lp_bld_jit_types.c +++ b/src/gallium/auxiliary/gallivm/lp_bld_jit_types.c @@ -421,6 +421,29 @@ lp_build_llvm_texture_member(struct gallivm_state *gallivm, return res; } +static LLVMValueRef +lp_build_llvm_texture_view_min_lod(struct gallivm_state *gallivm, + LLVMTypeRef resources_type, + LLVMValueRef resources_ptr, + unsigned texture_unit, + LLVMValueRef texture_unit_offset) +{ + /* Only reached in bindless mode, where apply_view_min_lod gates the call. */ + assert(gallivm->texture_descriptor); + + LLVMBuilderRef builder = gallivm->builder; + LLVMTypeRef float_type = LLVMFloatTypeInContext(gallivm->context); + + LLVMValueRef ptr = LLVMBuildAdd(builder, gallivm->texture_descriptor, + lp_build_const_int64(gallivm, + offsetof(struct lp_image_descriptor, texture.view_min_lod)), + ""); + ptr = LLVMBuildIntToPtr(builder, ptr, LLVMPointerType(float_type, 0), ""); + + return LLVMBuildLoad2(builder, float_type, ptr, "view_min_lod"); +} + + static LLVMValueRef lp_build_llvm_texture_residency(struct gallivm_state *gallivm, LLVMTypeRef resources_type, @@ -725,6 +748,8 @@ lp_build_jit_fill_sampler_dynamic_state(struct lp_sampler_dynamic_state *state) state->max_lod = lp_build_llvm_sampler_max_lod; state->lod_bias = lp_build_llvm_sampler_lod_bias; state->border_color = lp_build_llvm_sampler_border_color; + + state->view_min_lod = lp_build_llvm_texture_view_min_lod; } void diff --git a/src/gallium/auxiliary/gallivm/lp_bld_jit_types.h b/src/gallium/auxiliary/gallivm/lp_bld_jit_types.h index 4456fb1992e..4e349fee437 100644 --- a/src/gallium/auxiliary/gallivm/lp_bld_jit_types.h +++ b/src/gallium/auxiliary/gallivm/lp_bld_jit_types.h @@ -238,6 +238,7 @@ struct lp_jit_bindless_texture const void *base; const void *residency; uint32_t base_offset; + float view_min_lod; /* VK_EXT_image_view_min_lod, relative to first_level */ }; struct lp_image_descriptor { diff --git a/src/gallium/auxiliary/gallivm/lp_bld_sample.c b/src/gallium/auxiliary/gallivm/lp_bld_sample.c index 4105afc7eeb..14c882949ed 100644 --- a/src/gallium/auxiliary/gallivm/lp_bld_sample.c +++ b/src/gallium/auxiliary/gallivm/lp_bld_sample.c @@ -133,6 +133,13 @@ lp_sampler_static_texture_state(struct lp_static_texture_state *state, if (state->tiled) state->tiled_samples = texture->nr_samples; + /* VK_EXT_image_view_min_lod: bake "apply view min-lod" into the static state + * so views without a clamp don't pay any shader cost. The actual clamp value + * lives in the texture descriptor (dynamic state). + */ + state->apply_view_min_lod = texture->target != PIPE_BUFFER && + view->u.tex.min_lod_clamp > 0.0f; + /* * the layer / element / level parameters are all either dynamic * state or handled transparently wrt execution. @@ -1158,6 +1165,16 @@ lp_build_lod_selector(struct lp_build_sample_context *bld, bld->resources_ptr, sampler_unit); lod = lp_build_broadcast_scalar(lodf_bld, min_lod); + + /* VK_EXT_image_view_min_lod applies even when min == max lod. */ + if (bld->static_texture_state->apply_view_min_lod && + dynamic_state->view_min_lod) { + LLVMValueRef view_min_lod = + dynamic_state->view_min_lod(bld->gallivm, bld->resources_type, + bld->resources_ptr, sampler_unit, NULL); + view_min_lod = lp_build_broadcast_scalar(lodf_bld, view_min_lod); + lod = lp_build_max(lodf_bld, lod, view_min_lod); + } } else { if (explicit_lod) { if (bld->num_lods != bld->coord_type.length) { @@ -1274,6 +1291,17 @@ lp_build_lod_selector(struct lp_build_sample_context *bld, lod = lp_build_max(lodf_bld, lod, desc_min_lod); } + /* VK_EXT_image_view_min_lod: clamp lod to the per-view minimum. */ + if (bld->static_texture_state->apply_view_min_lod && + dynamic_state->view_min_lod) { + LLVMValueRef view_min_lod = + dynamic_state->view_min_lod(bld->gallivm, bld->resources_type, + bld->resources_ptr, sampler_unit, NULL); + view_min_lod = lp_build_broadcast_scalar(lodf_bld, view_min_lod); + + lod = lp_build_max(lodf_bld, lod, view_min_lod); + } + if (min_lod) { if (bld->num_lods != bld->coord_type.length) { min_lod = lp_build_pack_aos_scalars(bld->gallivm, bld->coord_bld.type, diff --git a/src/gallium/auxiliary/gallivm/lp_bld_sample.h b/src/gallium/auxiliary/gallivm/lp_bld_sample.h index 59e16a4279a..14b23147b35 100644 --- a/src/gallium/auxiliary/gallivm/lp_bld_sample.h +++ b/src/gallium/auxiliary/gallivm/lp_bld_sample.h @@ -221,6 +221,8 @@ struct lp_static_texture_state unsigned level_zero_only:1; unsigned tiled:1; unsigned tiled_samples:5; + /**< view min lod clamp (VK_EXT_image_view_min_lod): apply lod = max(lod, view_min_lod) */ + unsigned apply_view_min_lod:1; }; @@ -377,6 +379,13 @@ struct lp_sampler_dynamic_state LLVMValueRef resources_ptr, unsigned sampler_unit); + /* Obtain the per-view min-lod clamp (returns float, relative to first_level) */ + LLVMValueRef + (*view_min_lod)(struct gallivm_state *gallivm, + LLVMTypeRef resources_type, + LLVMValueRef resources_ptr, + unsigned texture_unit, LLVMValueRef texture_unit_offset); + /** * Obtain texture cache (returns ptr to lp_build_format_cache). * diff --git a/src/gallium/auxiliary/gallivm/lp_bld_sample_soa.c b/src/gallium/auxiliary/gallivm/lp_bld_sample_soa.c index 5189caa1059..559079c7859 100644 --- a/src/gallium/auxiliary/gallivm/lp_bld_sample_soa.c +++ b/src/gallium/auxiliary/gallivm/lp_bld_sample_soa.c @@ -2969,10 +2969,35 @@ lp_build_fetch_texel(struct lp_build_sample_context *bld, first_level = lp_build_broadcast_scalar(&bld->leveli_bld, first_level); last_level = lp_build_broadcast_scalar(&bld->leveli_bld, last_level); + + LLVMValueRef requested_level = ilevel; lp_build_nearest_mip_level(bld, first_level, last_level, ilevel, &ilevel, out_of_bound_ret_zero ? &out_of_bounds : NULL); + + /* The Vulkan spec defines an OpImageFetch with LOD below the view's + * minLodInteger as reading zero. + * Since view_min_lod is view-relative, clamp against its floor. + */ + if (out_of_bound_ret_zero && + bld->static_texture_state->apply_view_min_lod && + bld->dynamic_state->view_min_lod) { + LLVMValueRef vml = + bld->dynamic_state->view_min_lod(bld->gallivm, bld->resources_type, + bld->resources_ptr, texture_unit, NULL); + LLVMValueRef min_level = + lp_build_broadcast_scalar(&bld->leveli_bld, + lp_build_ifloor(&bld->float_bld, vml)); + LLVMValueRef below = lp_build_cmp(&bld->leveli_bld, PIPE_FUNC_LESS, + requested_level, min_level); + if (bld->num_mips == 1) + below = lp_build_broadcast_scalar(&bld->int_coord_bld, below); + else if (bld->num_mips != bld->coord_bld.type.length) + below = lp_build_unpack_broadcast_aos_scalars(bld->gallivm, + bld->leveli_bld.type, bld->int_coord_bld.type, below); + out_of_bounds = lp_build_or(int_coord_bld, out_of_bounds, below); + } } else { assert(bld->num_mips == 1); if (bld->static_texture_state->target != PIPE_BUFFER) { diff --git a/src/gallium/drivers/llvmpipe/lp_jit.c b/src/gallium/drivers/llvmpipe/lp_jit.c index 71e77b77cf6..8d39f7014de 100644 --- a/src/gallium/drivers/llvmpipe/lp_jit.c +++ b/src/gallium/drivers/llvmpipe/lp_jit.c @@ -515,8 +515,14 @@ lp_jit_bindless_texture_from_pipe(struct lp_jit_bindless_texture *jit, const str assert(!lp_tex->dt); if (llvmpipe_resource_is_texture(res)) { + /* Convert the view's min_lod_clamp to view-relative space for the shader. */ + float view_relative_min_lod = + view->u.tex.min_lod_clamp - (float)view->u.tex.first_level; + jit->view_min_lod = view_relative_min_lod > 0.0f ? view_relative_min_lod : 0.0f; + jit->base = lp_tex->tex_data; } else { + jit->view_min_lod = 0.0f; jit->base = lp_tex->data; } const void *base = jit->base; @@ -586,6 +592,7 @@ void lp_jit_bindless_texture_buffer_from_bda(struct lp_jit_bindless_texture *jit, void *mem) { jit->base = mem; + jit->view_min_lod = 0.0f; } void diff --git a/src/gallium/frontends/lavapipe/ci/lvp-vkd3d-fails.txt b/src/gallium/frontends/lavapipe/ci/lvp-vkd3d-fails.txt index ce37bf5def0..1fabe753e9c 100644 --- a/src/gallium/frontends/lavapipe/ci/lvp-vkd3d-fails.txt +++ b/src/gallium/frontends/lavapipe/ci/lvp-vkd3d-fails.txt @@ -8,7 +8,6 @@ test_planar_video_formats,Fail test_sample_instructions,Fail test_sampler_rounding,Fail -test_view_min_lod,Fail test_null_descriptor_resinfo_dxbc,Fail test_null_descriptor_resinfo_dxil,Fail diff --git a/src/gallium/frontends/lavapipe/lvp_device.c b/src/gallium/frontends/lavapipe/lvp_device.c index a289be96c59..775d37a2eb0 100644 --- a/src/gallium/frontends/lavapipe/lvp_device.c +++ b/src/gallium/frontends/lavapipe/lvp_device.c @@ -255,6 +255,7 @@ static const struct vk_device_extension_table lvp_device_extensions_supported = .EXT_image_2d_view_of_3d = true, .EXT_image_sliced_view_of_3d = true, .EXT_image_robustness = true, + .EXT_image_view_min_lod = true, .EXT_index_type_uint8 = true, .EXT_inline_uniform_block = true, .EXT_load_store_op_none = true, @@ -635,6 +636,9 @@ lvp_get_features(const struct lvp_physical_device *pdevice, /* VK_EXT_image_sliced_view_of_3d */ .imageSlicedViewOf3D = true, + /* VK_EXT_image_view_min_lod */ + .minLod = true, + /* VK_EXT_depth_bias_control */ .depthBiasControl = true, .leastRepresentableValueForceUnormRepresentation = true, diff --git a/src/gallium/frontends/lavapipe/lvp_image.c b/src/gallium/frontends/lavapipe/lvp_image.c index 461d3396bc8..44d457fd454 100644 --- a/src/gallium/frontends/lavapipe/lvp_image.c +++ b/src/gallium/frontends/lavapipe/lvp_image.c @@ -311,6 +311,7 @@ lvp_create_samplerview(struct pipe_context *pctx, struct lvp_image_view *iv, VkF templ.u.tex.last_layer = iv->vk.base_array_layer + iv->vk.layer_count - 1; templ.u.tex.first_level = iv->vk.base_mip_level; templ.u.tex.last_level = iv->vk.base_mip_level + iv->vk.level_count - 1; + templ.u.tex.min_lod_clamp = iv->vk.min_lod; templ.swizzle_r = vk_conv_swizzle(iv->vk.swizzle.r, PIPE_SWIZZLE_X); templ.swizzle_g = vk_conv_swizzle(iv->vk.swizzle.g, PIPE_SWIZZLE_Y); templ.swizzle_b = vk_conv_swizzle(iv->vk.swizzle.b, PIPE_SWIZZLE_Z);