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:

committed by
Marge Bot

parent
151f78150b
commit
95f881adbd
@@ -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,
|
||||
©_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++) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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).
|
||||
|
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user