|
|
|
|
@@ -605,14 +605,34 @@ void VulkanTextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|
|
|
|
|
|
|
|
|
VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(
|
|
|
|
|
uint32_t fetch_constant_index, xenos::FetchOpDimension dimension,
|
|
|
|
|
bool is_signed) const {
|
|
|
|
|
bool is_signed) {
|
|
|
|
|
VkImageView image_view = VK_NULL_HANDLE;
|
|
|
|
|
const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index);
|
|
|
|
|
if (binding && AreDimensionsCompatible(dimension, binding->key.dimension)) {
|
|
|
|
|
const VulkanTextureBinding& vulkan_binding =
|
|
|
|
|
vulkan_texture_bindings_[fetch_constant_index];
|
|
|
|
|
image_view = is_signed ? vulkan_binding.image_view_signed
|
|
|
|
|
: vulkan_binding.image_view_unsigned;
|
|
|
|
|
// Check for 3D texture sampled as 2D.
|
|
|
|
|
bool force_special_view =
|
|
|
|
|
(dimension == xenos::FetchOpDimension::k2D &&
|
|
|
|
|
binding->key.dimension == xenos::DataDimension::k3D);
|
|
|
|
|
|
|
|
|
|
if (force_special_view) {
|
|
|
|
|
// Get the appropriate texture for signed/unsigned.
|
|
|
|
|
Texture* texture = nullptr;
|
|
|
|
|
if (is_signed && IsSignedVersionSeparateForFormat(binding->key)) {
|
|
|
|
|
texture = binding->texture_signed;
|
|
|
|
|
} else {
|
|
|
|
|
texture = binding->texture;
|
|
|
|
|
}
|
|
|
|
|
if (texture) {
|
|
|
|
|
image_view =
|
|
|
|
|
static_cast<VulkanTexture*>(texture)->GetOrCreate3DAs2DImageView(
|
|
|
|
|
is_signed, binding->host_swizzle);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const VulkanTextureBinding& vulkan_binding =
|
|
|
|
|
vulkan_texture_bindings_[fetch_constant_index];
|
|
|
|
|
image_view = is_signed ? vulkan_binding.image_view_signed
|
|
|
|
|
: vulkan_binding.image_view_unsigned;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (image_view != VK_NULL_HANDLE) {
|
|
|
|
|
return image_view;
|
|
|
|
|
@@ -1081,7 +1101,8 @@ std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
|
|
|
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
|
|
|
// For scaled resolve textures with mips, we need transfer source to generate
|
|
|
|
|
// mip levels via blit from the base level.
|
|
|
|
|
if (key.scaled_resolve && key.mip_max_level > 0) {
|
|
|
|
|
// For 3D textures, we need transfer source to support 3D-to-2D conversion.
|
|
|
|
|
if ((key.scaled_resolve && key.mip_max_level > 0) || is_3d) {
|
|
|
|
|
image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
|
}
|
|
|
|
|
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
|
@@ -1765,8 +1786,10 @@ void VulkanTextureCache::UpdateTextureBindingsImpl(
|
|
|
|
|
|
|
|
|
|
VulkanTextureCache::VulkanTexture::VulkanTexture(
|
|
|
|
|
VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
|
|
|
|
|
VmaAllocation allocation)
|
|
|
|
|
: Texture(texture_cache, key), image_(image), allocation_(allocation) {
|
|
|
|
|
VmaAllocation allocation, bool track_usage)
|
|
|
|
|
: Texture(texture_cache, key, track_usage),
|
|
|
|
|
image_(image),
|
|
|
|
|
allocation_(allocation) {
|
|
|
|
|
VmaAllocationInfo allocation_info;
|
|
|
|
|
vmaGetAllocationInfo(texture_cache.vma_allocator_, allocation_,
|
|
|
|
|
&allocation_info);
|
|
|
|
|
@@ -1783,6 +1806,15 @@ VulkanTextureCache::VulkanTexture::~VulkanTexture() {
|
|
|
|
|
for (const auto& view_pair : views_) {
|
|
|
|
|
dfn.vkDestroyImageView(device, view_pair.second, nullptr);
|
|
|
|
|
}
|
|
|
|
|
// Clean up 3D-as-2D image views. The texture_3d_as_2d_ wrapper will clean
|
|
|
|
|
// itself up via unique_ptr destructor.
|
|
|
|
|
if (image_view_3d_as_2d_unsigned_ != VK_NULL_HANDLE) {
|
|
|
|
|
dfn.vkDestroyImageView(device, image_view_3d_as_2d_unsigned_, nullptr);
|
|
|
|
|
}
|
|
|
|
|
if (image_view_3d_as_2d_signed_ != VK_NULL_HANDLE) {
|
|
|
|
|
dfn.vkDestroyImageView(device, image_view_3d_as_2d_signed_, nullptr);
|
|
|
|
|
}
|
|
|
|
|
// texture_3d_as_2d_ is a unique_ptr and will destroy its image/allocation.
|
|
|
|
|
vmaDestroyImage(vulkan_texture_cache.vma_allocator_, image_, allocation_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1877,6 +1909,235 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView(
|
|
|
|
|
bool is_signed, uint32_t host_swizzle) {
|
|
|
|
|
int32_t mode = cvars::gpu_3d_to_2d_texture_mode;
|
|
|
|
|
|
|
|
|
|
// Mode 0: Feature disabled.
|
|
|
|
|
if (mode == 0) {
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return cached view if available.
|
|
|
|
|
VkImageView& cached_view =
|
|
|
|
|
is_signed ? image_view_3d_as_2d_signed_ : image_view_3d_as_2d_unsigned_;
|
|
|
|
|
if (cached_view != VK_NULL_HANDLE) {
|
|
|
|
|
return cached_view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VulkanTextureCache& vulkan_texture_cache =
|
|
|
|
|
static_cast<VulkanTextureCache&>(texture_cache());
|
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
|
|
|
vulkan_texture_cache.command_processor_.GetVulkanDevice();
|
|
|
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
|
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
|
|
|
|
|
|
// Create the 2D texture wrapper if it doesn't exist.
|
|
|
|
|
if (!texture_3d_as_2d_) {
|
|
|
|
|
const HostFormatPair& host_format_pair =
|
|
|
|
|
vulkan_texture_cache.GetHostFormatPair(key());
|
|
|
|
|
VkFormat format = host_format_pair.format_unsigned.format;
|
|
|
|
|
if (format == VK_FORMAT_UNDEFINED) {
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkImageCreateInfo image_create_info = {};
|
|
|
|
|
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
|
|
|
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
|
|
|
|
image_create_info.format = format;
|
|
|
|
|
image_create_info.extent.width = key().GetWidth();
|
|
|
|
|
image_create_info.extent.height = key().GetHeight();
|
|
|
|
|
image_create_info.extent.depth = 1;
|
|
|
|
|
image_create_info.mipLevels = 1;
|
|
|
|
|
image_create_info.arrayLayers = 1;
|
|
|
|
|
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
|
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
|
|
|
image_create_info.usage =
|
|
|
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
|
|
|
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
|
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
|
|
|
|
|
|
VmaAllocationCreateInfo allocation_create_info = {};
|
|
|
|
|
allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
|
|
|
|
|
|
|
|
|
VkImage image_2d;
|
|
|
|
|
VmaAllocation allocation_2d;
|
|
|
|
|
if (vmaCreateImage(vulkan_texture_cache.vma_allocator_, &image_create_info,
|
|
|
|
|
&allocation_create_info, &image_2d, &allocation_2d,
|
|
|
|
|
nullptr) != VK_SUCCESS) {
|
|
|
|
|
XELOGE("VulkanTexture: Failed to create 3D-as-2D image");
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a modified key for the 2D wrapper with depth=1 and
|
|
|
|
|
// mip_max_level=0. Keep dimension as k3D so that in Mode 2, LoadTextureData
|
|
|
|
|
// uses 3D tiling math to correctly read slice 0 from the 3D-tiled guest
|
|
|
|
|
// memory.
|
|
|
|
|
TextureKey key_2d = key();
|
|
|
|
|
key_2d.depth_or_array_size_minus_1 = 0;
|
|
|
|
|
key_2d.mip_max_level = 0;
|
|
|
|
|
|
|
|
|
|
if (mode == 1) {
|
|
|
|
|
// Mode 1: GPU copy - copy slice 0 from the 3D image to the 2D image.
|
|
|
|
|
DeferredCommandBuffer& command_buffer =
|
|
|
|
|
vulkan_texture_cache.command_processor_.deferred_command_buffer();
|
|
|
|
|
|
|
|
|
|
// Get the current layout from usage tracking (like D3D12 does).
|
|
|
|
|
VkPipelineStageFlags src_stage_mask;
|
|
|
|
|
VkAccessFlags src_access_mask;
|
|
|
|
|
VkImageLayout src_old_layout;
|
|
|
|
|
vulkan_texture_cache.GetTextureUsageMasks(
|
|
|
|
|
usage_, src_stage_mask, src_access_mask, src_old_layout);
|
|
|
|
|
|
|
|
|
|
// Transition 2D image to transfer destination.
|
|
|
|
|
VkImageMemoryBarrier barrier_2d_to_dst = {};
|
|
|
|
|
barrier_2d_to_dst.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
|
|
|
barrier_2d_to_dst.srcAccessMask = 0;
|
|
|
|
|
barrier_2d_to_dst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
|
barrier_2d_to_dst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
|
barrier_2d_to_dst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
|
|
|
barrier_2d_to_dst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_2d_to_dst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_2d_to_dst.image = image_2d;
|
|
|
|
|
barrier_2d_to_dst.subresourceRange =
|
|
|
|
|
ui::vulkan::util::InitializeSubresourceRange();
|
|
|
|
|
command_buffer.CmdVkPipelineBarrier(
|
|
|
|
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
|
|
|
|
0, nullptr, 0, nullptr, 1, &barrier_2d_to_dst);
|
|
|
|
|
|
|
|
|
|
// Transition 3D image to transfer source.
|
|
|
|
|
VkImageMemoryBarrier barrier_3d_to_src = {};
|
|
|
|
|
barrier_3d_to_src.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
|
|
|
barrier_3d_to_src.srcAccessMask = src_access_mask;
|
|
|
|
|
barrier_3d_to_src.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
|
barrier_3d_to_src.oldLayout = src_old_layout;
|
|
|
|
|
barrier_3d_to_src.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
|
barrier_3d_to_src.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_3d_to_src.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_3d_to_src.image = image_;
|
|
|
|
|
barrier_3d_to_src.subresourceRange =
|
|
|
|
|
ui::vulkan::util::InitializeSubresourceRange();
|
|
|
|
|
command_buffer.CmdVkPipelineBarrier(
|
|
|
|
|
src_stage_mask, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0,
|
|
|
|
|
nullptr, 1, &barrier_3d_to_src);
|
|
|
|
|
|
|
|
|
|
// Use vkCmdCopyImage for the slice copy.
|
|
|
|
|
// This works for all formats including compressed (BC) formats.
|
|
|
|
|
VkImageCopy copy_region = {};
|
|
|
|
|
copy_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
|
|
|
copy_region.srcSubresource.mipLevel = 0;
|
|
|
|
|
copy_region.srcSubresource.baseArrayLayer = 0;
|
|
|
|
|
copy_region.srcSubresource.layerCount = 1;
|
|
|
|
|
copy_region.srcOffset = {0, 0, 0};
|
|
|
|
|
copy_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
|
|
|
copy_region.dstSubresource.mipLevel = 0;
|
|
|
|
|
copy_region.dstSubresource.baseArrayLayer = 0;
|
|
|
|
|
copy_region.dstSubresource.layerCount = 1;
|
|
|
|
|
copy_region.dstOffset = {0, 0, 0};
|
|
|
|
|
copy_region.extent.width = key().GetWidth();
|
|
|
|
|
copy_region.extent.height = key().GetHeight();
|
|
|
|
|
copy_region.extent.depth = 1;
|
|
|
|
|
command_buffer.CmdVkCopyImage(
|
|
|
|
|
image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image_2d,
|
|
|
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region);
|
|
|
|
|
|
|
|
|
|
// Transition 3D image back to guest shader sampled state.
|
|
|
|
|
VkPipelineStageFlags dst_stage_mask;
|
|
|
|
|
VkAccessFlags dst_access_mask;
|
|
|
|
|
VkImageLayout new_layout;
|
|
|
|
|
vulkan_texture_cache.GetTextureUsageMasks(Usage::kGuestShaderSampled,
|
|
|
|
|
dst_stage_mask, dst_access_mask,
|
|
|
|
|
new_layout);
|
|
|
|
|
barrier_3d_to_src.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
|
barrier_3d_to_src.dstAccessMask = dst_access_mask;
|
|
|
|
|
barrier_3d_to_src.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
|
barrier_3d_to_src.newLayout = new_layout;
|
|
|
|
|
command_buffer.CmdVkPipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
|
|
|
dst_stage_mask, 0, 0, nullptr, 0,
|
|
|
|
|
nullptr, 1, &barrier_3d_to_src);
|
|
|
|
|
// Update tracking - texture is now in guest shader sampled state.
|
|
|
|
|
SetUsage(Usage::kGuestShaderSampled);
|
|
|
|
|
|
|
|
|
|
// Transition 2D image to shader read.
|
|
|
|
|
barrier_2d_to_dst.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
|
barrier_2d_to_dst.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
|
barrier_2d_to_dst.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
|
|
|
barrier_2d_to_dst.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
|
command_buffer.CmdVkPipelineBarrier(
|
|
|
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
|
|
|
0, 0, nullptr, 0, nullptr, 1, &barrier_2d_to_dst);
|
|
|
|
|
|
|
|
|
|
// Create the wrapper after successful GPU copy.
|
|
|
|
|
texture_3d_as_2d_.reset(new VulkanTexture(
|
|
|
|
|
vulkan_texture_cache, key_2d, image_2d, allocation_2d, false));
|
|
|
|
|
texture_3d_as_2d_->SetUsage(Usage::kGuestShaderSampled);
|
|
|
|
|
} else {
|
|
|
|
|
// Mode 2: CPU re-upload - reload texture data from guest memory.
|
|
|
|
|
// Create the wrapper first so LoadTextureData can work with it.
|
|
|
|
|
texture_3d_as_2d_.reset(new VulkanTexture(
|
|
|
|
|
vulkan_texture_cache, key_2d, image_2d, allocation_2d, false));
|
|
|
|
|
|
|
|
|
|
if (!vulkan_texture_cache.LoadTextureData(*texture_3d_as_2d_)) {
|
|
|
|
|
XELOGE("VulkanTexture: Failed to untile 3D-as-2D data");
|
|
|
|
|
texture_3d_as_2d_.reset();
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadTextureData leaves the texture in TRANSFER_DST state.
|
|
|
|
|
// Transition to shader read state.
|
|
|
|
|
DeferredCommandBuffer& command_buffer =
|
|
|
|
|
vulkan_texture_cache.command_processor_.deferred_command_buffer();
|
|
|
|
|
VkImageMemoryBarrier barrier_to_shader = {};
|
|
|
|
|
barrier_to_shader.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
|
|
|
barrier_to_shader.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
|
barrier_to_shader.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
|
barrier_to_shader.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
|
|
|
barrier_to_shader.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
|
barrier_to_shader.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_to_shader.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
barrier_to_shader.image = texture_3d_as_2d_->image();
|
|
|
|
|
barrier_to_shader.subresourceRange =
|
|
|
|
|
ui::vulkan::util::InitializeSubresourceRange();
|
|
|
|
|
command_buffer.CmdVkPipelineBarrier(
|
|
|
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
|
|
|
0, 0, nullptr, 0, nullptr, 1, &barrier_to_shader);
|
|
|
|
|
texture_3d_as_2d_->SetUsage(Usage::kGuestShaderSampled);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the image view.
|
|
|
|
|
const HostFormatPair& host_format_pair =
|
|
|
|
|
vulkan_texture_cache.GetHostFormatPair(key());
|
|
|
|
|
VkFormat format = (is_signed ? host_format_pair.format_signed
|
|
|
|
|
: host_format_pair.format_unsigned)
|
|
|
|
|
.format;
|
|
|
|
|
if (format == VK_FORMAT_UNDEFINED) {
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkImageViewCreateInfo view_create_info = {};
|
|
|
|
|
view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
|
|
|
view_create_info.image = texture_3d_as_2d_->image();
|
|
|
|
|
// Use 2D_ARRAY to match shader expectations (Dim = 2D, Arrayed = 1).
|
|
|
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
|
|
|
view_create_info.format = format;
|
|
|
|
|
view_create_info.components.r = GetComponentSwizzle(host_swizzle, 0);
|
|
|
|
|
view_create_info.components.g = GetComponentSwizzle(host_swizzle, 1);
|
|
|
|
|
view_create_info.components.b = GetComponentSwizzle(host_swizzle, 2);
|
|
|
|
|
view_create_info.components.a = GetComponentSwizzle(host_swizzle, 3);
|
|
|
|
|
view_create_info.subresourceRange =
|
|
|
|
|
ui::vulkan::util::InitializeSubresourceRange();
|
|
|
|
|
view_create_info.subresourceRange.layerCount = 1;
|
|
|
|
|
|
|
|
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr, &cached_view) !=
|
|
|
|
|
VK_SUCCESS) {
|
|
|
|
|
XELOGE("VulkanTexture: Failed to create 3D-as-2D image view");
|
|
|
|
|
return VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cached_view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VulkanTextureCache::VulkanTextureCache(
|
|
|
|
|
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
|
|
|
|
|
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
|
|
|
|
|
|