Files
KytyPS5/src/graphics/host_gpu/objects/textureCommon.cpp
T
2026-07-17 18:09:20 +02:00

1145 lines
48 KiB
C++

#include "graphics/host_gpu/objects/textureCommon.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/utils.h"
#include "graphics/host_gpu/vma.h"
#include <algorithm>
#include <atomic>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
// TODO: cleanup!
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_t raw_type,
uint32_t raw_order) {
const auto layout = static_cast<Prospero::ChannelLayout>(raw_layout);
const auto type = static_cast<Prospero::ChannelType>(raw_type);
const auto order = static_cast<Prospero::ChannelOrder>(raw_order);
const auto is = [=](Prospero::ChannelLayout l, Prospero::ChannelType t,
Prospero::ChannelOrder o) {
return layout == l && type == t && order == o;
};
if (layout == Prospero::ChannelLayout::k8 && type == Prospero::ChannelType::kUNorm &&
raw_order <= Prospero::GpuEnumValue(Prospero::ChannelOrder::kAltReversed)) {
return {VK_FORMAT_R8_UNORM, 1};
}
if (is(Prospero::ChannelLayout::k8_8, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R8G8_UNORM, 2};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R8G8B8A8_UNORM, 4};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R8G8B8A8_SNORM, 4};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R8G8B8A8_SRGB, 4};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt)) {
return {VK_FORMAT_B8G8R8A8_UNORM, 4};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kAlt)) {
return {VK_FORMAT_B8G8R8A8_SNORM, 4};
}
if (is(Prospero::ChannelLayout::k8_8_8_8, Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kAlt)) {
return {VK_FORMAT_B8G8R8A8_SRGB, 4};
}
if (is(Prospero::ChannelLayout::k5_5_5_1, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R5G5B5A1_UNORM_PACK16, 2};
}
if (is(Prospero::ChannelLayout::k4_4_4_4, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kReversed)) {
return {VK_FORMAT_B4G4R4A4_UNORM_PACK16, 2};
}
if (is(Prospero::ChannelLayout::k10_10_10_2, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4};
}
if (is(Prospero::ChannelLayout::k10_10_10_2, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt)) {
return {VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4};
}
if (is(Prospero::ChannelLayout::k11_11_10, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
}
if (is(Prospero::ChannelLayout::k16, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16_UNORM, 2};
}
if (is(Prospero::ChannelLayout::k16, Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16_UINT, 2};
}
if (is(Prospero::ChannelLayout::k16, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16_SFLOAT, 2};
}
if (is(Prospero::ChannelLayout::k16_16, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16_UNORM, 4};
}
if (is(Prospero::ChannelLayout::k16_16, Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16_SNORM, 4};
}
if (is(Prospero::ChannelLayout::k16_16, Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16_UINT, 4};
}
if (is(Prospero::ChannelLayout::k16_16, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16_SFLOAT, 4};
}
if (is(Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16B16A16_UNORM, 8};
}
if (is(Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R16G16B16A16_SFLOAT, 8};
}
if (is(Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kReversed)) {
return {VK_FORMAT_R16G16B16A16_SFLOAT, 8, Prospero::ColorMappingAbgr};
}
if (is(Prospero::ChannelLayout::k32, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R32_SFLOAT, 4};
}
if (is(Prospero::ChannelLayout::k32_32, Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R32G32_UINT, 8};
}
if (is(Prospero::ChannelLayout::k32_32, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R32G32_SFLOAT, 8};
}
if (is(Prospero::ChannelLayout::k32_32_32_32, Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard)) {
return {VK_FORMAT_R32G32B32A32_SFLOAT, 16};
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
raw_type, raw_order);
}
static uint64_t TextureUploadLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
static uint64_t TextureUploadLevelSrcSize(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
}
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice,
TextureUploadSliceLayout source_slice_layout) {
if (level >= 16 || layout.level_sizes[level].size == 0) {
EXIT("invalid texture upload slice source, level=%u slice=%u\n", level, slice);
}
const auto level_offset = TextureUploadLevelSrcOffset(layout.level_sizes[level]);
const auto slice_stride =
source_slice_layout == TextureUploadSliceLayout::MipChainPerSlice
? (layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride)
: TextureUploadLevelSrcSize(layout.level_sizes[level]);
if (slice_stride != 0 && slice > (UINT64_MAX - level_offset) / slice_stride) {
EXIT("texture upload slice source offset overflow, level=%u slice=%u\n", level, slice);
}
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
}
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
const std::vector<BufferImageCopy>& regions, uint64_t levels,
uint32_t depth, TextureUploadSliceLayout source_slice_layout) {
uint64_t size = 0;
for (const auto& r: regions) {
size = std::max<uint64_t>(size, static_cast<uint64_t>(r.offset) +
layout.level_sizes[r.dst_level].size);
}
for (uint32_t level = 0; level < levels; level++) {
const auto src_size = TextureUploadLevelSrcSize(layout.level_sizes[level]);
for (uint32_t z = 0; z < depth; z++) {
size = std::max<uint64_t>(
size,
TextureUploadSliceSourceOffset(layout, level, z, source_slice_layout) + src_size);
}
}
return size;
}
void TextureCopyBufferBytes(GraphicContext* ctx, VulkanBuffer* src_buffer,
uint64_t src_buffer_offset, uint64_t copy_size,
UtilScratchBuffer* dst) {
EXIT_IF(ctx == nullptr);
EXIT_IF(src_buffer == nullptr);
EXIT_IF(dst == nullptr);
EXIT_IF(dst->Data() == nullptr);
std::memset(dst->Data(), 0, copy_size);
if (copy_size == 0 || src_buffer_offset >= src_buffer->buffer_size) {
return;
}
const auto available =
std::min<uint64_t>(copy_size, src_buffer->buffer_size - src_buffer_offset);
if (available == 0) {
return;
}
if ((src_buffer->memory.property & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) {
void* data = nullptr;
VulkanMapMemory(ctx, &src_buffer->memory, &data);
std::memcpy(dst->Data(), static_cast<const uint8_t*>(data) + src_buffer_offset, available);
VulkanUnmapMemory(ctx, &src_buffer->memory);
return;
}
VulkanBuffer readback {};
readback.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
readback.memory.property = static_cast<uint32_t>(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
VulkanCreateBuffer(ctx, (src_buffer_offset + available + 3u) & ~uint64_t {3}, &readback);
UtilCopyBuffer(src_buffer, &readback, src_buffer_offset + available);
void* data = nullptr;
VulkanMapMemory(ctx, &readback.memory, &data);
std::memcpy(dst->Data(), static_cast<const uint8_t*>(data) + src_buffer_offset, available);
VulkanUnmapMemory(ctx, &readback.memory);
VulkanDeleteBuffer(ctx, &readback);
}
VkComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return VK_COMPONENT_SWIZZLE_ZERO;
case Prospero::CompSwizzle::kOne: return VK_COMPONENT_SWIZZLE_ONE;
case Prospero::CompSwizzle::kRed: return VK_COMPONENT_SWIZZLE_R;
case Prospero::CompSwizzle::kGreen: return VK_COMPONENT_SWIZZLE_G;
case Prospero::CompSwizzle::kBlue: return VK_COMPONENT_SWIZZLE_B;
case Prospero::CompSwizzle::kAlpha: return VK_COMPONENT_SWIZZLE_A;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
VkComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
VkComponentMapping components {};
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
return components;
}
bool TextureCheckFormat(GraphicContext* ctx, VkImageCreateInfo* image_info) {
VkImageFormatProperties props {};
if (ctx->GetImageFormatProperties(image_info->format, image_info->imageType, image_info->tiling,
image_info->usage, image_info->flags,
&props) == VK_ERROR_FORMAT_NOT_SUPPORTED) {
auto apply_fallback = [&](VkFormat replacement, const char* message) {
image_info->format = replacement;
const bool result = TextureCheckFormat(ctx, image_info);
LOGF("%s [%s]\n", message, (!result ? "FAIL" : "SUCCESS"));
return result;
};
if (image_info->format == VK_FORMAT_R8G8B8A8_SRGB) {
// TODO() convert SRGB -> LINEAR in shader
return apply_fallback(VK_FORMAT_R8G8B8A8_UNORM,
"replace VK_FORMAT_R8G8B8A8_SRGB => VK_FORMAT_R8G8B8A8_UNORM");
}
if (image_info->format == VK_FORMAT_B8G8R8A8_SRGB) {
// TODO() convert SRGB -> LINEAR in shader
return apply_fallback(VK_FORMAT_B8G8R8A8_UNORM,
"replace VK_FORMAT_B8G8R8A8_SRGB => VK_FORMAT_B8G8R8A8_UNORM");
}
return false;
}
return true;
}
static bool TextureCheckFormatExact(GraphicContext* ctx, const VkImageCreateInfo& image_info) {
VkImageFormatProperties props {};
return ctx->GetImageFormatProperties(image_info.format, image_info.imageType, image_info.tiling,
image_info.usage, image_info.flags,
&props) != VK_ERROR_FORMAT_NOT_SUPPORTED;
}
bool TextureCheckStorageSwizzle(VkImageCreateInfo* image_info, VkComponentMapping* components) {
if ((image_info->usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) {
if (components->r == VK_COMPONENT_SWIZZLE_R && components->g == VK_COMPONENT_SWIZZLE_G &&
components->b == VK_COMPONENT_SWIZZLE_B && components->a == VK_COMPONENT_SWIZZLE_A) {
return true;
}
if (components->r == VK_COMPONENT_SWIZZLE_B && components->g == VK_COMPONENT_SWIZZLE_G &&
components->b == VK_COMPONENT_SWIZZLE_R && components->a == VK_COMPONENT_SWIZZLE_A &&
image_info->format == VK_FORMAT_R8G8B8A8_SRGB) {
LOGF("replace VK_FORMAT_R8G8B8A8_SRGB => VK_FORMAT_B8G8R8A8_SRGB\n");
components->r = VK_COMPONENT_SWIZZLE_R;
components->g = VK_COMPONENT_SWIZZLE_G;
components->b = VK_COMPONENT_SWIZZLE_B;
components->a = VK_COMPONENT_SWIZZLE_A;
image_info->format = VK_FORMAT_B8G8R8A8_SRGB;
return true;
}
// TODO() swizzle channels in shader
return false;
}
return true;
}
VkImageUsageFlags TextureGetUsage(TextureFormatUsage usage) {
VkImageUsageFlags vk_usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT;
}
return vk_usage;
}
VkImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage) {
VkImageUsageFlags vk_usage = 0;
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT;
}
return vk_usage;
}
VkFormat TextureGetFormat(uint32_t fmt, [[maybe_unused]] TextureFormatUsage usage) {
const auto vk_format = Prospero::SurfaceFormat(fmt);
if (vk_format != VK_FORMAT_UNDEFINED) {
return vk_format;
}
EXIT("unknown format: fmt = %u\n", fmt);
return VK_FORMAT_UNDEFINED;
}
static uint32_t AlignUpU32(uint32_t value, uint32_t alignment) {
return (value + alignment - 1u) & ~(alignment - 1u);
}
static uint32_t ShiftCeilU32(uint32_t value, uint32_t shift) {
return static_cast<uint32_t>((static_cast<uint64_t>(value) + (1ull << shift) - 1ull) >> shift);
}
struct Standard4KBVolumeMipLayout {
uint32_t first_tail_level = 0;
uint32_t block_depth = 1;
uint64_t block_slice_size = 0;
uint64_t level_offsets[16] = {};
uint64_t level_sizes[16] = {};
};
static bool CalcStandard4kbVolumeMipLayout(uint32_t format, uint32_t pitch, uint32_t height,
uint64_t levels, Standard4KBVolumeMipLayout* out) {
EXIT_IF(out == nullptr);
EXIT_NOT_IMPLEMENTED(levels == 0 || levels > 16);
uint32_t bytes_per_element = 0;
uint32_t texels_per_element_wide = 0;
uint32_t texels_per_element_tall = 0;
uint32_t block_width_log2 = 0;
uint32_t block_height_log2 = 0;
uint32_t block_depth_log2 = 0;
if (!TileGetStandard4KBVolumeLayout(format, &bytes_per_element, &texels_per_element_wide,
&texels_per_element_tall, &block_width_log2,
&block_height_log2, &block_depth_log2)) {
return false;
}
const uint32_t row_elements0 =
std::max((pitch + texels_per_element_wide - 1u) / texels_per_element_wide, 1u);
const uint32_t height_elements0 =
std::max((height + texels_per_element_tall - 1u) / texels_per_element_tall, 1u);
const uint32_t block_width = 1u << block_width_log2;
const uint32_t block_height = 1u << block_height_log2;
const uint32_t block_depth = 1u << block_depth_log2;
const uint32_t tail_width_limit = block_width;
const uint32_t tail_height_limit = block_height >> 1u;
constexpr uint32_t max_tail_levels = 5u;
out->first_tail_level = static_cast<uint32_t>(levels);
out->block_depth = block_depth;
out->block_slice_size = 0;
for (uint32_t level = 0; level < levels; level++) {
const uint32_t row_elements = std::max(ShiftCeilU32(row_elements0, level), 1u);
const uint32_t height_elements = std::max(ShiftCeilU32(height_elements0, level), 1u);
out->level_offsets[level] = out->block_slice_size;
if (row_elements <= tail_width_limit && height_elements <= tail_height_limit &&
levels - level <= max_tail_levels) {
out->first_tail_level = level;
out->level_sizes[level] = 4096u;
out->block_slice_size += 4096u;
for (uint32_t tail_level = level + 1; tail_level < levels; tail_level++) {
out->level_offsets[tail_level] = out->level_offsets[level];
out->level_sizes[tail_level] = 4096u;
}
break;
}
out->level_sizes[level] = static_cast<uint64_t>(block_depth) *
AlignUpU32(row_elements, block_width) *
AlignUpU32(height_elements, block_height) * bytes_per_element;
out->block_slice_size += out->level_sizes[level];
}
return out->block_slice_size != 0;
}
uint32_t TextureGetAtlasSliceYStride(VkFormat format, uint32_t mip_height, uint32_t depth,
uint64_t levels) {
return (depth > 1 && levels > 1 && UtilIsBcFormat(format) ? AlignUpU32(mip_height, 4u)
: mip_height);
}
uint32_t TextureCalcStackedImageHeight(VkFormat format, uint32_t height, uint32_t depth,
uint64_t levels) {
auto image_height = height * depth;
if (depth <= 1 || levels <= 1 || !UtilIsBcFormat(format)) {
return image_height;
}
uint32_t mip_height = height;
for (uint32_t level = 0; level < levels; level++) {
const auto mip_image_height =
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
image_height = std::max<uint32_t>(image_height, mip_image_height << level);
if (mip_height > 1) {
mip_height /= 2;
}
}
return image_height;
}
uint32_t TextureCalcMipmapAtlasImageHeight(VkFormat format, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels) {
auto image_height = height * depth;
if (levels <= 1) {
return image_height;
}
uint32_t mip_height = height;
for (uint32_t level = 0; level < levels; level++) {
const auto mipmap_offset = UtilCalcMipmapOffset(level, width, height);
const auto mip_bottom =
static_cast<uint32_t>(mipmap_offset.second) +
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
image_height = std::max<uint32_t>(image_height, mip_bottom);
if (mip_height > 1) {
mip_height /= 2;
}
}
return image_height;
}
bool TextureIs3DTexture(uint64_t type) {
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kColor3D;
}
bool TextureIsCubeTexture(uint64_t type) {
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kCube;
}
bool TextureIsLayeredTexture(uint64_t type) {
const auto image_type = static_cast<Prospero::ImageType>(type);
return image_type == Prospero::ImageType::kCube ||
image_type == Prospero::ImageType::kColor1DArray ||
image_type == Prospero::ImageType::kColor2DArray ||
image_type == Prospero::ImageType::kColor2DMsaaArray;
}
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count) {
return TextureIsCubeTexture(type) && base_array % 6u == 0 && layer_count >= 6 &&
layer_count % 6u == 0;
}
VkComponentMapping TextureCreateImage(GraphicContext* ctx, VulkanImage* vk_obj, VulkanMemory* mem,
const TextureImageCreateParams& params) {
EXIT_IF(ctx == nullptr);
EXIT_IF(vk_obj == nullptr);
EXIT_IF(mem == nullptr);
EXIT_IF(params.owner == nullptr);
const bool array_texture = TextureIsLayeredTexture(params.type);
const bool volume_texture = TextureIs3DTexture(params.type);
auto pixel_format = TextureGetFormat(params.fmt, params.format_usage);
EXIT_NOT_IMPLEMENTED(pixel_format == VK_FORMAT_UNDEFINED);
EXIT_NOT_IMPLEMENTED(params.width == 0);
EXIT_NOT_IMPLEMENTED(params.height == 0);
EXIT_NOT_IMPLEMENTED(params.levels == 0 || params.levels > 16);
uint32_t image_height = 0;
uint32_t image_mips = 0;
if (params.image_layout == TextureUploadDestination::MipAtlas) {
EXIT_NOT_IMPLEMENTED(params.base_level != 0);
const auto atlas_depth = (array_texture || volume_texture ? 1u : params.depth);
image_height = TextureCalcMipmapAtlasImageHeight(
pixel_format, static_cast<uint32_t>(params.width), static_cast<uint32_t>(params.height),
atlas_depth, params.levels);
image_mips = 1;
} else {
image_height =
(array_texture || volume_texture
? static_cast<uint32_t>(params.height)
: TextureCalcStackedImageHeight(pixel_format, static_cast<uint32_t>(params.height),
params.depth, params.levels));
image_mips = static_cast<uint32_t>(params.levels);
}
VkComponentMapping components =
TextureGetComponentMapping(static_cast<uint32_t>(params.swizzle));
VkImageCreateInfo image_info {};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.flags =
(params.allow_cube_view && TextureCanCreateCubeView(params.type, 0, params.depth)
? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT
: 0) |
(volume_texture ? VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT : 0) |
(params.compatible_format_views ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0);
image_info.imageType = (volume_texture ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D);
image_info.extent.width = static_cast<uint32_t>(params.width);
image_info.extent.height = image_height;
image_info.extent.depth = (volume_texture ? params.depth : 1);
image_info.mipLevels = image_mips;
image_info.arrayLayers = (array_texture ? params.depth : 1);
image_info.format = pixel_format;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = TextureGetUsage(params.format_usage);
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
const bool storage_view = TextureHasFormatUsage(params.view_usage, TextureFormatUsage::Storage);
if (storage_view && !TextureCheckStorageSwizzle(&image_info, &components)) {
if (!params.storage_swizzle_fallback) {
EXIT("swizzle is not supported");
}
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("\t %s swizzle 0x%08" PRIx64 " is not supported, using identity mapping\n",
params.owner, params.swizzle);
}
components.r = VK_COMPONENT_SWIZZLE_R;
components.g = VK_COMPONENT_SWIZZLE_G;
components.b = VK_COMPONENT_SWIZZLE_B;
components.a = VK_COMPONENT_SWIZZLE_A;
}
const auto view_checked_format = image_info.format;
const auto requested_usage = params.format_usage;
const auto required_usage = params.required_format_usage;
const bool has_optional_usage = static_cast<uint32_t>(requested_usage & ~required_usage) != 0;
if (!TextureCheckFormatExact(ctx, image_info) && has_optional_usage) {
auto required_info = image_info;
required_info.usage = TextureGetUsage(required_usage);
required_info.format = view_checked_format;
if (TextureCheckFormatExact(ctx, required_info)) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("\t %s usage 0x%08x is not supported for format %d, using required "
"usage 0x%08x\n",
params.owner, static_cast<uint32_t>(TextureGetUsage(requested_usage)),
static_cast<int>(image_info.format),
static_cast<uint32_t>(TextureGetUsage(required_usage)));
}
image_info = required_info;
}
}
if (!TextureCheckFormat(ctx, &image_info)) {
if (has_optional_usage) {
image_info.format = view_checked_format;
image_info.usage = TextureGetUsage(required_usage);
}
if (!has_optional_usage || !TextureCheckFormat(ctx, &image_info)) {
EXIT("format is not supported");
}
}
vk_obj->extent.width = image_info.extent.width;
vk_obj->extent.height = image_info.extent.height;
vk_obj->layers = image_info.arrayLayers;
vk_obj->mip_levels = image_info.mipLevels;
vk_obj->format = image_info.format;
vk_obj->image = nullptr;
vk_obj->layout = image_info.initialLayout;
UtilResetImageViews(vk_obj);
mem->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
bool created = VulkanCreateImage(ctx, &image_info, vk_obj, mem);
EXIT_NOT_IMPLEMENTED(!created);
vk_obj->memory = *mem;
return components;
}
void TextureCreateImageViews(GraphicContext* ctx, VulkanImage* vk_obj,
VkComponentMapping components, uint64_t type, uint32_t base_array,
uint32_t base_level, uint32_t level_count, uint32_t depth,
bool allow_cube_view, TextureFormatUsage view_usage) {
EXIT_IF(ctx == nullptr);
EXIT_IF(vk_obj == nullptr);
EXIT_IF(level_count == 0 || base_level + level_count > vk_obj->mip_levels);
const bool layered_texture = TextureIsLayeredTexture(type);
const bool volume_texture = TextureIs3DTexture(type);
const auto layer_count = (layered_texture && base_array < depth ? depth - base_array : 1u);
const auto volume_slices = std::max(depth >> base_level, 1u);
VkImageViewUsageCreateInfo usage_info {};
usage_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
usage_info.pNext = nullptr;
usage_info.usage = TextureGetViewUsage(view_usage);
VkImageViewCreateInfo create_info {};
create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
create_info.pNext = (usage_info.usage != 0 ? &usage_info : nullptr);
create_info.flags = 0;
create_info.image = vk_obj->image;
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
create_info.format = vk_obj->format;
create_info.components = components;
create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
create_info.subresourceRange.baseArrayLayer = (layered_texture ? base_array : 0);
create_info.subresourceRange.baseMipLevel = base_level;
create_info.subresourceRange.layerCount = layer_count;
create_info.subresourceRange.levelCount = level_count;
if (volume_texture) {
create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
create_info.subresourceRange.baseArrayLayer = 0;
create_info.subresourceRange.layerCount = 1;
} else if (allow_cube_view && TextureCanCreateCubeView(type, base_array, layer_count)) {
create_info.viewType =
(layer_count > 6 ? VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE);
} else if (layered_texture) {
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
}
vkCreateImageView(ctx->device, &create_info, nullptr,
&vk_obj->image_view[VulkanImage::VIEW_DEFAULT]);
EXIT_NOT_IMPLEMENTED(vk_obj->image_view[VulkanImage::VIEW_DEFAULT] == nullptr);
if (!volume_texture || depth >= 1) {
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
create_info.subresourceRange.layerCount = (volume_texture ? volume_slices : layer_count);
vkCreateImageView(ctx->device, &create_info, nullptr,
&vk_obj->image_view[VulkanImage::VIEW_DEFAULT_ARRAY]);
EXIT_NOT_IMPLEMENTED(vk_obj->image_view[VulkanImage::VIEW_DEFAULT_ARRAY] == nullptr);
}
}
static uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride =
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
const auto guest_stride = total_size / depth;
if (guest_stride >= stride) {
stride = guest_stride;
}
}
return stride;
}
static uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
bytes_per_element != 0) {
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
}
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt);
bytes_per_block != 0) {
const uint32_t blocks_w = std::max((pitch + 3u) / 4u, 1u);
const uint32_t blocks_h = std::max((height + 3u) / 4u, 1u);
return static_cast<uint64_t>(blocks_w) * blocks_h * bytes_per_block;
}
return 0;
}
static uint64_t FillVolumeLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt,
uint64_t height, uint64_t levels,
uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h);
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull);
level_sizes[i].size = static_cast<uint32_t>(size);
level_sizes[i].offset = static_cast<uint32_t>(offset);
level_sizes[i].src_size = 0;
level_sizes[i].src_offset = 0;
level_sizes[i].x = 0;
level_sizes[i].y = 0;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile,
bool require_single_mip_small_tiles,
bool volume_texture, const char* owner) {
TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile);
layout.pitch = static_cast<uint32_t>(pitch);
layout.volume_texture = volume_texture;
if (fmt != 0) {
if (layout.tile != 0) {
const auto tile_mode = static_cast<Prospero::TileMode>(layout.tile);
layout.fmt_tiled_render_target =
(Prospero::RenderTargetBytesPerElement(static_cast<uint32_t>(fmt)) != 0 &&
tile_mode == Prospero::TileMode::kRenderTarget);
layout.fmt_tiled_standard256b =
(TileIsStandard256BTextureSupported(static_cast<uint32_t>(fmt)) &&
tile_mode == Prospero::TileMode::kStandard256B &&
(!require_single_mip_small_tiles || levels == 1));
layout.fmt_tiled_standard4kb =
(TileIsStandard4KBTextureSupported(static_cast<uint32_t>(fmt)) &&
tile_mode == Prospero::TileMode::kStandard4KB &&
(!require_single_mip_small_tiles || levels == 1));
layout.fmt_tiled_standard64kb =
(TileIsStandard64KBTextureSupported(static_cast<uint32_t>(fmt)) &&
tile_mode == Prospero::TileMode::kStandard64KB);
layout.fmt_tiled_depth =
(allow_depth_tile &&
Prospero::RenderTargetBytesPerElement(static_cast<uint32_t>(fmt)) != 0 &&
tile_mode == Prospero::TileMode::kDepth);
if (!layout.fmt_tiled_render_target && !layout.fmt_tiled_standard256b &&
!layout.fmt_tiled_standard4kb && !layout.fmt_tiled_standard64kb &&
!layout.fmt_tiled_depth) {
EXIT("%s: unsupported typed tiled upload, using linear fallback: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64
" levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, upload_size, width, height,
pitch, levels);
layout.tile = 0;
}
}
layout.pitch = TileGetTexturePitch(fmt, width, levels, layout.tile);
TileGetTextureSize(fmt, width, height, layout.pitch, levels, layout.tile, nullptr,
layout.level_sizes, layout.padded_sizes);
if (layout.volume_texture) {
TileSizeOffset source_levels[16] {};
std::copy_n(layout.level_sizes, levels, source_levels);
layout.slice_stride =
FillVolumeLinearUploadLevels(layout.level_sizes, fmt, height, levels, layout.pitch);
if (layout.fmt_tiled_render_target) {
layout.source_slice_stride =
CalcTextureSliceStride(source_levels, levels, upload_size, depth);
for (uint32_t level = 0; level < levels; level++) {
layout.level_sizes[level].src_offset = source_levels[level].offset;
layout.level_sizes[level].src_size = source_levels[level].size;
layout.level_sizes[level].x = source_levels[level].x;
layout.level_sizes[level].y = source_levels[level].y;
}
}
}
if (layout.fmt_tiled_depth) {
for (uint32_t i = 0; i < levels; i++) {
layout.level_sizes[i].x = layout.padded_sizes[i].width;
layout.level_sizes[i].y = layout.padded_sizes[i].height;
}
}
} else {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n",
owner, layout.tile, upload_size, width, height, pitch, levels);
}
if (!layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
return layout;
}
std::vector<BufferImageCopy>
TextureBuildUploadRegions(const TextureUploadLayout& layout, VkFormat image_format, uint32_t width,
uint32_t height, uint32_t depth, uint64_t levels, bool array_texture,
bool volume_texture, TextureUploadDestination destination,
TextureUploadSliceLayout slice_layout) {
uint32_t mip_width = width;
uint32_t mip_height = height;
uint32_t mip_pitch = layout.pitch;
std::vector<BufferImageCopy> regions(levels * depth);
for (uint32_t i = 0; i < levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
const auto mipmap_offset = UtilCalcMipmapOffset(i, width, height);
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
const auto slice_offset = (slice_layout == TextureUploadSliceLayout::MipChainPerSlice
? z * layout.slice_stride
: z * static_cast<uint64_t>(layout.level_sizes[i].size));
regions[region_index].offset =
static_cast<uint32_t>(layout.level_sizes[i].offset + slice_offset);
regions[region_index].width = mip_width;
regions[region_index].height = mip_height;
regions[region_index].copy_height =
(!array_texture && !volume_texture && depth > 1 && levels > 1 &&
UtilIsBcFormat(image_format)
? TextureGetAtlasSliceYStride(image_format, mip_height, depth, levels)
: 0);
regions[region_index].dst_layer = (array_texture ? z : 0);
regions[region_index].dst_z = (volume_texture ? static_cast<int>(z) : 0);
if (layout.fmt_tiled_depth && layout.level_sizes[i].x != 0) {
regions[region_index].pitch = layout.level_sizes[i].x;
} else if (!layout.volume_texture &&
static_cast<Prospero::TileMode>(layout.tile) ==
Prospero::TileMode::kLinear &&
layout.padded_sizes[i].width != 0) {
regions[region_index].pitch = layout.padded_sizes[i].width;
} else {
regions[region_index].pitch = mip_pitch;
}
if (destination == TextureUploadDestination::MipLevels) {
regions[region_index].dst_level = i;
regions[region_index].dst_x = 0;
regions[region_index].dst_y =
(array_texture || volume_texture
? 0
: static_cast<int>(z * TextureGetAtlasSliceYStride(
image_format, mip_height, depth, levels)));
} else {
regions[region_index].dst_level = 0;
regions[region_index].dst_x = mipmap_offset.first;
regions[region_index].dst_y =
(array_texture || volume_texture
? mipmap_offset.second
: mipmap_offset.second +
static_cast<int>(z * TextureGetAtlasSliceYStride(
image_format, mip_height, depth, levels)));
}
}
if (mip_width > 1) {
mip_width /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
if (mip_pitch > 1) {
mip_pitch /= 2;
}
}
return regions;
}
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
static uint64_t GetLevelSrcSize(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
}
static uint64_t GetSliceSrcStride(const TextureUploadLayout& layout, uint32_t level,
TextureUploadSliceLayout source_slice_layout) {
return (
source_slice_layout == TextureUploadSliceLayout::MipChainPerSlice
? (layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride)
: GetLevelSrcSize(layout.level_sizes[level]));
}
static uint64_t FmaskRegionCopySize(const BufferImageCopy& region) {
const uint64_t row_length = (region.pitch != 0 ? region.pitch : region.width);
if (region.height == 0 || region.width == 0) {
return 0;
}
return ((static_cast<uint64_t>(region.height - 1u) * row_length) + region.width) *
sizeof(uint32_t);
}
static void UploadFmaskIdentity(GraphicContext* ctx, VulkanImage* vk_obj,
const std::vector<BufferImageCopy>& regions, uint64_t dst_layout,
const char* owner) {
constexpr uint32_t kIdentityFmaskPattern = 0x76543210u;
std::vector<BufferImageCopy> upload_regions = regions;
uint64_t upload_size = 0;
for (auto& region: upload_regions) {
upload_size = (upload_size + 255u) & ~uint64_t {255};
EXIT_NOT_IMPLEMENTED(upload_size > UINT32_MAX);
region.offset = static_cast<uint32_t>(upload_size);
upload_size += FmaskRegionCopySize(region);
}
LOGF("%s: temporary: decoding PS5 FMASK8_S4_F4 as identity pattern 0x%08" PRIx32
", upload_size=%" PRIu64 " regions=%zu\n",
owner, kIdentityFmaskPattern, upload_size, upload_regions.size());
UtilScratchBuffer temp_buf(upload_size);
auto* words = static_cast<uint32_t*>(temp_buf.Data());
for (uint64_t i = 0; i < upload_size / sizeof(uint32_t); i++) {
words[i] = kIdentityFmaskPattern;
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), upload_size, upload_regions, dst_layout);
}
void TextureUploadGuestImage(GraphicContext* ctx, VulkanImage* vk_obj, const void* src_data,
uint64_t size, const std::vector<BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
uint64_t height, uint32_t depth, uint64_t levels,
TextureUploadSliceLayout source_slice_layout, const char* owner,
uint64_t dst_layout) {
if (fmt == 0) {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " depth=%u pitch=%u levels=%" PRIu64 "\n",
owner, layout.tile, size, width, height, depth, layout.pitch, levels);
} else if (static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear) {
UtilFillImage(ctx, vk_obj, src_data, size, regions, dst_layout);
} else if (layout.fmt_tiled_render_target) {
LOGF("%s: detiling typed render-target texture: fmt=%u tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " depth=%" PRIu64 " pitch=%u levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height,
static_cast<uint64_t>(depth), layout.pitch, levels);
UtilScratchBuffer temp_buf(size);
for (uint32_t i = 0; i < levels; i++) {
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
auto* dst = static_cast<uint8_t*>(temp_buf.Data()) + regions[region_index].offset;
const auto* src = static_cast<const uint8_t*>(src_data) +
TextureUploadSliceSourceOffset(layout, i, z, source_slice_layout);
TileConvertTiledToLinearRenderTarget(
dst, src, regions[region_index].width, regions[region_index].height,
regions[region_index].pitch,
Prospero::RenderTargetBytesPerElement(static_cast<uint32_t>(fmt)),
layout.level_sizes[i].size, GetLevelSrcSize(layout.level_sizes[i]),
layout.level_sizes[i].x, layout.level_sizes[i].y);
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
} else if (layout.fmt_tiled_depth) {
if (Prospero::IsFmaskTextureFormat(static_cast<uint32_t>(fmt))) {
UploadFmaskIdentity(ctx, vk_obj, regions, dst_layout, owner);
return;
}
const uint32_t bytes_per_element =
Prospero::RenderTargetBytesPerElement(static_cast<uint32_t>(fmt));
if (bytes_per_element == 1) {
UtilFillImage(ctx, vk_obj, src_data, size, regions, dst_layout);
} else {
LOGF("%s: detiling typed depth texture: fmt=%u tile=%u size=%" PRIu64 " extent=%" PRIu64
"x%" PRIu64 " depth=%" PRIu64 " pitch=%u levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height,
static_cast<uint64_t>(depth), layout.pitch, levels);
UtilScratchBuffer temp_buf(size);
for (uint32_t i = 0; i < levels; i++) {
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
auto* dst =
static_cast<uint8_t*>(temp_buf.Data()) + regions[region_index].offset;
const auto* src = static_cast<const uint8_t*>(src_data) +
GetLevelSrcOffset(layout.level_sizes[i]) +
z * GetSliceSrcStride(layout, i, source_slice_layout);
TileConvertTiledToLinearDepth(
dst, src, static_cast<uint32_t>(fmt), regions[region_index].width,
regions[region_index].height, regions[region_index].pitch,
layout.level_sizes[i].size);
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
}
} else if (layout.fmt_tiled_standard256b) {
UtilScratchBuffer temp_buf(size);
for (uint32_t i = 0; i < levels; i++) {
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
auto* dst = static_cast<uint8_t*>(temp_buf.Data()) + regions[region_index].offset;
const auto* src = static_cast<const uint8_t*>(src_data) +
layout.level_sizes[i].offset +
z * GetSliceSrcStride(layout, i, source_slice_layout);
TileConvertTiledToLinearStandard256B(
dst, src, static_cast<uint32_t>(fmt), regions[region_index].width,
regions[region_index].height, regions[region_index].pitch,
layout.level_sizes[i].size, layout.level_sizes[i].size);
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
} else if (layout.fmt_tiled_standard4kb && layout.volume_texture) {
LOGF("%s: detiling typed Standard4KB 3D texture: fmt=%u tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " depth=%" PRIu64 " pitch=%u levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height,
static_cast<uint64_t>(depth), layout.pitch, levels);
UtilScratchBuffer temp_buf(size);
if (levels == 1) {
TileConvertTiledToLinearStandard4KB3D(
temp_buf.Data(), src_data, static_cast<uint32_t>(fmt), static_cast<uint32_t>(width),
static_cast<uint32_t>(height), depth, layout.pitch, layout.slice_stride, size,
size);
} else {
std::memset(temp_buf.Data(), 0, static_cast<size_t>(size));
Standard4KBVolumeMipLayout volume_layout {};
EXIT_NOT_IMPLEMENTED(!CalcStandard4kbVolumeMipLayout(
static_cast<uint32_t>(fmt), layout.pitch, static_cast<uint32_t>(height), levels,
&volume_layout));
auto* dst_base = static_cast<uint8_t*>(temp_buf.Data());
const auto* src_base = static_cast<const uint8_t*>(src_data);
uint32_t mip_width = static_cast<uint32_t>(width);
uint32_t mip_height = static_cast<uint32_t>(height);
uint32_t mip_pitch = layout.pitch;
for (uint32_t level = 0; level < levels; level++) {
if (level < volume_layout.first_tail_level) {
for (uint32_t z = 0; z < depth; z += volume_layout.block_depth) {
const uint32_t copy_depth = std::min(volume_layout.block_depth, depth - z);
const auto region_index = level * depth + z;
auto* dst = dst_base + regions[region_index].offset;
const auto* src = src_base +
(static_cast<uint64_t>(z / volume_layout.block_depth) *
volume_layout.block_slice_size) +
volume_layout.level_offsets[level];
const uint64_t dst_size =
(static_cast<uint64_t>(copy_depth - 1u) * layout.slice_stride) +
layout.level_sizes[level].size;
TileConvertTiledToLinearStandard4KB3D(
dst, src, static_cast<uint32_t>(fmt), mip_width, mip_height, copy_depth,
mip_pitch, layout.slice_stride, dst_size,
volume_layout.level_sizes[level], false);
}
}
if (mip_width > 1) {
mip_width /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
if (mip_pitch > 1) {
mip_pitch /= 2;
}
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
} else if (layout.fmt_tiled_standard4kb) {
LOGF("%s: detiling typed Standard4KB texture: fmt=%u tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " depth=%" PRIu64 " pitch=%u\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height,
static_cast<uint64_t>(depth), layout.pitch);
UtilScratchBuffer temp_buf(size);
for (uint32_t i = 0; i < levels; i++) {
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
auto* dst = static_cast<uint8_t*>(temp_buf.Data()) + regions[region_index].offset;
const auto* src = static_cast<const uint8_t*>(src_data) +
GetLevelSrcOffset(layout.level_sizes[i]) +
z * GetSliceSrcStride(layout, i, source_slice_layout);
TileConvertTiledToLinearStandard4KB(
dst, src, static_cast<uint32_t>(fmt), regions[region_index].width,
regions[region_index].height, regions[region_index].pitch,
layout.level_sizes[i].size, GetLevelSrcSize(layout.level_sizes[i]),
layout.level_sizes[i].x, layout.level_sizes[i].y);
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
} else if (layout.fmt_tiled_standard64kb) {
LOGF("%s: detiling typed Standard64KB texture: fmt=%u tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " depth=%" PRIu64 " pitch=%u\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height,
static_cast<uint64_t>(depth), layout.pitch);
UtilScratchBuffer temp_buf(size);
for (uint32_t i = 0; i < levels; i++) {
for (uint32_t z = 0; z < depth; z++) {
const auto region_index = i * depth + z;
auto* dst = static_cast<uint8_t*>(temp_buf.Data()) + regions[region_index].offset;
const auto* src = static_cast<const uint8_t*>(src_data) +
GetLevelSrcOffset(layout.level_sizes[i]) +
z * GetSliceSrcStride(layout, i, source_slice_layout);
TileConvertTiledToLinearStandard64KB(
dst, src, static_cast<uint32_t>(fmt), regions[region_index].width,
regions[region_index].height, regions[region_index].pitch,
layout.level_sizes[i].size, GetLevelSrcSize(layout.level_sizes[i]),
layout.level_sizes[i].x, layout.level_sizes[i].y);
}
}
UtilFillImage(ctx, vk_obj, temp_buf.Data(), size, regions, dst_layout);
} else if (layout.tile != 0) {
EXIT("%s: typed tiled upload still unsupported after sizing, using linear fallback: fmt=%u "
"tile=%u size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%u levels=%" PRIu64
"\n",
owner, static_cast<uint32_t>(fmt), layout.tile, size, width, height, layout.pitch,
levels);
UtilFillImage(ctx, vk_obj, src_data, size, regions, dst_layout);
}
}
} // namespace Libs::Graphics