graphics: simplify texture upload and component mapping paths

This commit is contained in:
nmzik
2026-09-10 13:52:48 +04:00
parent a305a6c1c6
commit 23e7df21b1
2 changed files with 172 additions and 261 deletions
@@ -8,9 +8,8 @@
#include "graphics/shader/shader.h"
#include <algorithm>
#include <bit>
#include <array>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
@@ -57,32 +56,11 @@ HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
}
}
} // namespace
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host = ResolveHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
namespace {
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
size_t GetTextureRegionCount(uint32_t depth, uint32_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
@@ -90,63 +68,15 @@ static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume
return count;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s,
Prospero::ColorComponentMapping storage_to_host) {
if (s >= 4u && s <= 7u) {
s = static_cast<uint8_t>(4u + storage_to_host.Map(s - 4u));
}
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
}
} // namespace
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage) {
Prospero::ColorComponentMapping storage_to_host {0u};
for (uint32_t host = 0; host < 4u; ++host) {
storage_to_host.packed |= static_cast<uint8_t>(host << (host_to_storage.Map(host) * 2u));
}
vk::ComponentMapping components {};
components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0), storage_to_host);
components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1), storage_to_host);
components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2), storage_to_host);
components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3), storage_to_host);
return components;
}
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto host = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (host.format != vk::Format::eUndefined) {
return {host.format, conversion_format, host.host_to_storage};
}
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
}
namespace {
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint64_t levels,
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint32_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, mips[i].offset + mips[i].size);
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
stride = std::max(stride, mips[mip_level].offset + mips[mip_level].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;
}
stride = std::max(stride, total_size / depth);
}
return stride;
@@ -154,48 +84,102 @@ uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint64_t lev
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t elements_w =
const uint32_t element_columns =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t elements_h =
const uint32_t element_rows =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
return static_cast<uint64_t>(element_columns) * element_rows * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, 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);
const TileTextureElementLayout& element, uint32_t height,
uint32_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto mip_pitch = base_pitch;
auto mip_height = height;
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(element, pitch, h);
mips[i].size = size;
mips[i].offset = offset;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto size = CalcLinearUploadLevelSize(element, mip_pitch, mip_height);
mips[mip_level] = {offset, size};
offset += size;
if (pitch > 1) {
pitch /= 2;
if (mip_pitch > 1) {
mip_pitch /= 2;
}
if (h > 1) {
h /= 2;
if (mip_height > 1) {
mip_height /= 2;
}
}
return offset;
}
bool FitsBufferRange(uint64_t offset, uint64_t size, uint64_t capacity) {
return offset <= capacity && size <= capacity - offset;
}
} // namespace
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host_format.format, bytes, host_format.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage) {
constexpr std::array host_components {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA};
std::array<vk::ComponentSwizzle, 4> storage_components {};
for (uint32_t host_component = 0; host_component < host_components.size(); ++host_component) {
storage_components[host_to_storage.Map(host_component)] = host_components[host_component];
}
const auto resolve = [&](uint32_t component) {
const auto selector = static_cast<Prospero::CompSwizzle>(GetDstSel(swizzle, component));
switch (selector) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return storage_components[0];
case Prospero::CompSwizzle::kGreen: return storage_components[1];
case Prospero::CompSwizzle::kBlue: return storage_components[2];
case Prospero::CompSwizzle::kAlpha: return storage_components[3];
default: EXIT("unknown swizzle: %u\n", static_cast<uint32_t>(selector));
}
};
return {resolve(0), resolve(1), resolve(2), resolve(3)};
}
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto host_format = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (host_format.format != vk::Format::eUndefined) {
return {host_format.format, conversion_format, host_format.host_to_storage};
}
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
}
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
format,
tile,
tile_mode,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
height,
@@ -204,7 +188,6 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (format == Prospero::BufferFormat::kInvalid) {
@@ -214,34 +197,19 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
if (tile_mode != Prospero::TileMode::kLinear) {
if (tile_mode == Prospero::TileMode::kLinear) {
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
} else {
if ((tile_mode == Prospero::TileMode::kDepth && !allow_depth_tile) ||
!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
upload_size, width, height, levels);
}
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
@@ -274,40 +242,35 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const bool linear_texture = description.tile_mode == Prospero::TileMode::kLinear;
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && !linear_texture ? description.width : layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.mips[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset = layout.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = description.tile_mode == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
}
for (uint32_t mip_level = 0; mip_level < description.levels; ++mip_level) {
EXIT_NOT_IMPLEMENTED(layout.mips[mip_level].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
vk::BufferImageCopy region {};
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
region.imageExtent = {mip_width, mip_height, 1};
if (linear_texture) {
region.bufferRowLength = layout.mips[mip_level].row_length;
region.bufferImageHeight = layout.mips[mip_level].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align_to_texel = [texel_width](uint32_t dimension) {
return ((dimension + texel_width - 1u) / texel_width) * texel_width;
};
const auto aligned_pitch = align_to_texel(mip_pitch);
region.bufferRowLength = aligned_pitch > align_to_texel(mip_width) ? aligned_pitch : 0;
}
for (uint32_t slice_index = 0; slice_index < mip_depth; ++slice_index) {
region.bufferOffset = layout.mips[mip_level].offset + slice_index * layout.slice_stride;
region.imageSubresource.baseArrayLayer = volume_texture ? 0u : slice_index;
region.imageOffset.z = volume_texture ? static_cast<int>(slice_index) : 0;
regions.push_back(region);
}
@@ -325,17 +288,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
return regions;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) {
return false;
}
size = length;
return true;
}
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& out_infos) {
std::vector<GpuTileInfo>& out_tile_infos) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
@@ -353,111 +308,69 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (volume_texture) {
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= surface.first_tail_level;
const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * surface.block_slice_size +
surface.mips[level].offset;
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.mips[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, surface.mips[level].size, tiled_size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.depth = copy_depth;
info.surface_z =
std::vector<GpuTileInfo> tile_infos;
tile_infos.reserve(regions.size());
const auto slices_per_copy = volume_texture ? block.block_depth : 1u;
size_t region_base = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto& mip = surface.mips[mip_level];
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
for (uint32_t slice_index = 0; slice_index < mip_depth; slice_index += slices_per_copy) {
const auto& region = regions[region_base + slice_index];
GpuTileInfo tile_info {};
tile_info.family = block.family;
tile_info.bytes_per_element = block.bytes_per_element;
tile_info.linear_offset = region.bufferOffset;
tile_info.linear_size = layout.mips[mip_level].size;
tile_info.tiled_size = mip.size;
if (volume_texture) {
tile_info.depth = std::min(slices_per_copy, mip_depth - slice_index);
tile_info.linear_slice_stride = layout.slice_stride;
tile_info.linear_size += (tile_info.depth - 1u) * layout.slice_stride;
tile_info.tiled_offset =
mip.offset +
static_cast<uint64_t>(slice_index / slices_per_copy) * surface.block_slice_size;
tile_info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail;
info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.mips[level];
const auto& mip = surface.mips[level];
const bool tail = level >= surface.first_tail_level;
const auto level_depth = GetTextureLevelDepth(depth, level, false);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
} else {
const auto source_stride = layout.source_slice_stride != 0
? layout.source_slice_stride
: surface.block_slice_size;
if (z > (UINT64_MAX - mip.offset) / source_stride) {
if (slice_index > (UINT64_MAX - mip.offset) / source_stride) {
return false;
}
info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
return false;
}
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail;
info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = mip.padded_width;
info.tiled_height = mip.padded_height;
infos.push_back(info);
tile_info.tiled_offset =
mip.offset + static_cast<uint64_t>(slice_index) * source_stride;
tile_info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
}
if (!FitsBufferRange(tile_info.linear_offset, tile_info.linear_size, UINT64_MAX) ||
!FitsBufferRange(tile_info.tiled_offset, tile_info.tiled_size, tiled_size)) {
return false;
}
const auto row_length =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto image_height = region.bufferImageHeight != 0 ? region.bufferImageHeight
: region.imageExtent.height;
tile_info.width = std::max(
(region.imageExtent.width + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.height =
std::max((image_height + texture.texel_height - 1u) / texture.texel_height, 1u);
tile_info.pitch =
std::max((row_length + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.tail = mip_level >= surface.first_tail_level;
tile_info.tail_x = tile_info.tail ? mip.tail_x : 0;
tile_info.tail_y = tile_info.tail ? mip.tail_y : 0;
tile_info.tiled_width = mip.padded_width;
tile_info.tiled_height = mip.padded_height;
tile_infos.push_back(tile_info);
}
region_base += mip_depth;
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
out_tile_infos = std::move(tile_infos);
return true;
}