[GPU] 3d-to-d2 texture implementation

Adds vulkan version of mode 1 and 2 and fixes related crashes by keeping
the 2d texture views from the texture cache.
This commit is contained in:
Herman S.
2026-01-13 21:33:49 +09:00
parent 7c9b5af236
commit 90c48e1d21
11 changed files with 389 additions and 43 deletions

View File

@@ -15,7 +15,6 @@
#include <cstring>
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
@@ -28,12 +27,6 @@
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
#include "xenia/ui/d3d12/d3d12_util.h"
DEFINE_int32(d3d12_3d_to_2d_texture_mode, 0,
"Handle shaders that sample 3D textures as 2D by creating a 2D "
"view of slice 0. 0 = disabled (default), 1 = GPU copy, "
"2 = CPU re-upload from guest memory.",
"D3D12");
namespace xe {
namespace gpu {
namespace d3d12 {
@@ -1235,8 +1228,9 @@ ID3D12Resource* D3D12TextureCache::RequestSwapTexture(
D3D12TextureCache::D3D12Texture::D3D12Texture(
D3D12TextureCache& texture_cache, const TextureKey& key,
ID3D12Resource* resource, D3D12_RESOURCE_STATES resource_state)
: Texture(texture_cache, key),
ID3D12Resource* resource, D3D12_RESOURCE_STATES resource_state,
bool track_usage)
: Texture(texture_cache, key, track_usage),
resource_(resource),
resource_state_(resource_state) {
ID3D12Device* device =
@@ -1842,7 +1836,7 @@ void D3D12TextureCache::UpdateTextureBindingsImpl(
ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource(
D3D12_RESOURCE_STATES end_state) {
int32_t mode = cvars::d3d12_3d_to_2d_texture_mode;
int32_t mode = cvars::gpu_3d_to_2d_texture_mode;
// Mode 0: Feature disabled.
if (mode == 0) {
@@ -1922,16 +1916,18 @@ ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource(
TextureKey key_2d = key();
key_2d.depth_or_array_size_minus_1 = 0;
key_2d.mip_max_level = 0;
texture_3d_as_2d_.reset(new D3D12Texture(d3d12_cache, key_2d,
resource_2d.Get(), initial_state));
texture_3d_as_2d_.reset(new D3D12Texture(
d3d12_cache, key_2d, resource_2d.Get(), initial_state, false));
} else {
// Mode 2: CPU re-upload - reload texture data from guest memory.
// Keep dimension as k3D so LoadTextureData uses 3D tiling math to correctly
// read slice 0 from the 3D-tiled guest memory.
TextureKey key_load = key();
key_load.depth_or_array_size_minus_1 = 0;
key_load.mip_max_level = 0;
std::unique_ptr<D3D12Texture> texture_wrap(new D3D12Texture(
d3d12_cache, key_load, resource_2d.Get(), initial_state));
d3d12_cache, key_load, resource_2d.Get(), initial_state, false));
if (!d3d12_cache.LoadTextureData(*texture_wrap)) {
XELOGE("D3D12Texture: Failed to untile 3D-as-2D data");

View File

@@ -571,9 +571,11 @@ class D3D12TextureCache final : public TextureCache {
ID3D12Resource* GetOrCreate3DAs2DResource(D3D12_RESOURCE_STATES end_state);
// track_usage: if false, texture won't participate in LRU cache eviction.
explicit D3D12Texture(D3D12TextureCache& texture_cache,
const TextureKey& key, ID3D12Resource* resource,
D3D12_RESOURCE_STATES resource_state);
D3D12_RESOURCE_STATES resource_state,
bool track_usage = true);
~D3D12Texture();
ID3D12Resource* resource() const { return resource_.Get(); }

View File

@@ -89,3 +89,9 @@ DEFINE_bool(no_discard_stencil_in_transfer_pipelines, false,
"Skip stencil bit discard in render target transfer pipelines. "
"May improve performance on some GPUs.",
"GPU");
DEFINE_int32(gpu_3d_to_2d_texture_mode, 2,
"Handle shaders that sample 3D textures as 2D by creating a 2D "
"copy of slice 0. 0 = disabled, 1 = GPU copy, "
"2 = CPU re-upload from guest memory (default).",
"GPU");

View File

@@ -36,6 +36,8 @@ DECLARE_bool(disassemble_pm4);
DECLARE_bool(no_discard_stencil_in_transfer_pipelines);
DECLARE_int32(gpu_3d_to_2d_texture_mode);
#define XE_GPU_FINE_GRAINED_DRAW_SCOPES 1
#endif // XENIA_GPU_GPU_FLAGS_H_

View File

@@ -504,7 +504,7 @@ void TextureCache::Texture::LogAction(const char* action) const {
// performed somehow. The list is maintained by the Texture, not the
// TextureCache itself (unlike the `textures_` container).
TextureCache::Texture::Texture(TextureCache& texture_cache,
const TextureKey& key)
const TextureKey& key, bool track_usage)
: texture_cache_(texture_cache),
key_(key),
guest_layout_(key.GetGuestLayout()),
@@ -512,14 +512,17 @@ TextureCache::Texture::Texture(TextureCache& texture_cache,
mips_resolved_(key.scaled_resolve),
last_usage_submission_index_(texture_cache.current_submission_index_),
last_usage_time_(texture_cache.current_submission_time_),
used_previous_(texture_cache.texture_used_last_),
used_next_(nullptr) {
if (texture_cache.texture_used_last_) {
texture_cache.texture_used_last_->used_next_ = this;
} else {
texture_cache.texture_used_first_ = this;
used_previous_(track_usage ? texture_cache.texture_used_last_ : nullptr),
used_next_(nullptr),
in_usage_list_(track_usage) {
if (track_usage) {
if (texture_cache.texture_used_last_) {
texture_cache.texture_used_last_->used_next_ = this;
} else {
texture_cache.texture_used_first_ = this;
}
texture_cache.texture_used_last_ = this;
}
texture_cache.texture_used_last_ = this;
// Never try to upload data that doesn't exist.
base_outdated_ = guest_layout().base.level_data_extent_bytes != 0;
@@ -534,15 +537,18 @@ TextureCache::Texture::~Texture() {
texture_cache().shared_memory().UnwatchMemoryRange(base_watch_handle_);
}
if (used_previous_) {
used_previous_->used_next_ = used_next_;
} else {
texture_cache_.texture_used_first_ = used_next_;
}
if (used_next_) {
used_next_->used_previous_ = used_previous_;
} else {
texture_cache_.texture_used_last_ = used_previous_;
// Only remove from usage list if we were added to it (track_usage=true).
if (in_usage_list_) {
if (used_previous_) {
used_previous_->used_next_ = used_next_;
} else {
texture_cache_.texture_used_first_ = used_next_;
}
if (used_next_) {
used_next_->used_previous_ = used_previous_;
} else {
texture_cache_.texture_used_last_ = used_previous_;
}
}
texture_cache_.UpdateTexturesTotalHostMemoryUsage(0, host_memory_usage_);
@@ -568,6 +574,10 @@ void TextureCache::Texture::MakeUpToDateAndWatch(
}
void TextureCache::Texture::MarkAsUsed() {
// Textures not in usage tracking (track_usage=false) should not be linked.
if (!in_usage_list_) {
return;
}
assert_true(last_usage_submission_index_ <=
texture_cache_.current_submission_index_);
// This is called very frequently, don't relink unless needed for caching.

View File

@@ -294,7 +294,11 @@ class TextureCache {
void LogAction(const char* action) const;
protected:
explicit Texture(TextureCache& texture_cache, const TextureKey& key);
// track_usage: if false, the texture won't be added to the LRU tracking
// list. Use this for wrapper textures that shouldn't participate in cache
// eviction (like texture_3d_as_2d_ wrappers).
explicit Texture(TextureCache& texture_cache, const TextureKey& key,
bool track_usage = true);
void SetHostMemoryUsage(uint64_t new_host_memory_usage) {
texture_cache_.UpdateTexturesTotalHostMemoryUsage(new_host_memory_usage,
@@ -315,6 +319,9 @@ class TextureCache {
uint64_t last_usage_time_;
Texture* used_previous_;
Texture* used_next_;
// Whether this texture is in the usage tracking list (for LRU eviction).
// Set to false via constructor for wrapper textures.
bool in_usage_list_;
// Whether the most up-to-date base / mips contain pages with data from a
// resolve operation (rather than from the CPU or memexport), primarily for

View File

@@ -175,6 +175,16 @@ void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) {
args.filter);
} break;
case Command::kVkCopyImage: {
auto& args = *reinterpret_cast<const ArgsVkCopyImage*>(stream);
dfn.vkCmdCopyImage(
command_buffer, args.src_image, args.src_image_layout,
args.dst_image, args.dst_image_layout, args.region_count,
reinterpret_cast<const VkImageCopy*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkCopyImage), alignof(VkImageCopy))));
} break;
case Command::kVkDispatch: {
auto& args = *reinterpret_cast<const ArgsVkDispatch*>(stream);
dfn.vkCmdDispatch(command_buffer, args.group_count_x,

View File

@@ -260,6 +260,32 @@ class DeferredCommandBuffer {
regions, sizeof(VkImageBlit) * region_count);
}
VkImageCopy* CmdCopyImageEmplace(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count) {
const size_t header_size =
xe::align(sizeof(ArgsVkCopyImage), alignof(VkImageCopy));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkCopyImage,
header_size + sizeof(VkImageCopy) * region_count));
auto& args = *reinterpret_cast<ArgsVkCopyImage*>(args_ptr);
args.src_image = src_image;
args.src_image_layout = src_image_layout;
args.dst_image = dst_image;
args.dst_image_layout = dst_image_layout;
args.region_count = region_count;
return reinterpret_cast<VkImageCopy*>(args_ptr + header_size);
}
void CmdVkCopyImage(VkImage src_image, VkImageLayout src_image_layout,
VkImage dst_image, VkImageLayout dst_image_layout,
uint32_t region_count, const VkImageCopy* regions) {
std::memcpy(CmdCopyImageEmplace(src_image, src_image_layout, dst_image,
dst_image_layout, region_count),
regions, sizeof(VkImageCopy) * region_count);
}
void CmdVkDispatch(uint32_t group_count_x, uint32_t group_count_y,
uint32_t group_count_z) {
auto& args = *reinterpret_cast<ArgsVkDispatch*>(
@@ -398,6 +424,7 @@ class DeferredCommandBuffer {
kVkCopyBuffer,
kVkCopyBufferToImage,
kVkBlitImage,
kVkCopyImage,
kVkDispatch,
kVkDraw,
kVkDrawIndexed,
@@ -504,6 +531,16 @@ class DeferredCommandBuffer {
static_assert(alignof(VkImageBlit) <= alignof(uintmax_t));
};
struct ArgsVkCopyImage {
VkImage src_image;
VkImageLayout src_image_layout;
VkImage dst_image;
VkImageLayout dst_image_layout;
uint32_t region_count;
// Followed by aligned VkImageCopy[].
static_assert(alignof(VkImageCopy) <= alignof(uintmax_t));
};
struct ArgsVkDispatch {
uint32_t group_count_x;
uint32_t group_count_y;

View File

@@ -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, &copy_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,

View File

@@ -92,7 +92,7 @@ class VulkanTextureCache final : public TextureCache {
VkImageView GetActiveBindingOrNullImageView(uint32_t fetch_constant_index,
xenos::FetchOpDimension dimension,
bool is_signed) const;
bool is_signed);
SamplerParameters GetSamplerParameters(
const VulkanShader::SamplerBinding& binding) const;
@@ -254,9 +254,10 @@ class VulkanTextureCache final : public TextureCache {
};
// Takes ownership of the image and its memory.
// track_usage: if false, texture won't participate in LRU cache eviction.
explicit VulkanTexture(VulkanTextureCache& texture_cache,
const TextureKey& key, VkImage image,
VmaAllocation allocation);
VmaAllocation allocation, bool track_usage = true);
~VulkanTexture();
VkImage image() const { return image_; }
@@ -271,6 +272,10 @@ class VulkanTextureCache final : public TextureCache {
VkImageView GetView(bool is_signed, uint32_t host_swizzle,
bool is_array = true);
// For 3D textures sampled as 2D - creates a 2D copy of slice 0.
VkImageView GetOrCreate3DAs2DImageView(bool is_signed,
uint32_t host_swizzle);
private:
union ViewKey {
uint32_t key;
@@ -331,6 +336,14 @@ class VulkanTextureCache final : public TextureCache {
Usage usage_ = Usage::kUndefined;
std::unordered_map<ViewKey, VkImageView, ViewKey::Hasher> views_;
// For 3D textures sampled as 2D - cached 2D copy of slice 0.
// This is a wrapper around the 2D image with a modified key (depth=1).
// For Mode 1 (GPU copy), the wrapper is created after the copy.
// For Mode 2 (CPU re-upload), LoadTextureData is called on the wrapper.
std::unique_ptr<VulkanTexture> texture_3d_as_2d_;
VkImageView image_view_3d_as_2d_unsigned_ = VK_NULL_HANDLE;
VkImageView image_view_3d_as_2d_signed_ = VK_NULL_HANDLE;
};
struct VulkanTextureBinding {
@@ -359,7 +372,8 @@ class VulkanTextureCache final : public TextureCache {
case xenos::FetchOpDimension::k1D:
case xenos::FetchOpDimension::k2D:
return resource_dimension == xenos::DataDimension::k1D ||
resource_dimension == xenos::DataDimension::k2DOrStacked;
resource_dimension == xenos::DataDimension::k2DOrStacked ||
resource_dimension == xenos::DataDimension::k3D;
case xenos::FetchOpDimension::k3DOrStacked:
return resource_dimension == xenos::DataDimension::k3D;
case xenos::FetchOpDimension::kCube:

View File

@@ -15,6 +15,7 @@ XE_UI_VULKAN_FUNCTION(vkCmdClearAttachments)
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage)
XE_UI_VULKAN_FUNCTION(vkCmdCopyImage)
XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer)
XE_UI_VULKAN_FUNCTION(vkCmdDispatch)
XE_UI_VULKAN_FUNCTION(vkCmdDraw)