[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:
@@ -15,7 +15,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/cvar.h"
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
@@ -28,12 +27,6 @@
|
|||||||
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
|
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_util.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 xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
namespace d3d12 {
|
namespace d3d12 {
|
||||||
@@ -1235,8 +1228,9 @@ ID3D12Resource* D3D12TextureCache::RequestSwapTexture(
|
|||||||
|
|
||||||
D3D12TextureCache::D3D12Texture::D3D12Texture(
|
D3D12TextureCache::D3D12Texture::D3D12Texture(
|
||||||
D3D12TextureCache& texture_cache, const TextureKey& key,
|
D3D12TextureCache& texture_cache, const TextureKey& key,
|
||||||
ID3D12Resource* resource, D3D12_RESOURCE_STATES resource_state)
|
ID3D12Resource* resource, D3D12_RESOURCE_STATES resource_state,
|
||||||
: Texture(texture_cache, key),
|
bool track_usage)
|
||||||
|
: Texture(texture_cache, key, track_usage),
|
||||||
resource_(resource),
|
resource_(resource),
|
||||||
resource_state_(resource_state) {
|
resource_state_(resource_state) {
|
||||||
ID3D12Device* device =
|
ID3D12Device* device =
|
||||||
@@ -1842,7 +1836,7 @@ void D3D12TextureCache::UpdateTextureBindingsImpl(
|
|||||||
|
|
||||||
ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource(
|
ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource(
|
||||||
D3D12_RESOURCE_STATES end_state) {
|
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.
|
// Mode 0: Feature disabled.
|
||||||
if (mode == 0) {
|
if (mode == 0) {
|
||||||
@@ -1922,16 +1916,18 @@ ID3D12Resource* D3D12TextureCache::D3D12Texture::GetOrCreate3DAs2DResource(
|
|||||||
TextureKey key_2d = key();
|
TextureKey key_2d = key();
|
||||||
key_2d.depth_or_array_size_minus_1 = 0;
|
key_2d.depth_or_array_size_minus_1 = 0;
|
||||||
key_2d.mip_max_level = 0;
|
key_2d.mip_max_level = 0;
|
||||||
texture_3d_as_2d_.reset(new D3D12Texture(d3d12_cache, key_2d,
|
texture_3d_as_2d_.reset(new D3D12Texture(
|
||||||
resource_2d.Get(), initial_state));
|
d3d12_cache, key_2d, resource_2d.Get(), initial_state, false));
|
||||||
} else {
|
} else {
|
||||||
// Mode 2: CPU re-upload - reload texture data from guest memory.
|
// 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();
|
TextureKey key_load = key();
|
||||||
key_load.depth_or_array_size_minus_1 = 0;
|
key_load.depth_or_array_size_minus_1 = 0;
|
||||||
key_load.mip_max_level = 0;
|
key_load.mip_max_level = 0;
|
||||||
|
|
||||||
std::unique_ptr<D3D12Texture> texture_wrap(new D3D12Texture(
|
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)) {
|
if (!d3d12_cache.LoadTextureData(*texture_wrap)) {
|
||||||
XELOGE("D3D12Texture: Failed to untile 3D-as-2D data");
|
XELOGE("D3D12Texture: Failed to untile 3D-as-2D data");
|
||||||
|
|||||||
@@ -571,9 +571,11 @@ class D3D12TextureCache final : public TextureCache {
|
|||||||
|
|
||||||
ID3D12Resource* GetOrCreate3DAs2DResource(D3D12_RESOURCE_STATES end_state);
|
ID3D12Resource* GetOrCreate3DAs2DResource(D3D12_RESOURCE_STATES end_state);
|
||||||
|
|
||||||
|
// track_usage: if false, texture won't participate in LRU cache eviction.
|
||||||
explicit D3D12Texture(D3D12TextureCache& texture_cache,
|
explicit D3D12Texture(D3D12TextureCache& texture_cache,
|
||||||
const TextureKey& key, ID3D12Resource* resource,
|
const TextureKey& key, ID3D12Resource* resource,
|
||||||
D3D12_RESOURCE_STATES resource_state);
|
D3D12_RESOURCE_STATES resource_state,
|
||||||
|
bool track_usage = true);
|
||||||
~D3D12Texture();
|
~D3D12Texture();
|
||||||
|
|
||||||
ID3D12Resource* resource() const { return resource_.Get(); }
|
ID3D12Resource* resource() const { return resource_.Get(); }
|
||||||
|
|||||||
@@ -89,3 +89,9 @@ DEFINE_bool(no_discard_stencil_in_transfer_pipelines, false,
|
|||||||
"Skip stencil bit discard in render target transfer pipelines. "
|
"Skip stencil bit discard in render target transfer pipelines. "
|
||||||
"May improve performance on some GPUs.",
|
"May improve performance on some GPUs.",
|
||||||
"GPU");
|
"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");
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ DECLARE_bool(disassemble_pm4);
|
|||||||
|
|
||||||
DECLARE_bool(no_discard_stencil_in_transfer_pipelines);
|
DECLARE_bool(no_discard_stencil_in_transfer_pipelines);
|
||||||
|
|
||||||
|
DECLARE_int32(gpu_3d_to_2d_texture_mode);
|
||||||
|
|
||||||
#define XE_GPU_FINE_GRAINED_DRAW_SCOPES 1
|
#define XE_GPU_FINE_GRAINED_DRAW_SCOPES 1
|
||||||
|
|
||||||
#endif // XENIA_GPU_GPU_FLAGS_H_
|
#endif // XENIA_GPU_GPU_FLAGS_H_
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ void TextureCache::Texture::LogAction(const char* action) const {
|
|||||||
// performed somehow. The list is maintained by the Texture, not the
|
// performed somehow. The list is maintained by the Texture, not the
|
||||||
// TextureCache itself (unlike the `textures_` container).
|
// TextureCache itself (unlike the `textures_` container).
|
||||||
TextureCache::Texture::Texture(TextureCache& texture_cache,
|
TextureCache::Texture::Texture(TextureCache& texture_cache,
|
||||||
const TextureKey& key)
|
const TextureKey& key, bool track_usage)
|
||||||
: texture_cache_(texture_cache),
|
: texture_cache_(texture_cache),
|
||||||
key_(key),
|
key_(key),
|
||||||
guest_layout_(key.GetGuestLayout()),
|
guest_layout_(key.GetGuestLayout()),
|
||||||
@@ -512,14 +512,17 @@ TextureCache::Texture::Texture(TextureCache& texture_cache,
|
|||||||
mips_resolved_(key.scaled_resolve),
|
mips_resolved_(key.scaled_resolve),
|
||||||
last_usage_submission_index_(texture_cache.current_submission_index_),
|
last_usage_submission_index_(texture_cache.current_submission_index_),
|
||||||
last_usage_time_(texture_cache.current_submission_time_),
|
last_usage_time_(texture_cache.current_submission_time_),
|
||||||
used_previous_(texture_cache.texture_used_last_),
|
used_previous_(track_usage ? texture_cache.texture_used_last_ : nullptr),
|
||||||
used_next_(nullptr) {
|
used_next_(nullptr),
|
||||||
|
in_usage_list_(track_usage) {
|
||||||
|
if (track_usage) {
|
||||||
if (texture_cache.texture_used_last_) {
|
if (texture_cache.texture_used_last_) {
|
||||||
texture_cache.texture_used_last_->used_next_ = this;
|
texture_cache.texture_used_last_->used_next_ = this;
|
||||||
} else {
|
} else {
|
||||||
texture_cache.texture_used_first_ = this;
|
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.
|
// Never try to upload data that doesn't exist.
|
||||||
base_outdated_ = guest_layout().base.level_data_extent_bytes != 0;
|
base_outdated_ = guest_layout().base.level_data_extent_bytes != 0;
|
||||||
@@ -534,6 +537,8 @@ TextureCache::Texture::~Texture() {
|
|||||||
texture_cache().shared_memory().UnwatchMemoryRange(base_watch_handle_);
|
texture_cache().shared_memory().UnwatchMemoryRange(base_watch_handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only remove from usage list if we were added to it (track_usage=true).
|
||||||
|
if (in_usage_list_) {
|
||||||
if (used_previous_) {
|
if (used_previous_) {
|
||||||
used_previous_->used_next_ = used_next_;
|
used_previous_->used_next_ = used_next_;
|
||||||
} else {
|
} else {
|
||||||
@@ -544,6 +549,7 @@ TextureCache::Texture::~Texture() {
|
|||||||
} else {
|
} else {
|
||||||
texture_cache_.texture_used_last_ = used_previous_;
|
texture_cache_.texture_used_last_ = used_previous_;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
texture_cache_.UpdateTexturesTotalHostMemoryUsage(0, host_memory_usage_);
|
texture_cache_.UpdateTexturesTotalHostMemoryUsage(0, host_memory_usage_);
|
||||||
}
|
}
|
||||||
@@ -568,6 +574,10 @@ void TextureCache::Texture::MakeUpToDateAndWatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::Texture::MarkAsUsed() {
|
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_ <=
|
assert_true(last_usage_submission_index_ <=
|
||||||
texture_cache_.current_submission_index_);
|
texture_cache_.current_submission_index_);
|
||||||
// This is called very frequently, don't relink unless needed for caching.
|
// This is called very frequently, don't relink unless needed for caching.
|
||||||
|
|||||||
@@ -294,7 +294,11 @@ class TextureCache {
|
|||||||
void LogAction(const char* action) const;
|
void LogAction(const char* action) const;
|
||||||
|
|
||||||
protected:
|
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) {
|
void SetHostMemoryUsage(uint64_t new_host_memory_usage) {
|
||||||
texture_cache_.UpdateTexturesTotalHostMemoryUsage(new_host_memory_usage,
|
texture_cache_.UpdateTexturesTotalHostMemoryUsage(new_host_memory_usage,
|
||||||
@@ -315,6 +319,9 @@ class TextureCache {
|
|||||||
uint64_t last_usage_time_;
|
uint64_t last_usage_time_;
|
||||||
Texture* used_previous_;
|
Texture* used_previous_;
|
||||||
Texture* used_next_;
|
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
|
// 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
|
// resolve operation (rather than from the CPU or memexport), primarily for
|
||||||
|
|||||||
@@ -175,6 +175,16 @@ void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) {
|
|||||||
args.filter);
|
args.filter);
|
||||||
} break;
|
} 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: {
|
case Command::kVkDispatch: {
|
||||||
auto& args = *reinterpret_cast<const ArgsVkDispatch*>(stream);
|
auto& args = *reinterpret_cast<const ArgsVkDispatch*>(stream);
|
||||||
dfn.vkCmdDispatch(command_buffer, args.group_count_x,
|
dfn.vkCmdDispatch(command_buffer, args.group_count_x,
|
||||||
|
|||||||
@@ -260,6 +260,32 @@ class DeferredCommandBuffer {
|
|||||||
regions, sizeof(VkImageBlit) * region_count);
|
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,
|
void CmdVkDispatch(uint32_t group_count_x, uint32_t group_count_y,
|
||||||
uint32_t group_count_z) {
|
uint32_t group_count_z) {
|
||||||
auto& args = *reinterpret_cast<ArgsVkDispatch*>(
|
auto& args = *reinterpret_cast<ArgsVkDispatch*>(
|
||||||
@@ -398,6 +424,7 @@ class DeferredCommandBuffer {
|
|||||||
kVkCopyBuffer,
|
kVkCopyBuffer,
|
||||||
kVkCopyBufferToImage,
|
kVkCopyBufferToImage,
|
||||||
kVkBlitImage,
|
kVkBlitImage,
|
||||||
|
kVkCopyImage,
|
||||||
kVkDispatch,
|
kVkDispatch,
|
||||||
kVkDraw,
|
kVkDraw,
|
||||||
kVkDrawIndexed,
|
kVkDrawIndexed,
|
||||||
@@ -504,6 +531,16 @@ class DeferredCommandBuffer {
|
|||||||
static_assert(alignof(VkImageBlit) <= alignof(uintmax_t));
|
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 {
|
struct ArgsVkDispatch {
|
||||||
uint32_t group_count_x;
|
uint32_t group_count_x;
|
||||||
uint32_t group_count_y;
|
uint32_t group_count_y;
|
||||||
|
|||||||
@@ -605,15 +605,35 @@ void VulkanTextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|||||||
|
|
||||||
VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(
|
VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(
|
||||||
uint32_t fetch_constant_index, xenos::FetchOpDimension dimension,
|
uint32_t fetch_constant_index, xenos::FetchOpDimension dimension,
|
||||||
bool is_signed) const {
|
bool is_signed) {
|
||||||
VkImageView image_view = VK_NULL_HANDLE;
|
VkImageView image_view = VK_NULL_HANDLE;
|
||||||
const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index);
|
const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index);
|
||||||
if (binding && AreDimensionsCompatible(dimension, binding->key.dimension)) {
|
if (binding && AreDimensionsCompatible(dimension, binding->key.dimension)) {
|
||||||
|
// 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 =
|
const VulkanTextureBinding& vulkan_binding =
|
||||||
vulkan_texture_bindings_[fetch_constant_index];
|
vulkan_texture_bindings_[fetch_constant_index];
|
||||||
image_view = is_signed ? vulkan_binding.image_view_signed
|
image_view = is_signed ? vulkan_binding.image_view_signed
|
||||||
: vulkan_binding.image_view_unsigned;
|
: vulkan_binding.image_view_unsigned;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (image_view != VK_NULL_HANDLE) {
|
if (image_view != VK_NULL_HANDLE) {
|
||||||
return image_view;
|
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;
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
// For scaled resolve textures with mips, we need transfer source to generate
|
// For scaled resolve textures with mips, we need transfer source to generate
|
||||||
// mip levels via blit from the base level.
|
// 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.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
}
|
}
|
||||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
@@ -1765,8 +1786,10 @@ void VulkanTextureCache::UpdateTextureBindingsImpl(
|
|||||||
|
|
||||||
VulkanTextureCache::VulkanTexture::VulkanTexture(
|
VulkanTextureCache::VulkanTexture::VulkanTexture(
|
||||||
VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
|
VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
|
||||||
VmaAllocation allocation)
|
VmaAllocation allocation, bool track_usage)
|
||||||
: Texture(texture_cache, key), image_(image), allocation_(allocation) {
|
: Texture(texture_cache, key, track_usage),
|
||||||
|
image_(image),
|
||||||
|
allocation_(allocation) {
|
||||||
VmaAllocationInfo allocation_info;
|
VmaAllocationInfo allocation_info;
|
||||||
vmaGetAllocationInfo(texture_cache.vma_allocator_, allocation_,
|
vmaGetAllocationInfo(texture_cache.vma_allocator_, allocation_,
|
||||||
&allocation_info);
|
&allocation_info);
|
||||||
@@ -1783,6 +1806,15 @@ VulkanTextureCache::VulkanTexture::~VulkanTexture() {
|
|||||||
for (const auto& view_pair : views_) {
|
for (const auto& view_pair : views_) {
|
||||||
dfn.vkDestroyImageView(device, view_pair.second, nullptr);
|
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_);
|
vmaDestroyImage(vulkan_texture_cache.vma_allocator_, image_, allocation_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1877,6 +1909,235 @@ VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
|
|||||||
return view;
|
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(
|
VulkanTextureCache::VulkanTextureCache(
|
||||||
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
|
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
|
||||||
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
|
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class VulkanTextureCache final : public TextureCache {
|
|||||||
|
|
||||||
VkImageView GetActiveBindingOrNullImageView(uint32_t fetch_constant_index,
|
VkImageView GetActiveBindingOrNullImageView(uint32_t fetch_constant_index,
|
||||||
xenos::FetchOpDimension dimension,
|
xenos::FetchOpDimension dimension,
|
||||||
bool is_signed) const;
|
bool is_signed);
|
||||||
|
|
||||||
SamplerParameters GetSamplerParameters(
|
SamplerParameters GetSamplerParameters(
|
||||||
const VulkanShader::SamplerBinding& binding) const;
|
const VulkanShader::SamplerBinding& binding) const;
|
||||||
@@ -254,9 +254,10 @@ class VulkanTextureCache final : public TextureCache {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Takes ownership of the image and its memory.
|
// 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,
|
explicit VulkanTexture(VulkanTextureCache& texture_cache,
|
||||||
const TextureKey& key, VkImage image,
|
const TextureKey& key, VkImage image,
|
||||||
VmaAllocation allocation);
|
VmaAllocation allocation, bool track_usage = true);
|
||||||
~VulkanTexture();
|
~VulkanTexture();
|
||||||
|
|
||||||
VkImage image() const { return image_; }
|
VkImage image() const { return image_; }
|
||||||
@@ -271,6 +272,10 @@ class VulkanTextureCache final : public TextureCache {
|
|||||||
VkImageView GetView(bool is_signed, uint32_t host_swizzle,
|
VkImageView GetView(bool is_signed, uint32_t host_swizzle,
|
||||||
bool is_array = true);
|
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:
|
private:
|
||||||
union ViewKey {
|
union ViewKey {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
@@ -331,6 +336,14 @@ class VulkanTextureCache final : public TextureCache {
|
|||||||
Usage usage_ = Usage::kUndefined;
|
Usage usage_ = Usage::kUndefined;
|
||||||
|
|
||||||
std::unordered_map<ViewKey, VkImageView, ViewKey::Hasher> views_;
|
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 {
|
struct VulkanTextureBinding {
|
||||||
@@ -359,7 +372,8 @@ class VulkanTextureCache final : public TextureCache {
|
|||||||
case xenos::FetchOpDimension::k1D:
|
case xenos::FetchOpDimension::k1D:
|
||||||
case xenos::FetchOpDimension::k2D:
|
case xenos::FetchOpDimension::k2D:
|
||||||
return resource_dimension == xenos::DataDimension::k1D ||
|
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:
|
case xenos::FetchOpDimension::k3DOrStacked:
|
||||||
return resource_dimension == xenos::DataDimension::k3D;
|
return resource_dimension == xenos::DataDimension::k3D;
|
||||||
case xenos::FetchOpDimension::kCube:
|
case xenos::FetchOpDimension::kCube:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ XE_UI_VULKAN_FUNCTION(vkCmdClearAttachments)
|
|||||||
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
|
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
|
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage)
|
XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage)
|
||||||
|
XE_UI_VULKAN_FUNCTION(vkCmdCopyImage)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer)
|
XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdDispatch)
|
XE_UI_VULKAN_FUNCTION(vkCmdDispatch)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdDraw)
|
XE_UI_VULKAN_FUNCTION(vkCmdDraw)
|
||||||
|
|||||||
Reference in New Issue
Block a user