v3dv: Implement and enable nullDescriptor support

Handle null descriptors by emitting zeroed descriptor state.
When the nullDescriptor feature is enabled, a dedicated null_bo is
allocated. Null image descriptors now pack a TEXTURE_SHADER_STATE whose
base address points to this BO, ensuring that the TMU reads from valid
memory.

Reviewed-by: Iago Toral Quiroga <itoral@igalia.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40485>
This commit is contained in:
Daivik Bhatia 2026-04-15 23:58:57 +05:30 committed by Marge Bot
parent 6aed7d2988
commit 990d76eae6
9 changed files with 193 additions and 35 deletions

View file

@ -3482,6 +3482,8 @@ v3dv_CmdBindVertexBuffers2(VkCommandBuffer commandBuffer,
for (uint32_t i = 0; i < bindingCount; i++) {
struct v3dv_buffer *buffer = v3dv_buffer_from_handle(pBuffers[i]);
assert(buffer || cmd_buffer->device->vk.enabled_features.nullDescriptor);
if (vb[firstBinding + i].buffer != buffer) {
vb[firstBinding + i].buffer = v3dv_buffer_from_handle(pBuffers[i]);
vb_state_changed = true;
@ -3491,14 +3493,19 @@ v3dv_CmdBindVertexBuffers2(VkCommandBuffer commandBuffer,
vb[firstBinding + i].offset = pOffsets[i];
vb_state_changed = true;
}
assert(pOffsets[i] <= buffer->size);
VkDeviceSize size;
if (!pSizes || pSizes[i] == VK_WHOLE_SIZE)
size = buffer->size - pOffsets[i];
else
size = pSizes[i];
assert(pOffsets[i] + size <= buffer->size);
if (!buffer) {
size = 0;
} else {
assert(pOffsets[i] <= buffer->size);
if (!pSizes || pSizes[i] == VK_WHOLE_SIZE)
size = buffer->size - pOffsets[i];
else
size = pSizes[i];
assert(pOffsets[i] + size <= buffer->size);
}
if (vb[firstBinding + i].size != size) {
vb[firstBinding + i].size = size;

View file

@ -196,8 +196,6 @@ v3dv_descriptor_map_get_sampler(struct v3dv_descriptor_state *descriptor_state,
assert(descriptor->type == VK_DESCRIPTOR_TYPE_SAMPLER ||
descriptor->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
assert(descriptor->sampler);
return descriptor->sampler;
}
@ -238,13 +236,15 @@ v3dv_descriptor_map_get_texture_bo(struct v3dv_descriptor_state *descriptor_stat
switch (descriptor->type) {
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
assert(descriptor->buffer_view);
if (!descriptor->buffer_view)
return NULL;
return descriptor->buffer_view->buffer->mem->bo;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: {
assert(descriptor->image_view);
if (!descriptor->image_view)
return NULL;
struct v3dv_image *image =
(struct v3dv_image *) descriptor->image_view->vk.image;
assert(map->plane[index] < image->plane_count);
@ -1059,6 +1059,17 @@ write_buffer_descriptor(struct v3dv_descriptor *descriptor,
descriptor->type = desc_type;
descriptor->buffer = buffer;
/* This can happen when nullDescriptor is used. In that
* case the compiler will not emit the buffer access so
* the descriptor won't be accessed at all.
*/
if (!buffer) {
descriptor->offset = 0;
descriptor->range = 0;
return;
}
descriptor->offset = buffer_info->offset;
if (buffer_info->range == VK_WHOLE_SIZE) {
descriptor->range = buffer->size - buffer_info->offset;
@ -1082,12 +1093,46 @@ write_image_descriptor(struct v3dv_device *device,
descriptor->sampler = sampler;
descriptor->image_view = iview;
assert(iview || sampler);
uint8_t plane_count = iview ? iview->plane_count : sampler->plane_count;
if (!device->vk.enabled_features.nullDescriptor)
assert(iview || sampler);
/* When VK_KHR_robustness2 nullDescriptor is enabled, applications are
* allowed to write VK_NULL_HANDLE for the imageView and sampler.
*/
const bool sampler_required =
(desc_type == VK_DESCRIPTOR_TYPE_SAMPLER ||
desc_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) &&
!binding_layout->immutable_samplers_offset;
const bool image_required =
desc_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
desc_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ||
desc_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT ||
desc_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
void *desc_map = descriptor_bo_map(device, set,
binding_layout, array_index);
if ((image_required && !iview) || (sampler_required && !sampler)) {
/* Multiplanar YCbCr descriptors require immutable samplers and a non-null
* imageView (VUID-VkWriteDescriptorSet-descriptorType-02738) both of
* which are not true here so we must never reach the null path
* with plane count > 1 when VK_KHR_robustness2 nullDescriptor is enabled.
*/
const uint32_t size =
v3d_X((&device->devinfo), descriptor_bo_size)(desc_type) *
binding_layout->plane_stride;
memset(desc_map, 0, size);
v3d_X((&device->devinfo), pack_null_texture_state)(device, desc_map);
descriptor->sampler = NULL;
descriptor->image_view = NULL;
return;
}
uint8_t plane_count = iview ? iview->plane_count : sampler->plane_count;
for (uint8_t plane = 0; plane < plane_count; plane++) {
if (iview) {
uint32_t offset = desc_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ?
@ -1128,12 +1173,19 @@ write_buffer_view_descriptor(struct v3dv_device *device,
struct v3dv_buffer_view *bview,
uint32_t array_index)
{
assert(bview);
assert(bview || device->vk.enabled_features.nullDescriptor);
descriptor->type = desc_type;
descriptor->buffer_view = bview;
void *desc_map = descriptor_bo_map(device, set, binding_layout, array_index);
if (!bview) {
memset(desc_map, 0, sizeof(bview->texture_shader_state));
v3d_X((&device->devinfo), pack_null_texture_state)(device, desc_map);
return;
}
memcpy(desc_map,
bview->texture_shader_state,
sizeof(bview->texture_shader_state));
@ -1240,7 +1292,7 @@ v3dv_UpdateDescriptorSets(VkDevice _device,
* image sampler, but for YCbCr we kwnow that we must use
* immutable combined image samplers
*/
assert(iview->plane_count == 1);
assert(!iview || iview->plane_count == 1);
V3DV_FROM_HANDLE(v3dv_sampler, _sampler, image_info->sampler);
sampler = _sampler;
}
@ -1417,7 +1469,16 @@ v3dv_UpdateDescriptorSetWithTemplate(
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorImageInfo *info =
pData + entry->offset + j * entry->stride;
V3DV_FROM_HANDLE(v3dv_sampler, sampler, info->sampler);
write_image_descriptor(device, descriptor + entry->array_element + j,
entry->type, set, binding_layout, NULL,
sampler, entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
@ -1425,10 +1486,29 @@ v3dv_UpdateDescriptorSetWithTemplate(
const VkDescriptorImageInfo *info =
pData + entry->offset + j * entry->stride;
V3DV_FROM_HANDLE(v3dv_image_view, iview, info->imageView);
V3DV_FROM_HANDLE(v3dv_sampler, sampler, info->sampler);
write_image_descriptor(device, descriptor + entry->array_element + j,
entry->type, set, binding_layout, iview,
sampler, entry->array_element + j);
NULL, entry->array_element + j);
}
break;
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
for (uint32_t j = 0; j < entry->array_count; j++) {
const VkDescriptorImageInfo *info =
pData + entry->offset + j * entry->stride;
V3DV_FROM_HANDLE(v3dv_image_view, iview, info->imageView);
struct v3dv_sampler *sampler = NULL;
if (!binding_layout->immutable_samplers_offset) {
/* In general we ignore the sampler when updating a combined
* image sampler, but for YCbCr we know that we must use
* immutable combined image samplers.
*/
assert(!iview || iview->plane_count == 1);
sampler = v3dv_sampler_from_handle(info->sampler);
}
write_image_descriptor(device, descriptor + entry->array_element + j,
entry->type, set, binding_layout,
iview, sampler, entry->array_element + j);
}
break;

View file

@ -423,7 +423,7 @@ get_features(const struct v3dv_physical_device *physical_device,
.robustImageAccess = true,
.robustBufferAccess2 = false,
.robustImageAccess2 = true,
.nullDescriptor = false,
.nullDescriptor = true,
.shaderIntegerDotProduct = true,
/* VK_EXT_4444_formats */
@ -2030,6 +2030,18 @@ v3dv_CreateDevice(VkPhysicalDevice physicalDevice,
if (result != VK_SUCCESS)
goto fail;
if (device->vk.enabled_features.nullDescriptor) {
device->null_bo =
v3dv_bo_alloc(device, 4096, "null texture data", true);
if (!device->null_bo ||
!v3dv_bo_map(device, device->null_bo,
device->null_bo->size)) {
result = vk_error(device, VK_ERROR_OUT_OF_DEVICE_MEMORY);
goto fail;
}
memset(device->null_bo->map, 0, device->null_bo->size);
}
device->device_address_mem_ctx = ralloc_context(NULL);
util_dynarray_init(&device->device_address_bo_list,
device->device_address_mem_ctx);
@ -2066,6 +2078,7 @@ fail_queues_alloc:
v3dv_pipeline_cache_finish(&device->default_pipeline_cache);
v3dv_event_free_resources(device);
v3dv_query_free_resources(device);
v3dv_bo_free(device, device->null_bo);
vk_device_finish(&device->vk);
vk_free(&device->vk.alloc, device);
@ -2099,6 +2112,11 @@ v3dv_DestroyDevice(VkDevice _device,
device->default_attribute_float = NULL;
}
if (device->null_bo) {
v3dv_bo_free(device, device->null_bo);
device->null_bo = NULL;
}
ralloc_free(device->device_address_mem_ctx);
/* Bo cache should be removed the last, as any other object could be

View file

@ -395,6 +395,11 @@ struct v3dv_device {
*/
struct v3dv_bo *default_attribute_float;
/* When nullDescriptor is enabled, this BO provides valid zeroed memory
* for null descriptor paths.
*/
struct v3dv_bo *null_bo;
void *device_address_mem_ctx;
struct util_dynarray device_address_bo_list; /* Array of struct v3dv_bo * */
};

View file

@ -1046,6 +1046,8 @@ pipeline_populate_v3d_key(struct v3d_key *key,
p_stage->robustness.images == robust_image_enabled;
key->robust_image_access_2 =
p_stage->robustness.images == robust_image2_enabled;
key->null_descriptor =
p_stage->pipeline->device->vk.enabled_features.nullDescriptor;
}
uint32_t

View file

@ -152,7 +152,11 @@ write_tmu_p0(struct v3dv_cmd_buffer *cmd_buffer,
v3dv_descriptor_map_get_texture_bo(descriptor_state,
&pipeline->shared_data->maps[stage]->texture_map,
pipeline->layout, texture_idx);
assert(texture_bo);
assert(texture_bo || cmd_buffer->device->vk.enabled_features.nullDescriptor);
if (!texture_bo)
texture_bo = cmd_buffer->device->null_bo;
assert(texture_idx < V3D_MAX_TEXTURE_SAMPLERS);
tex_bos->tex[texture_idx] = texture_bo;
@ -201,11 +205,12 @@ write_tmu_p1(struct v3dv_cmd_buffer *cmd_buffer,
v3dv_descriptor_map_get_sampler(descriptor_state,
&pipeline->shared_data->maps[stage]->sampler_map,
pipeline->layout, sampler_idx);
assert(sampler);
assert(sampler || cmd_buffer->device->vk.enabled_features.nullDescriptor);
/* Set unnormalized coordinates flag from sampler object */
uint32_t p1_packed = v3d_unit_data_get_offset(data);
if (sampler->unnormalized_coordinates) {
if (sampler && sampler->unnormalized_coordinates) {
v3d_pack_unnormalized_coordinates(&cmd_buffer->device->devinfo, &p1_packed,
sampler->unnormalized_coordinates);
}
@ -308,15 +313,25 @@ write_ubo_ssbo_uniforms(struct v3dv_cmd_buffer *cmd_buffer,
bo = reloc.bo;
addr = reloc.bo->offset + reloc.offset + offset;
} else {
assert(descriptor->buffer);
assert(descriptor->buffer->mem);
assert(descriptor->buffer->mem->bo);
/* This can happen when nullDescriptor is used. In that
* case the compiler will not emit the buffer access so
* the descriptor won't be accessed at all.
*/
if (!descriptor->buffer) {
assert(cmd_buffer->device->vk.enabled_features.nullDescriptor);
bo = NULL;
addr = 0;
} else {
assert(descriptor->buffer);
assert(descriptor->buffer->mem);
assert(descriptor->buffer->mem->bo);
bo = descriptor->buffer->mem->bo;
addr = bo->offset +
descriptor->buffer->mem_offset +
descriptor->offset +
offset + dynamic_offset;
bo = descriptor->buffer->mem->bo;
addr = bo->offset +
descriptor->buffer->mem_offset +
descriptor->offset +
offset + dynamic_offset;
}
}
cl_aligned_u32(uniforms, addr);
@ -364,6 +379,9 @@ get_texture_size_from_image_view(struct v3dv_image_view *image_view,
enum quniform_contents contents,
uint32_t data)
{
if (!image_view)
return 0;
switch(contents) {
case QUNIFORM_IMAGE_WIDTH:
case QUNIFORM_TEXTURE_WIDTH:
@ -401,6 +419,9 @@ get_texture_size_from_buffer_view(struct v3dv_buffer_view *buffer_view,
enum quniform_contents contents,
uint32_t data)
{
if (!buffer_view)
return 0;
switch(contents) {
case QUNIFORM_IMAGE_WIDTH:
case QUNIFORM_TEXTURE_WIDTH:

View file

@ -2612,12 +2612,16 @@ v3dX(cmd_buffer_emit_gl_shader_state)(struct v3dv_cmd_buffer *cmd_buffer)
cl_emit_with_prepacked(&job->indirect, GL_SHADER_STATE_ATTRIBUTE_RECORD,
&pipeline->vertex_attrs[i * packet_length], attr) {
assert(c_vb->buffer->mem->bo);
attr.address = v3dv_cl_address(c_vb->buffer->mem->bo,
c_vb->buffer->mem_offset +
pipeline->va[i].offset +
c_vb->offset);
if (c_vb->buffer) {
assert(c_vb->buffer->mem->bo);
attr.address = v3dv_cl_address(c_vb->buffer->mem->bo,
c_vb->buffer->mem_offset +
pipeline->va[i].offset +
c_vb->offset);
} else {
assert(cmd_buffer->device->null_bo);
attr.address = v3dv_cl_address(cmd_buffer->device->null_bo, 0);
}
attr.number_of_values_read_by_coordinate_shader =
prog_data_vs_bin->vattr_sizes[location];

View file

@ -235,3 +235,22 @@ v3dX(pack_texture_shader_state_from_buffer_view)(struct v3dv_device *device,
#endif
}
}
void
v3dX(pack_null_texture_state)(struct v3dv_device *device, void *map)
{
assert(device->null_bo);
const uint32_t base_offset = device->null_bo->offset;
v3dvx_pack(map, TEXTURE_SHADER_STATE, tex) {
tex.image_width = 1;
tex.image_height = 1;
tex.image_depth = 1;
tex.texture_base_pointer = v3dv_cl_address(NULL, base_offset);
#if V3D_VERSION >= 71
/* See comment in XML field definition for rationale of the shifts */
tex.texture_base_pointer_cb = base_offset >> 6;
tex.texture_base_pointer_cr = base_offset >> 6;
#endif
}
}

View file

@ -306,6 +306,8 @@ uint32_t v3dX(combined_image_sampler_texture_state_offset)(uint8_t plane);
uint32_t v3dX(combined_image_sampler_sampler_state_offset)(uint8_t plane);
void v3dX(pack_null_texture_state)(struct v3dv_device *device, void *map);
/* General utils */
void