/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/vulkan/vulkan_texture_cache.h" #include #include "xenia/base/assert.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/gpu/vulkan/vulkan_command_processor.h" #include "xenia/ui/vulkan/vulkan_util.h" namespace xe { namespace gpu { namespace vulkan { // Generated with `xb buildshaders`. namespace shaders { #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_128bpb_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_128bpb_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_16bpb_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_16bpb_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_32bpb_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_32bpb_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_64bpb_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_64bpb_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_8bpb_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_8bpb_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_ctx1_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_unorm_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_unorm_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxn_rg8_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt1_rgba8_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3_rgba8_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3a_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3aas1111_argb4_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt5_rgba8_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt5a_r8_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_snorm_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_snorm_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_snorm_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_snorm_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_snorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_snorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_unorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_unorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r4g4b4a4_a4r4g4b4_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r4g4b4a4_a4r4g4b4_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b5a1_b5g5r5a1_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b5a1_b5g5r5a1_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g6b5_b5g6r5_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g6b5_b5g6r5_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_snorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_snorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_unorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_unorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_snorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_snorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_unorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_unorm_float_scaled_cs.h" } // namespace shaders VulkanTextureCache::~VulkanTextureCache() { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); VkDevice device = provider.device(); if (null_image_view_3d_ != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, null_image_view_3d_, nullptr); } if (null_image_view_cube_ != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, null_image_view_cube_, nullptr); } if (null_image_view_2d_array_ != VK_NULL_HANDLE) { dfn.vkDestroyImageView(device, null_image_view_2d_array_, nullptr); } if (null_image_3d_ != VK_NULL_HANDLE) { dfn.vkDestroyImage(device, null_image_3d_, nullptr); } if (null_image_2d_array_cube_ != VK_NULL_HANDLE) { dfn.vkDestroyImage(device, null_image_2d_array_cube_, nullptr); } for (VkDeviceMemory null_images_memory : null_images_memory_) { if (null_images_memory != VK_NULL_HANDLE) { dfn.vkFreeMemory(device, null_images_memory, nullptr); } } } void VulkanTextureCache::BeginSubmission(uint64_t new_submission_index) { TextureCache::BeginSubmission(new_submission_index); if (!null_images_cleared_) { VkImage null_images[] = {null_image_2d_array_cube_, null_image_3d_}; VkImageSubresourceRange null_image_subresource_range( ui::vulkan::util::InitializeSubresourceRange()); for (size_t i = 0; i < xe::countof(null_images); ++i) { command_processor_.PushImageMemoryBarrier( null_images[i], null_image_subresource_range, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } command_processor_.SubmitBarriers(true); DeferredCommandBuffer& command_buffer = command_processor_.deferred_command_buffer(); // TODO(Triang3l): Find the return value for invalid texture fetch constants // on the real hardware. VkClearColorValue null_image_clear_color; null_image_clear_color.float32[0] = 0.0f; null_image_clear_color.float32[1] = 0.0f; null_image_clear_color.float32[2] = 0.0f; null_image_clear_color.float32[3] = 0.0f; for (size_t i = 0; i < xe::countof(null_images); ++i) { command_buffer.CmdVkClearColorImage( null_images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &null_image_clear_color, 1, &null_image_subresource_range); } for (size_t i = 0; i < xe::countof(null_images); ++i) { command_processor_.PushImageMemoryBarrier( null_images[i], null_image_subresource_range, VK_PIPELINE_STAGE_TRANSFER_BIT, guest_shader_pipeline_stages_, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } null_images_cleared_ = true; } } uint32_t VulkanTextureCache::GetHostFormatSwizzle(TextureKey key) const { // TODO(Triang3l): Implement GetHostFormatSwizzle. return xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA; } uint32_t VulkanTextureCache::GetMaxHostTextureWidthHeight( xenos::DataDimension dimension) const { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDeviceLimits& device_limits = provider.device_properties().limits; switch (dimension) { case xenos::DataDimension::k1D: case xenos::DataDimension::k2DOrStacked: // 1D and 2D are emulated as 2D arrays. return device_limits.maxImageDimension2D; case xenos::DataDimension::k3D: return device_limits.maxImageDimension3D; case xenos::DataDimension::kCube: return device_limits.maxImageDimensionCube; default: assert_unhandled_case(dimension); return 0; } } uint32_t VulkanTextureCache::GetMaxHostTextureDepthOrArraySize( xenos::DataDimension dimension) const { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const VkPhysicalDeviceLimits& device_limits = provider.device_properties().limits; switch (dimension) { case xenos::DataDimension::k1D: case xenos::DataDimension::k2DOrStacked: // 1D and 2D are emulated as 2D arrays. return device_limits.maxImageArrayLayers; case xenos::DataDimension::k3D: return device_limits.maxImageDimension3D; case xenos::DataDimension::kCube: // Not requesting the imageCubeArray feature, and the Xenos doesn't // support cube map arrays. return 6; default: assert_unhandled_case(dimension); return 0; } } std::unique_ptr VulkanTextureCache::CreateTexture( TextureKey key) { // TODO(Triang3l): Implement CreateTexture. return std::unique_ptr(new VulkanTexture(*this, key)); } bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, bool load_mips) { // TODO(Triang3l): Implement LoadTextureDataFromResidentMemoryImpl. return true; } VulkanTextureCache::VulkanTexture::VulkanTexture( VulkanTextureCache& texture_cache, const TextureKey& key) : Texture(texture_cache, key) {} VulkanTextureCache::VulkanTextureCache( const RegisterFile& register_file, VulkanSharedMemory& shared_memory, uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y, VulkanCommandProcessor& command_processor, VkPipelineStageFlags guest_shader_pipeline_stages) : TextureCache(register_file, shared_memory, draw_resolution_scale_x, draw_resolution_scale_y), command_processor_(command_processor), guest_shader_pipeline_stages_(guest_shader_pipeline_stages) { // TODO(Triang3l): Support draw resolution scaling. assert_true(draw_resolution_scale_x == 1 && draw_resolution_scale_y == 1); } bool VulkanTextureCache::Initialize() { const ui::vulkan::VulkanProvider& provider = command_processor_.GetVulkanProvider(); const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); VkDevice device = provider.device(); const VkPhysicalDevicePortabilitySubsetFeaturesKHR* device_portability_subset_features = provider.device_portability_subset_features(); // Null images as a replacement for unneeded bindings and for bindings for // which the real image hasn't been created. VkImageCreateInfo null_image_create_info; null_image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; null_image_create_info.pNext = nullptr; null_image_create_info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; null_image_create_info.imageType = VK_IMAGE_TYPE_2D; // Four components to return (0, 0, 0, 0). // TODO(Triang3l): Find the return value for invalid texture fetch constants // on the real hardware. null_image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM; null_image_create_info.extent.width = 1; null_image_create_info.extent.height = 1; null_image_create_info.extent.depth = 1; null_image_create_info.mipLevels = 1; null_image_create_info.arrayLayers = 6; null_image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; null_image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; null_image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; null_image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; null_image_create_info.queueFamilyIndexCount = 0; null_image_create_info.pQueueFamilyIndices = nullptr; null_image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; if (dfn.vkCreateImage(device, &null_image_create_info, nullptr, &null_image_2d_array_cube_) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to create the null 2D array and cube " "image"); return false; } null_image_create_info.flags &= ~VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; null_image_create_info.imageType = VK_IMAGE_TYPE_3D; null_image_create_info.arrayLayers = 1; if (dfn.vkCreateImage(device, &null_image_create_info, nullptr, &null_image_3d_) != VK_SUCCESS) { XELOGE("VulkanTextureCache: Failed to create the null 3D image"); return false; } VkMemoryRequirements null_image_memory_requirements_2d_array_cube_; dfn.vkGetImageMemoryRequirements( device, null_image_2d_array_cube_, &null_image_memory_requirements_2d_array_cube_); VkMemoryRequirements null_image_memory_requirements_3d_; dfn.vkGetImageMemoryRequirements(device, null_image_3d_, &null_image_memory_requirements_3d_); uint32_t null_image_memory_type_common = ui::vulkan::util::ChooseMemoryType( provider, null_image_memory_requirements_2d_array_cube_.memoryTypeBits & null_image_memory_requirements_3d_.memoryTypeBits, ui::vulkan::util::MemoryPurpose::kDeviceLocal); if (null_image_memory_type_common != UINT32_MAX) { // Place both null images in one memory allocation because maximum total // memory allocation count is limited. VkDeviceSize null_image_memory_offset_3d_ = xe::align(null_image_memory_requirements_2d_array_cube_.size, std::max(null_image_memory_requirements_3d_.alignment, VkDeviceSize(1))); VkMemoryAllocateInfo null_image_memory_allocate_info; null_image_memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; null_image_memory_allocate_info.pNext = nullptr; null_image_memory_allocate_info.allocationSize = null_image_memory_offset_3d_ + null_image_memory_requirements_3d_.size; null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_common; if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr, &null_images_memory_[0]) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to allocate the memory for the null " "images"); return false; } if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_, null_images_memory_[0], 0) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to bind the memory to the null 2D array " "and cube image"); return false; } if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[0], null_image_memory_offset_3d_) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to bind the memory to the null 3D image"); return false; } } else { // Place each null image in separate allocations. uint32_t null_image_memory_type_2d_array_cube_ = ui::vulkan::util::ChooseMemoryType( provider, null_image_memory_requirements_2d_array_cube_.memoryTypeBits, ui::vulkan::util::MemoryPurpose::kDeviceLocal); uint32_t null_image_memory_type_3d_ = ui::vulkan::util::ChooseMemoryType( provider, null_image_memory_requirements_3d_.memoryTypeBits, ui::vulkan::util::MemoryPurpose::kDeviceLocal); if (null_image_memory_type_2d_array_cube_ == UINT32_MAX || null_image_memory_type_3d_ == UINT32_MAX) { XELOGE( "VulkanTextureCache: Failed to get the memory types for the null " "images"); return false; } VkMemoryAllocateInfo null_image_memory_allocate_info; VkMemoryAllocateInfo* null_image_memory_allocate_info_last = &null_image_memory_allocate_info; null_image_memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; null_image_memory_allocate_info.pNext = nullptr; null_image_memory_allocate_info.allocationSize = null_image_memory_requirements_2d_array_cube_.size; null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_2d_array_cube_; VkMemoryDedicatedAllocateInfoKHR null_image_memory_dedicated_allocate_info; if (provider.device_extensions().khr_dedicated_allocation) { null_image_memory_allocate_info_last->pNext = &null_image_memory_dedicated_allocate_info; null_image_memory_allocate_info_last = reinterpret_cast( &null_image_memory_dedicated_allocate_info); null_image_memory_dedicated_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR; null_image_memory_dedicated_allocate_info.pNext = nullptr; null_image_memory_dedicated_allocate_info.image = null_image_2d_array_cube_; null_image_memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE; } if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr, &null_images_memory_[0]) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to allocate the memory for the null 2D " "array and cube image"); return false; } if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_, null_images_memory_[0], 0) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to bind the memory to the null 2D array " "and cube image"); return false; } null_image_memory_allocate_info.allocationSize = null_image_memory_requirements_3d_.size; null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_3d_; null_image_memory_dedicated_allocate_info.image = null_image_3d_; if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr, &null_images_memory_[1]) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to allocate the memory for the null 3D " "image"); return false; } if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[1], 0) != VK_SUCCESS) { XELOGE( "VulkanTextureCache: Failed to bind the memory to the null 3D image"); return false; } } VkImageViewCreateInfo null_image_view_create_info; null_image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; null_image_view_create_info.pNext = nullptr; null_image_view_create_info.flags = 0; null_image_view_create_info.image = null_image_2d_array_cube_; null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; null_image_view_create_info.format = null_image_create_info.format; // TODO(Triang3l): Find the return value for invalid texture fetch constants // on the real hardware. // Micro-optimization if this has any effect on the host GPU at all, use only // constant components instead of the real texels. The image will be cleared // to (0, 0, 0, 0) anyway. VkComponentSwizzle null_image_view_swizzle = (!device_portability_subset_features || device_portability_subset_features->imageViewFormatSwizzle) ? VK_COMPONENT_SWIZZLE_ZERO : VK_COMPONENT_SWIZZLE_IDENTITY; null_image_view_create_info.components.r = null_image_view_swizzle; null_image_view_create_info.components.g = null_image_view_swizzle; null_image_view_create_info.components.b = null_image_view_swizzle; null_image_view_create_info.components.a = null_image_view_swizzle; null_image_view_create_info.subresourceRange = ui::vulkan::util::InitializeSubresourceRange( VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, 1); if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr, &null_image_view_2d_array_) != VK_SUCCESS) { XELOGE("VulkanTextureCache: Failed to create the null 2D array image view"); return false; } null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; null_image_view_create_info.subresourceRange.layerCount = 6; if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr, &null_image_view_cube_) != VK_SUCCESS) { XELOGE("VulkanTextureCache: Failed to create the null cube image view"); return false; } null_image_view_create_info.image = null_image_3d_; null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D; null_image_view_create_info.subresourceRange.layerCount = 1; if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr, &null_image_view_3d_) != VK_SUCCESS) { XELOGE("VulkanTextureCache: Failed to create the null 3D image view"); return false; } null_images_cleared_ = false; return true; } } // namespace vulkan } // namespace gpu } // namespace xe