diff --git a/src/xenia/gpu/vulkan/deferred_command_buffer.cc b/src/xenia/gpu/vulkan/deferred_command_buffer.cc index 98d42865d..fef52b9f4 100644 --- a/src/xenia/gpu/vulkan/deferred_command_buffer.cc +++ b/src/xenia/gpu/vulkan/deferred_command_buffer.cc @@ -134,6 +134,17 @@ void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) { attachments, args.rect_count, rects); } break; + case Command::kVkClearColorImage: { + auto& args = *reinterpret_cast(stream); + dfn.vkCmdClearColorImage( + command_buffer, args.image, args.image_layout, &args.color, + args.range_count, + reinterpret_cast( + reinterpret_cast(stream) + + xe::align(sizeof(ArgsVkClearColorImage), + alignof(VkImageSubresourceRange)))); + } break; + case Command::kVkCopyBuffer: { auto& args = *reinterpret_cast(stream); dfn.vkCmdCopyBuffer( diff --git a/src/xenia/gpu/vulkan/deferred_command_buffer.h b/src/xenia/gpu/vulkan/deferred_command_buffer.h index e3605f1e6..60b216354 100644 --- a/src/xenia/gpu/vulkan/deferred_command_buffer.h +++ b/src/xenia/gpu/vulkan/deferred_command_buffer.h @@ -163,6 +163,30 @@ class DeferredCommandBuffer { std::memcpy(rects_arg, rects, sizeof(VkClearRect) * rect_count); } + VkImageSubresourceRange* CmdClearColorImageEmplace( + VkImage image, VkImageLayout image_layout, const VkClearColorValue* color, + uint32_t range_count) { + const size_t header_size = xe::align(sizeof(ArgsVkClearColorImage), + alignof(VkImageSubresourceRange)); + uint8_t* args_ptr = reinterpret_cast(WriteCommand( + Command::kVkClearColorImage, + header_size + sizeof(VkImageSubresourceRange) * range_count)); + auto& args = *reinterpret_cast(args_ptr); + args.image = image; + args.image_layout = image_layout; + args.color = *color; + args.range_count = range_count; + return reinterpret_cast(args_ptr + header_size); + } + void CmdVkClearColorImage(VkImage image, VkImageLayout image_layout, + const VkClearColorValue* color, + uint32_t range_count, + const VkImageSubresourceRange* ranges) { + std::memcpy( + CmdClearColorImageEmplace(image, image_layout, color, range_count), + ranges, sizeof(VkImageSubresourceRange) * range_count); + } + VkBufferCopy* CmdCopyBufferEmplace(VkBuffer src_buffer, VkBuffer dst_buffer, uint32_t region_count) { const size_t header_size = @@ -316,6 +340,7 @@ class DeferredCommandBuffer { kVkBindPipeline, kVkBindVertexBuffers, kVkClearAttachments, + kVkClearColorImage, kVkCopyBuffer, kVkDispatch, kVkDraw, @@ -386,6 +411,15 @@ class DeferredCommandBuffer { static_assert(alignof(VkClearRect) <= alignof(uintmax_t)); }; + struct ArgsVkClearColorImage { + VkImage image; + VkImageLayout image_layout; + VkClearColorValue color; + uint32_t range_count; + // Followed by aligned VkImageSubresourceRange[]. + static_assert(alignof(VkImageSubresourceRange) <= alignof(uintmax_t)); + }; + struct ArgsVkCopyBuffer { VkBuffer src_buffer; VkBuffer dst_buffer; diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 9556f9efe..c67a59367 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -229,6 +229,15 @@ bool VulkanCommandProcessor::SetupContext() { return false; } + // TODO(Triang3l): Actual draw resolution scale. + texture_cache_ = + VulkanTextureCache::Create(*register_file_, *shared_memory_, 1, 1, *this, + guest_shader_pipeline_stages_); + if (!texture_cache_) { + XELOGE("Failed to initialize the texture cache"); + return false; + } + // Shared memory and EDRAM common bindings. VkDescriptorPoolSize descriptor_pool_sizes[1]; descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; @@ -537,6 +546,8 @@ void VulkanCommandProcessor::ShutdownContext() { dfn.vkDestroyDescriptorPool, device, shared_memory_and_edram_descriptor_pool_); + texture_cache_.reset(); + pipeline_cache_.reset(); render_target_cache_.reset(); @@ -1747,6 +1758,8 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss( render_target_cache_->CompletedSubmissionUpdated(); + texture_cache_->CompletedSubmissionUpdated(submission_completed_); + // Destroy outdated swap objects. while (!swap_framebuffers_outdated_.empty()) { const auto& framebuffer_pair = swap_framebuffers_outdated_.front(); @@ -1829,6 +1842,8 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) { current_graphics_descriptor_sets_bound_up_to_date_ = 0; primitive_processor_->BeginSubmission(); + + texture_cache_->BeginSubmission(GetCurrentSubmission()); } if (is_opening_frame) { @@ -1854,6 +1869,8 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) { uniform_buffer_pool_->Reclaim(frame_completed_); primitive_processor_->BeginFrame(); + + texture_cache_->BeginFrame(); } return true; @@ -2093,6 +2110,8 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) { uniform_buffer_pool_->ClearCache(); transient_descriptor_pool_uniform_buffers_->ClearCache(); + texture_cache_->ClearCache(); + pipeline_cache_->ClearCache(); render_target_cache_->ClearCache(); diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.h b/src/xenia/gpu/vulkan/vulkan_command_processor.h index d81cde4a1..c23279bcf 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.h +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.h @@ -30,6 +30,7 @@ #include "xenia/gpu/vulkan/vulkan_render_target_cache.h" #include "xenia/gpu/vulkan/vulkan_shader.h" #include "xenia/gpu/vulkan/vulkan_shared_memory.h" +#include "xenia/gpu/vulkan/vulkan_texture_cache.h" #include "xenia/gpu/xenos.h" #include "xenia/kernel/kernel_state.h" #include "xenia/ui/vulkan/transient_descriptor_pool.h" @@ -82,8 +83,10 @@ class VulkanCommandProcessor : public CommandProcessor { uint64_t GetCurrentFrame() const { return frame_current_; } uint64_t GetCompletedFrame() const { return frame_completed_; } - // Submission must be open to insert barriers. Returning true if the barrier - // has actually been inserted and not dropped. + // Submission must be open to insert barriers. If no pipeline stages access + // the resource in a synchronization scope, the stage masks should be 0 (top / + // bottom of pipe should be specified only if explicitly needed). Returning + // true if the barrier has actually been inserted and not dropped. bool PushBufferMemoryBarrier( VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, @@ -328,9 +331,11 @@ class VulkanCommandProcessor : public CommandProcessor { std::unique_ptr primitive_processor_; + std::unique_ptr render_target_cache_; + std::unique_ptr pipeline_cache_; - std::unique_ptr render_target_cache_; + std::unique_ptr texture_cache_; VkDescriptorPool shared_memory_and_edram_descriptor_pool_ = VK_NULL_HANDLE; VkDescriptorSet shared_memory_and_edram_descriptor_set_; diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.cc b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc new file mode 100644 index 000000000..c9b8f8e58 --- /dev/null +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc @@ -0,0 +1,452 @@ +/** + ****************************************************************************** + * 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 diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.h b/src/xenia/gpu/vulkan/vulkan_texture_cache.h new file mode 100644 index 000000000..69f3965d5 --- /dev/null +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.h @@ -0,0 +1,94 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_ +#define XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_ + +#include +#include +#include + +#include "xenia/gpu/texture_cache.h" +#include "xenia/gpu/vulkan/vulkan_shared_memory.h" +#include "xenia/ui/vulkan/vulkan_provider.h" + +namespace xe { +namespace gpu { +namespace vulkan { + +class VulkanCommandProcessor; + +class VulkanTextureCache final : public TextureCache { + public: + static std::unique_ptr Create( + 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) { + std::unique_ptr texture_cache(new VulkanTextureCache( + register_file, shared_memory, draw_resolution_scale_x, + draw_resolution_scale_y, command_processor, + guest_shader_pipeline_stages)); + if (!texture_cache->Initialize()) { + return nullptr; + } + return std::move(texture_cache); + } + + ~VulkanTextureCache(); + + void BeginSubmission(uint64_t new_submission_index) override; + + protected: + uint32_t GetHostFormatSwizzle(TextureKey key) const override; + + uint32_t GetMaxHostTextureWidthHeight( + xenos::DataDimension dimension) const override; + uint32_t GetMaxHostTextureDepthOrArraySize( + xenos::DataDimension dimension) const override; + + std::unique_ptr CreateTexture(TextureKey key) override; + + bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, + bool load_mips) override; + + private: + class VulkanTexture final : public Texture { + public: + explicit VulkanTexture(VulkanTextureCache& texture_cache, + const TextureKey& key); + }; + + explicit 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); + + bool Initialize(); + + VulkanCommandProcessor& command_processor_; + VkPipelineStageFlags guest_shader_pipeline_stages_; + + // If both images can be placed in the same allocation, it's one allocation, + // otherwise it's two separate. + std::array null_images_memory_{}; + VkImage null_image_2d_array_cube_ = VK_NULL_HANDLE; + VkImage null_image_3d_ = VK_NULL_HANDLE; + VkImageView null_image_view_2d_array_ = VK_NULL_HANDLE; + VkImageView null_image_view_cube_ = VK_NULL_HANDLE; + VkImageView null_image_view_3d_ = VK_NULL_HANDLE; + bool null_images_cleared_ = false; +}; + +} // namespace vulkan +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_