Compare commits

...

2 Commits

Author SHA1 Message Date
nmzik 3da05eff71 kernel: share directory streams across seeks and reads
Fix PS5 boot failure when an optional empty sound path opens /app0/ and the game seeks and reads that directory. Pack directory records once and share their bytes and position across raw reads, enumeration, seeking, and size reporting. Apply seek and read error semantics with checked offsets.

Add multiblock directory regression coverage using the existing production Vulkan resource lifecycle. Existing CMake build and kernel_file_system test pass. Independent correctness, fixture, runtime, and final compactness audits passed; launch.json run reached Jurassic Park gameplay with 4560 sampled flips, no Vulkan validation errors, and normal window-close exit.
2026-09-10 14:36:15 +04:00
nmzik 489d483362 graphics: simplify texture upload and component mapping paths
Use an explicit selector switch and descriptive channel, mip, and slice names. Share volume/array tile setup, hoist per-mip copy work, consolidate tiled validation, and remove redundant code.

Preserve layout bounds, mip tails, source strides, and cache interfaces. Independent correctness and compactness audits passed; native packed-texture tests and 330 GPU tiler parity cases passed.
2026-09-10 14:36:15 +04:00
4 changed files with 400 additions and 393 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;
}
@@ -1,9 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -48,13 +46,13 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order);
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);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& infos);
std::vector<GpuTileInfo>& out_tile_infos);
} // namespace Libs::Graphics
+109 -132
View File
@@ -28,7 +28,8 @@ namespace Libs::LibKernel::FileSystem {
LIB_NAME("libkernel", "libkernel");
constexpr int DESCRIPTOR_MIN = 3;
constexpr int DESCRIPTOR_MIN = 3;
constexpr uint64_t DIR_BLOCK_SIZE = 512;
enum class SpecialFile {
None,
@@ -68,7 +69,7 @@ struct File {
std::atomic_bool sync_writes;
SpecialFile special;
Common::Mutex mutex;
std::vector<Common::File::DirEntry> dents;
std::vector<uint8_t> dirents;
uint64_t dents_offset;
};
@@ -119,6 +120,51 @@ static uint64_t AlignUp(uint64_t value, uint64_t alignment) {
return (alignment != 0 ? (value + alignment - 1) & ~(alignment - 1) : value);
}
static std::vector<uint8_t> PackDirents(const std::vector<Common::File::DirEntry>& entries) {
std::vector<uint8_t> dirents;
uint64_t offset = 0;
uint64_t last_reclen_offset = 0;
for (const auto& entry: entries) {
const auto& name = entry.name;
EXIT_NOT_IMPLEMENTED(name.size() > 255);
const auto reclen = AlignUp(8 + name.size() + 1, 4);
if (offset + reclen > dirents.size()) {
if (!dirents.empty()) {
auto* last_reclen =
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
}
offset = dirents.size();
dirents.resize(offset + DIR_BLOCK_SIZE);
}
*reinterpret_cast<uint32_t*>(dirents.data() + offset) =
Common::hash(name.data(), static_cast<uint32_t>(name.size()));
*reinterpret_cast<uint16_t*>(dirents.data() + offset + 4) = static_cast<uint16_t>(reclen);
dirents[offset + 6] = entry.is_file ? 8 : 4;
dirents[offset + 7] = static_cast<uint8_t>(name.size());
memcpy(dirents.data() + offset + 8, name.c_str(), name.size() + 1);
last_reclen_offset = offset + 4;
offset += reclen;
}
if (!dirents.empty()) {
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen += static_cast<uint16_t>(dirents.size() - offset);
}
return dirents;
}
static uint64_t ReadDirectory(File* file, void* buf, uint64_t count) {
const auto remaining =
file->dents_offset < file->dirents.size() ? file->dirents.size() - file->dents_offset : 0;
const auto bytes = std::min(count, remaining);
if (bytes != 0) {
Memory::InvalidateMemory(reinterpret_cast<uint64_t>(buf), bytes);
memcpy(buf, file->dirents.data() + file->dents_offset, bytes);
file->dents_offset += bytes;
}
return bytes;
}
static int PosixToKernel(int posix_errno) {
return KERNEL_ERROR_UNKNOWN + posix_errno;
}
@@ -433,15 +479,16 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) {
EXIT_NOT_IMPLEMENTED(!directory && rw_mode != Common::File::Mode::Read);
EXIT_NOT_IMPLEMENTED(!directory && (trunc || creat));
file->dents = Common::File::GetDirEntries(file->real_name);
const auto entries = Common::File::GetDirEntries(file->real_name);
file->dirents = PackDirents(entries);
file->dents_offset = 0;
file->directory = true;
LOGF_COLOR(Log::Color::Green, "\tOpen dir: %s, entries = %" PRIu32 ", [ok]\n",
Common::PathToString(file->real_name).c_str(),
static_cast<uint32_t>(file->dents.size()));
static_cast<uint32_t>(entries.size()));
for (const auto& f: file->dents) {
for (const auto& f: entries) {
LOGF("\t\t%s %s\n", f.is_file ? "[file]" : "[dir ]", f.name.c_str());
}
} else {
@@ -538,11 +585,18 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes) {
return KERNEL_ERROR_EBADF;
}
EXIT_NOT_IMPLEMENTED(file->directory);
EXIT_IF(!file->opened);
EXIT_NOT_IMPLEMENTED(nbytes > UINT_MAX);
if (nbytes > INT_MAX) {
return KERNEL_ERROR_EINVAL;
}
if (file->directory) {
Common::LockGuard lock(file->mutex);
const auto bytes = ReadDirectory(file, buf, nbytes);
LOGF("\tRead %" PRIu64 " directory bytes from: %s\n", bytes,
Common::PathToString(file->real_name).c_str());
return static_cast<int64_t>(bytes);
}
if (file->special == SpecialFile::Random) {
FillRandomBuffer(buf, nbytes);
@@ -761,7 +815,7 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
PRINT_NAME();
if (d < DESCRIPTOR_MIN) {
return KERNEL_ERROR_EPERM;
return KERNEL_ERROR_EBADF;
}
auto* file = g_files->GetFile(d);
@@ -770,8 +824,6 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
return KERNEL_ERROR_EBADF;
}
EXIT_NOT_IMPLEMENTED(file->directory);
EXIT_IF(!file->opened);
if (file->special != SpecialFile::None) {
@@ -780,38 +832,47 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
Common::LockGuard lock(file->mutex);
bool is_invalid = file->f.IsInvalid();
if (whence == 1) {
offset = static_cast<int64_t>(file->f.Tell()) + offset;
whence = 0;
}
if (whence == 2) {
offset = static_cast<int64_t>(file->f.Size()) + offset;
whence = 0;
}
EXIT_NOT_IMPLEMENTED(whence != 0);
if (offset < 0) {
return KERNEL_ERROR_EINVAL;
}
file->f.Seek(offset);
auto pos = static_cast<int64_t>(file->f.Tell());
EXIT_IF(pos != offset);
if (is_invalid) {
if (!file->directory && file->f.IsInvalid()) {
LOGF("\tfile is invalid\n");
return KERNEL_ERROR_EIO;
}
LOGF("\tLseek (pos = %" PRId64 ") to: %s\n", offset,
uint64_t base = 0;
switch (whence) {
case 0: break;
case 1: base = file->directory ? file->dents_offset : file->f.Tell(); break;
case 2: base = file->directory ? file->dirents.size() : file->f.Size(); break;
default: return KERNEL_ERROR_EINVAL;
}
uint64_t position = 0;
if (offset >= 0) {
if (base > static_cast<uint64_t>(INT64_MAX - offset)) {
return KERNEL_ERROR_EOVERFLOW;
}
position = base + static_cast<uint64_t>(offset);
} else {
const auto distance = uint64_t {0} - static_cast<uint64_t>(offset);
if (base < distance) {
return KERNEL_ERROR_EINVAL;
}
position = base - distance;
if (position > INT64_MAX) {
return KERNEL_ERROR_EOVERFLOW;
}
}
if (file->directory) {
file->dents_offset = position;
} else {
file->f.Seek(position);
EXIT_IF(file->f.Tell() != position);
}
LOGF("\tLseek (pos = %" PRIu64 ") to: %s\n", position,
Common::PathToString(file->real_name).c_str());
return pos;
return static_cast<int64_t>(position);
}
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
@@ -842,9 +903,10 @@ int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb) {
auto wt = at;
if (is_dir) {
stat.st_size = 0;
stat.st_size =
static_cast<int64_t>(PackDirents(Common::File::GetDirEntries(real_file_name)).size());
stat.st_blksize = 512;
stat.st_blocks = 0;
stat.st_blocks = stat.st_size / 512;
} else {
stat.st_size = static_cast<int64_t>(Common::File::Size(real_file_name));
stat.st_blksize = 512;
@@ -920,9 +982,9 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
stat.st_blksize = 512;
stat.st_blocks = (stat.st_size + 511) / 512;
} else {
stat.st_size = 0;
stat.st_size = static_cast<int64_t>(file->dirents.size());
stat.st_blksize = 512;
stat.st_blocks = 0;
stat.st_blocks = stat.st_size / 512;
}
SecToTimespec(&stat.st_atim, at.ToUnix());
@@ -1057,14 +1119,15 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
return KERNEL_ERROR_EBADF;
}
constexpr uint64_t DIR_BLOCK_SIZE = 512;
constexpr uint64_t DIRENT_META_SIZE = 8;
if (!file->directory || nbytes < static_cast<int>(DIR_BLOCK_SIZE)) {
return KERNEL_ERROR_EINVAL;
}
EXIT_IF(!file->opened);
Common::LockGuard lock(file->mutex);
if (file->dents_offset > file->dirents.size() || file->dents_offset % DIR_BLOCK_SIZE != 0) {
return KERNEL_ERROR_EINVAL;
}
LOGF("\t dir = %s\n"
"\t nbytes = %d\n"
@@ -1074,94 +1137,8 @@ int KYTY_SYSV_ABI KernelGetdirentries(int fd, char* buf, int nbytes, int64_t* ba
if (basep != nullptr) {
*basep = static_cast<int64_t>(file->dents_offset);
}
std::vector<uint8_t> dirents;
dirents.reserve(
std::max<size_t>(static_cast<size_t>(DIR_BLOCK_SIZE), file->dents.size() * 64u));
uint64_t next_ceiling = 0;
uint64_t dirent_offset = 0;
int64_t last_reclen_offset = -1;
for (const auto& entry: file->dents) {
const auto& str = entry.name;
auto str_size = str.size();
EXIT_NOT_IMPLEMENTED(str_size > 255);
uint64_t reclen = AlignUp(DIRENT_META_SIZE + str_size + 1, 4);
if (dirent_offset + reclen > next_ceiling) {
if (last_reclen_offset >= 0) {
auto* last_reclen =
reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
}
dirent_offset = next_ceiling;
next_ceiling += DIR_BLOCK_SIZE;
dirents.resize(next_ceiling);
std::fill(dirents.begin() + static_cast<std::ptrdiff_t>(dirent_offset), dirents.end(),
0);
}
*reinterpret_cast<uint32_t*>(dirents.data() + dirent_offset + 0) =
Common::hash(str.data(), static_cast<uint32_t>(str.size()));
*reinterpret_cast<uint16_t*>(dirents.data() + dirent_offset + 4) =
static_cast<uint16_t>(reclen);
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 6) = (entry.is_file ? 8 : 4);
*reinterpret_cast<uint8_t*>(dirents.data() + dirent_offset + 7) =
static_cast<uint8_t>(str_size);
memcpy(dirents.data() + dirent_offset + 8, str.data(), str_size);
dirents[dirent_offset + 8 + str_size] = '\0';
last_reclen_offset = static_cast<int64_t>(dirent_offset + 4);
dirent_offset += reclen;
LOGF("\t name = %s\n", str.data());
}
if (last_reclen_offset >= 0) {
auto* last_reclen = reinterpret_cast<uint16_t*>(dirents.data() + last_reclen_offset);
*last_reclen = static_cast<uint16_t>(*last_reclen + (next_ceiling - dirent_offset));
}
const uint64_t directory_size = next_ceiling;
if (file->dents_offset >= directory_size) {
return 0;
}
uint64_t bytes_written = 0;
uint64_t working_offset = file->dents_offset;
uint64_t dirent_buffer_offset = 0;
uint64_t aligned_count = AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE);
while (dirent_buffer_offset < dirents.size()) {
const auto* normal_dirent = dirents.data() + dirent_buffer_offset;
uint16_t reclen = *reinterpret_cast<const uint16_t*>(normal_dirent + 4);
uint8_t namlen = *(normal_dirent + 7);
if (namlen == 0 || reclen == 0) {
break;
}
if (working_offset >= reclen) {
dirent_buffer_offset += reclen;
working_offset -= reclen;
continue;
}
if (bytes_written + reclen > aligned_count) {
break;
}
const auto bytes_to_copy = reclen - working_offset;
memcpy(buf + bytes_written, normal_dirent + working_offset, bytes_to_copy);
bytes_written += bytes_to_copy;
dirent_buffer_offset += reclen;
working_offset = 0;
}
file->dents_offset += bytes_written;
return static_cast<int64_t>(bytes_written);
return static_cast<int>(
ReadDirectory(file, buf, AlignDown(static_cast<uint64_t>(nbytes), DIR_BLOCK_SIZE)));
}
int KYTY_SYSV_ABI KernelGetdents(int fd, char* buf, int nbytes) {
+119
View File
@@ -1,8 +1,10 @@
#include "SDL.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "common/threads.h"
#include "graphics/presentation/window/windowInternal.h"
#include "kernel/fileSystem.h"
#include "libs/errno.h"
#include "libs/network.h"
@@ -15,8 +17,11 @@
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace Libs::LibKernelApr {
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
@@ -123,6 +128,109 @@ void CheckMountRoot(const std::filesystem::path &root) {
FileSystem::Umount("/app0");
}
void CheckDirectoryStream(const std::filesystem::path &root) {
const auto directory = root / "directory-stream";
Check(std::filesystem::create_directory(directory),
"create seek fixture directory");
for (int i = 0; i < 48; ++i) {
Common::File fixture;
Check(fixture.Create(directory / ("directory-entry-" + std::to_string(i))),
"create enough entries to cross directory blocks");
fixture.Close();
}
FileSystem::Mount(directory, "/app0");
const int fd = FileSystem::KernelOpen("/app0/", 0, 0);
Check(fd >= 3, "open directory as read-only asset");
const auto end = FileSystem::KernelLseek(fd, 0, 2);
Check(end > 512 && end % 512 == 0,
"directory SEEK_END uses padded stream size");
FileSystem::FileStat stat{};
Check(FileSystem::KernelFstat(fd, &stat) == OK && stat.st_size == end &&
stat.st_blksize == 512,
"directory stat agrees with seek and enumeration");
Check(FileSystem::KernelStat("/app0/", &stat) == OK && stat.st_size == end &&
stat.st_blocks == end / 512,
"path and descriptor directory stat agree");
Check(FileSystem::KernelLseek(fd, 0, 0) == 0,
"rewind directory for asset read");
std::vector<char> raw(static_cast<size_t>(end));
Check(FileSystem::KernelRead(fd, raw.data(), raw.size()) == end &&
FileSystem::KernelRead(fd, raw.data(), 1) == 0,
"raw directory read reaches EOF");
Check(FileSystem::KernelLseek(fd, -end, 1) == 0,
"directory SEEK_CUR uses the position advanced by read");
std::array<char, 512> block{};
int64_t base = -1;
for (int64_t offset = 0; offset < end; offset += block.size()) {
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == block.size() &&
base == offset &&
std::memcmp(block.data(), raw.data() + offset, block.size()) == 0,
"directory enumeration shares raw bytes and reports each block "
"position");
for (size_t pos = 0; pos < block.size();) {
Check(block.size() - pos >= 8, "directory record header fits");
uint16_t length = 0;
std::memcpy(&length, block.data() + pos + 4, sizeof(length));
const auto name_length = static_cast<uint8_t>(block[pos + 7]);
Check(length >= 9 + name_length && length <= block.size() - pos &&
block[pos + 8 + name_length] == '\0',
"directory entries remain complete within every block");
pos += length;
}
}
Check(FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == 0 &&
base == end,
"directory enumeration reports EOF position");
Check(FileSystem::KernelLseek(fd, 512, 0) == 512 &&
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) == block.size() &&
base == 512 &&
std::memcmp(block.data(), raw.data() + 512, block.size()) == 0,
"restore and reread a directory enumeration position");
const auto position = FileSystem::KernelLseek(fd, 0, 1);
Check(
FileSystem::KernelLseek(fd, 0, 9) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, -1, 0) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::min(), 1) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, std::numeric_limits<int64_t>::max(), 1) ==
Libs::LibKernel::KERNEL_ERROR_EOVERFLOW &&
FileSystem::KernelLseek(fd, 0, 1) == position,
"invalid and overflowing directory seeks preserve the position");
Check(FileSystem::KernelLseek(fd, -19, 2) == end - 19 &&
FileSystem::KernelRead(fd, block.data(), block.size()) == 19 &&
std::memcmp(block.data(), raw.data() + end - 19, 19) == 0,
"raw directory reads support byte positions and stop at EOF");
Check(FileSystem::KernelLseek(fd, 1, 0) == 1 &&
FileSystem::KernelGetdents(fd, block.data(), block.size()) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == 1,
"directory enumeration rejects an incomplete record position");
Check(FileSystem::KernelLseek(fd, 512, 2) == end + 512 &&
FileSystem::KernelGetdirentries(fd, block.data(), block.size(),
&base) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
"directory enumeration rejects a position beyond EOF");
Check(FileSystem::KernelRead(
fd, block.data(),
static_cast<size_t>(std::numeric_limits<int>::max()) + 1) ==
Libs::LibKernel::KERNEL_ERROR_EINVAL &&
FileSystem::KernelLseek(fd, 0, 1) == end + 512,
"oversized read fails without changing the directory position");
Check(FileSystem::KernelClose(fd) == OK, "close directory stream");
Check(FileSystem::KernelLseek(fd, 0, 0) ==
Libs::LibKernel::KERNEL_ERROR_EBADF,
"seek rejects a closed descriptor");
FileSystem::Umount("/app0");
}
void CheckAprPaths(const std::filesystem::path &root) {
Loader::SymbolDatabase symbols;
Libs::LibKernelApr::InitLibKernel_1_Apr(&symbols);
@@ -267,15 +375,26 @@ int main() {
Config::Load(options);
subsystems.Initialize<Log::Lifecycle>();
Check(SDL_InitSubSystem(SDL_INIT_VIDEO) == 0, "initialize Vulkan test video");
auto graphics = std::make_unique<Libs::Graphics::WindowContext>();
graphics->graphic_ctx.screen_width = 64;
graphics->graphic_ctx.screen_height = 64;
graphics->window = SDL_CreateWindow("KernelFileSystemTests", 0, 0, 64, 64,
SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
Check(graphics->window != nullptr, "create hidden Vulkan test window");
graphics->CreateVulkan();
TempDirectory temporary;
FileSystem::Initialize();
CheckMountRoot(temporary.Path());
CheckDirectoryStream(temporary.Path());
CheckAprPaths(temporary.Path());
FileSystem::Mount(temporary.Path(), "/savedata0");
CheckSaveRename(temporary.Path(), "first-save");
CheckSaveRename(temporary.Path(), "replacement-save");
FileSystem::Shutdown();
CheckSocketWakeup();
graphics.reset();
subsystems.Destroy();
std::printf("KernelFileSystemTests: all cases passed\n");