v3dv: add support for sampling simple 2D linear textures

V3D can't sample linear images (other than 1D), however, some applications
will require this to work. Particularly, our swapchain images may need to be
linear (for display), so sampling from them won't work.

This change detects the case where we are binding a descriptor which attempts
to sample from a simple 2D linear texture, transparently creates a tiled
copy of the image and rewrites the  descriptor to refer to the tiled image
instead. This will be slow but will allow some applications that require this
to work (i.e. some aspects of Android's user interface).

As of this patch, this only supports sampling linear images with a single
miplevel and layer from single-plane images in non-arrayed descriptors. We
could handle other cases too with a bit more work though.

Closes: https://gitlab.freedesktop.org/mesa/mesa/-/issues/9712
Tested-by: Roman Stratiienko <r.stratiienko@gmail.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/25048>
This commit is contained in:
Iago Toral Quiroga
2023-08-31 13:14:19 +02:00
committed by Marge Bot
parent 151f78150b
commit 95f881adbd
4 changed files with 281 additions and 0 deletions

View File

@@ -24,6 +24,7 @@
#include "v3dv_private.h"
#include "util/u_pack_color.h"
#include "vk_util.h"
#include "vulkan/runtime/vk_common_entrypoints.h"
void
v3dv_job_add_bo(struct v3dv_job *job, struct v3dv_bo *bo)
@@ -3415,6 +3416,235 @@ v3dv_CmdSetLineWidth(VkCommandBuffer commandBuffer,
cmd_buffer->state.dirty |= V3DV_CMD_DIRTY_LINE_WIDTH;
}
/**
* This checks a descriptor set to see if are binding any descriptors that would
* involve sampling from a linear image (the hardware only supports this for
* 1D images), and if so, attempts to create a tiled copy of the linear image
* and rewrite the descriptor set to use that instead.
*
* This was added to support a scenario with Android where some part of the UI
* wanted to show previews of linear swapchain images. For more details:
* https://gitlab.freedesktop.org/mesa/mesa/-/issues/9712
*
* Currently this only supports a linear sampling from a simple 2D image, but
* it could be extended to support more cases if necessary.
*/
static void
handle_sample_from_linear_image(struct v3dv_cmd_buffer *cmd_buffer,
struct v3dv_descriptor_set *set)
{
for (int32_t i = 0; i < set->layout->binding_count; i++) {
const struct v3dv_descriptor_set_binding_layout *blayout =
&set->layout->binding[i];
if (blayout->type != VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE &&
blayout->type != VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
continue;
struct v3dv_descriptor *desc = &set->descriptors[blayout->descriptor_index];
struct v3dv_image *image = (struct v3dv_image *) desc->image_view->vk.image;
struct v3dv_image_view *view = (struct v3dv_image_view *) desc->image_view;
if (image->tiled || view->vk.view_type == VK_IMAGE_VIEW_TYPE_1D)
continue;
/* FIXME: we can probably handle most of these restrictions too with
* a bit of extra effort.
*/
if (view->vk.view_type != VK_IMAGE_VIEW_TYPE_2D ||
view->vk.level_count != 1 || view->vk.layer_count != 1 ||
view->plane_count != 1 || blayout->array_size != 1) {
fprintf(stderr, "Sampling from linear image is not supported. "
"Expect corruption.\n");
continue;
}
/* We are sampling from a linear image. V3D doesn't support this
* so we create a tiled copy of the image and rewrite the descriptor
* to read from it instead.
*/
perf_debug("Sampling from linear image is not supported natively and "
"requires a copy.\n");
struct v3dv_device *device = cmd_buffer->device;
VkDevice vk_device = v3dv_device_to_handle(device);
/* Allocate shadow tiled image if needed, we only do this once for
* each image, on the first sampling attempt. We need to take a lock
* since we may be trying to do the same in another command buffer in
* a separate thread.
*/
mtx_lock(&device->meta.mtx);
VkResult result;
VkImage tiled_image;
if (image->shadow) {
tiled_image = v3dv_image_to_handle(image->shadow);
} else {
VkImageCreateInfo image_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.flags = image->vk.create_flags,
.imageType = image->vk.image_type,
.format = image->vk.format,
.extent = {
image->vk.extent.width,
image->vk.extent.height,
image->vk.extent.depth,
},
.mipLevels = image->vk.mip_levels,
.arrayLayers = image->vk.array_layers,
.samples = image->vk.samples,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = image->vk.usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
};
result = v3dv_CreateImage(vk_device, &image_info,
&device->vk.alloc, &tiled_image);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to copy linear 2D image for sampling."
"Expect corruption.\n");
mtx_unlock(&device->meta.mtx);
continue;
}
VkMemoryRequirements reqs;
vk_common_GetImageMemoryRequirements(vk_device, tiled_image, &reqs);
VkDeviceMemory mem;
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = reqs.size,
.memoryTypeIndex = 0,
};
result = v3dv_AllocateMemory(vk_device, &alloc_info,
&device->vk.alloc, &mem);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to copy linear 2D image for sampling."
"Expect corruption.\n");
v3dv_DestroyImage(vk_device, tiled_image, &device->vk.alloc);
mtx_unlock(&device->meta.mtx);
continue;
}
result = vk_common_BindImageMemory(vk_device, tiled_image, mem, 0);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to copy linear 2D image for sampling."
"Expect corruption.\n");
v3dv_DestroyImage(vk_device, tiled_image, &device->vk.alloc);
v3dv_FreeMemory(vk_device, mem, &device->vk.alloc);
mtx_unlock(&device->meta.mtx);
continue;
}
image->shadow = v3dv_image_from_handle(tiled_image);
}
/* Create a shadow view that refers to the tiled image if needed */
VkImageView tiled_view;
if (view->shadow) {
tiled_view = v3dv_image_view_to_handle(view->shadow);
} else {
VkImageViewCreateInfo view_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.flags = view->vk.create_flags,
.image = tiled_image,
.viewType = view->vk.view_type,
.format = view->vk.format,
.components = view->vk.swizzle,
.subresourceRange = {
.aspectMask = view->vk.aspects,
.baseMipLevel = view->vk.base_mip_level,
.levelCount = view->vk.level_count,
.baseArrayLayer = view->vk.base_array_layer,
.layerCount = view->vk.layer_count,
},
};
result = v3dv_create_image_view(device, &view_info, &tiled_view);
if (result != VK_SUCCESS) {
fprintf(stderr, "Failed to copy linear 2D image for sampling."
"Expect corruption.\n");
mtx_unlock(&device->meta.mtx);
continue;
}
}
view->shadow = v3dv_image_view_from_handle(tiled_view);
mtx_unlock(&device->meta.mtx);
/* Rewrite the descriptor to use the shadow view */
VkDescriptorImageInfo desc_image_info = {
.sampler = v3dv_sampler_to_handle(desc->sampler),
.imageView = tiled_view,
.imageLayout = VK_IMAGE_LAYOUT_GENERAL,
};
VkWriteDescriptorSet write = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = v3dv_descriptor_set_to_handle(set),
.dstBinding = i,
.dstArrayElement = 0, /* Assumes array_size is 1 */
.descriptorCount = 1,
.descriptorType = desc->type,
.pImageInfo = &desc_image_info,
};
v3dv_UpdateDescriptorSets(vk_device, 1, &write, 0, NULL);
/* Now we need to actually copy the pixel data from the linear image
* into the tiled image storage to ensure it is up-to-date.
*
* FIXME: ideally we would track if the linear image is dirty and skip
* this step otherwise, but that would be a bit of a pain.
*
* Note that we need to place the copy job *before* the current job in
* the command buffer state so we have the tiled image ready to process
* an upcoming draw call in the current job that samples from it.
*
* Also, we need to use the TFU path for this copy, as any other path
* will use the tile buffer and would require a new framebuffer setup,
* thus requiring extra work to stop and resume any in-flight render
* pass. Since we are converting a full 2D texture here the TFU should
* be able to handle this.
*/
struct VkImageCopy2 copy_region = {
.sType = VK_STRUCTURE_TYPE_IMAGE_COPY_2,
.srcSubresource = {
.aspectMask = view->vk.aspects,
.mipLevel = view->vk.base_mip_level,
.baseArrayLayer = view->vk.base_array_layer,
.layerCount = view->vk.layer_count,
},
.srcOffset = {0, 0, 0 },
.dstSubresource = {
.aspectMask = view->vk.aspects,
.mipLevel = view->vk.base_mip_level,
.baseArrayLayer = view->vk.base_array_layer,
.layerCount = view->vk.layer_count,
},
.dstOffset = { 0, 0, 0},
.extent = image->vk.extent,
};
struct v3dv_image *copy_src = image;
struct v3dv_image *copy_dst = v3dv_image_from_handle(tiled_image);
bool ok = v3dv_cmd_buffer_copy_image_tfu(cmd_buffer, copy_dst, copy_src,
&copy_region);
if (ok) {
/* This will emit the TFU job right before the current in-flight
* job (if any), since in-fight jobs are only added to the list
* when finished.
*/
struct v3dv_job *job =
list_last_entry(&cmd_buffer->jobs, struct v3dv_job, list_link);
assert(job->type == V3DV_JOB_TYPE_GPU_TFU);
/* Serialize the copy since we don't know who is producing the linear
* image and we need the image to be ready by the time the copy
* executes.
*/
job->serialize = V3DV_BARRIER_ALL;
} else {
fprintf(stderr, "Failed to copy linear 2D image for sampling."
"TFU doesn't support copy. Expect corruption.\n");
}
}
}
VKAPI_ATTR void VKAPI_CALL
v3dv_CmdBindDescriptorSets(VkCommandBuffer commandBuffer,
VkPipelineBindPoint pipelineBindPoint,
@@ -3448,6 +3678,13 @@ v3dv_CmdBindDescriptorSets(VkCommandBuffer commandBuffer,
descriptor_state->descriptor_sets[index] = set;
dirty_stages |= set->layout->shader_stages;
descriptor_state_changed = true;
/* Check if we are sampling from a linear 2D image. This is not
* supported in hardware, but may be required for some applications
* so we will transparently convert to tiled at the expense of
* performance.
*/
handle_sample_from_linear_image(cmd_buffer, set);
}
for (uint32_t j = 0; j < set->layout->dynamic_offset_count; j++, dyn_index++) {

View File

@@ -659,6 +659,19 @@ v3dv_DestroyImage(VkDevice _device,
if (image == NULL)
return;
/* If we have created a shadow tiled image for this image we must also free
* it (along with its memory allocation).
*/
if (image->shadow) {
assert(image->shadow->plane_count == 1);
v3dv_FreeMemory(_device,
v3dv_device_memory_to_handle(image->shadow->planes[0].mem),
pAllocator);
v3dv_DestroyImage(_device, v3dv_image_to_handle(image->shadow),
pAllocator);
image->shadow = NULL;
}
#ifdef ANDROID
assert(image->plane_count == 1);
if (image->is_native_buffer_memory)
@@ -817,6 +830,13 @@ v3dv_DestroyImageView(VkDevice _device,
if (image_view == NULL)
return;
if (image_view->shadow) {
v3dv_DestroyImageView(_device,
v3dv_image_view_to_handle(image_view->shadow),
pAllocator);
image_view->shadow = NULL;
}
vk_image_view_destroy(&device->vk, pAllocator, &image_view->vk);
}

View File

@@ -1253,6 +1253,15 @@ copy_image_tfu(struct v3dv_cmd_buffer *cmd_buffer,
return true;
}
inline bool
v3dv_cmd_buffer_copy_image_tfu(struct v3dv_cmd_buffer *cmd_buffer,
struct v3dv_image *dst,
struct v3dv_image *src,
const VkImageCopy2 *region)
{
return copy_image_tfu(cmd_buffer, dst, src, region);
}
/**
* Returns true if the implementation supports the requested operation (even if
* it failed to process it, for example, due to an out-of-memory error).

View File

@@ -728,6 +728,11 @@ struct v3dv_image {
VkFormat vk_format;
} planes[V3DV_MAX_PLANE_COUNT];
/* Used only when sampling a linear texture (which V3D doesn't support).
* This holds a tiled copy of the image we can use for that purpose.
*/
struct v3dv_image *shadow;
#ifdef ANDROID
/* Image is backed by VK_ANDROID_native_buffer, */
bool is_native_buffer_memory;
@@ -800,6 +805,11 @@ struct v3dv_image_view {
*/
uint8_t texture_shader_state[2][V3DV_TEXTURE_SHADER_STATE_LENGTH];
} planes[V3DV_MAX_PLANE_COUNT];
/* Used only when sampling a linear texture (which V3D doesn't support).
* This would represent a view over the tiled shadow image.
*/
struct v3dv_image_view *shadow;
};
VkResult v3dv_create_image_view(struct v3dv_device *device,
@@ -1815,6 +1825,11 @@ bool v3dv_cmd_buffer_check_needs_store(const struct v3dv_cmd_buffer_state *state
void v3dv_cmd_buffer_emit_pipeline_barrier(struct v3dv_cmd_buffer *cmd_buffer,
const VkDependencyInfoKHR *info);
bool v3dv_cmd_buffer_copy_image_tfu(struct v3dv_cmd_buffer *cmd_buffer,
struct v3dv_image *dst,
struct v3dv_image *src,
const VkImageCopy2 *region);
struct v3dv_event {
struct vk_object_base base;