diff --git a/src/broadcom/vulkan/v3dv_cmd_buffer.c b/src/broadcom/vulkan/v3dv_cmd_buffer.c index 96360a96b44..d4c7fe9d657 100644 --- a/src/broadcom/vulkan/v3dv_cmd_buffer.c +++ b/src/broadcom/vulkan/v3dv_cmd_buffer.c @@ -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++) { diff --git a/src/broadcom/vulkan/v3dv_image.c b/src/broadcom/vulkan/v3dv_image.c index fe6e7a9b97f..dfad789b5e6 100644 --- a/src/broadcom/vulkan/v3dv_image.c +++ b/src/broadcom/vulkan/v3dv_image.c @@ -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); } diff --git a/src/broadcom/vulkan/v3dv_meta_copy.c b/src/broadcom/vulkan/v3dv_meta_copy.c index da02de0f3ad..e59755e75b0 100644 --- a/src/broadcom/vulkan/v3dv_meta_copy.c +++ b/src/broadcom/vulkan/v3dv_meta_copy.c @@ -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). diff --git a/src/broadcom/vulkan/v3dv_private.h b/src/broadcom/vulkan/v3dv_private.h index 943be02fb71..dd5959cec7d 100644 --- a/src/broadcom/vulkan/v3dv_private.h +++ b/src/broadcom/vulkan/v3dv_private.h @@ -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;