mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
graphics: represent rebased contiguous mip-tail texture views
Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
@@ -21,6 +21,54 @@ bool IsDepthPlane(const ImageInfo& info) {
|
||||
info.tile_mode == Prospero::TileMode::kLinear);
|
||||
}
|
||||
|
||||
GuestRange ContiguousMipRange(const ImageInfo& info, uint32_t level) {
|
||||
const auto& block = info.tiling.block;
|
||||
if (info.tile_mode != Prospero::TileMode::kStandard4KB ||
|
||||
block.family != TileBlockFamily::Standard4KB || info.IsVolume() || info.samples != 1 ||
|
||||
info.HasMetadata() || level >= info.resources.levels || level >= info.mip_layout.size() ||
|
||||
info.tiling.texel_width == 0 || info.tiling.texel_height == 0) {
|
||||
return {};
|
||||
}
|
||||
const auto extent = info.MipExtent(level);
|
||||
const auto width = (extent.width + info.tiling.texel_width - 1u) / info.tiling.texel_width;
|
||||
const auto height = (extent.height + info.tiling.texel_height - 1u) / info.tiling.texel_height;
|
||||
const auto& mip = info.mip_layout[level];
|
||||
uint32_t last = 0, origin = 0;
|
||||
if (!std::has_single_bit(width) || !std::has_single_bit(height) ||
|
||||
width > block.block_width || height > block.block_height ||
|
||||
mip.tail_x > block.block_width - width || mip.tail_y > block.block_height - height ||
|
||||
mip.tail_x % width != 0 || mip.tail_y % height != 0 ||
|
||||
!TileGetBlockOffset(block, width - 1u, height - 1u, 0, last) ||
|
||||
!TileGetBlockOffset(block, mip.tail_x, mip.tail_y, 0, origin)) {
|
||||
return {};
|
||||
}
|
||||
// Standard4KB interleaves monotonically ordered X/Y bits. A dense rectangle
|
||||
// fills exactly this power-of-two interval, including when rebased into a tail.
|
||||
const uint64_t size = uint64_t {last} + block.bytes_per_element;
|
||||
if (!std::has_single_bit(size) || size != uint64_t {width} * height * block.bytes_per_element ||
|
||||
(origin & (size - 1u)) != 0 || size > block.block_size - origin ||
|
||||
mip.offset > UINT64_MAX - origin) {
|
||||
return {};
|
||||
}
|
||||
return {mip.offset + origin, size};
|
||||
}
|
||||
|
||||
uint32_t CompactMipSize(const ImageInfo& info) {
|
||||
if (info.resources.levels != 1 || info.resources.layers != 1) {
|
||||
return 0;
|
||||
}
|
||||
const auto range = ContiguousMipRange(info, 0);
|
||||
if (range.size == 0 || range.address != 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto alignment = std::max<uint64_t>(256, range.size);
|
||||
const auto in_block = info.data.address & (info.tiling.block.block_size - 1u);
|
||||
return (info.data.address & (alignment - 1u)) == 0 &&
|
||||
range.size <= info.tiling.block.block_size - in_block
|
||||
? static_cast<uint32_t>(range.size)
|
||||
: 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::Extent3D ImageInfo::MipExtent(uint32_t level) const {
|
||||
@@ -34,6 +82,7 @@ void ImageInfo::UpdateSize() {
|
||||
extent.height == 0 || extent.depth == 0 || resources.levels == 0 ||
|
||||
resources.levels > mip_layout.size() || resources.layers == 0 || samples == 0 ||
|
||||
!std::has_single_bit(samples));
|
||||
const bool auto_size = data.size == 0;
|
||||
TileTextureElementLayout element {};
|
||||
EXIT_NOT_IMPLEMENTED(!TileGetTextureElementLayout(guest_format, element));
|
||||
if (bytes_per_block == 0) {
|
||||
@@ -164,9 +213,86 @@ void ImageInfo::UpdateSize() {
|
||||
linear_offset = Add(mip.linear_offset, Multiply(mip.linear_size, active.depth));
|
||||
mip_pitch = std::max(mip_pitch >> 1u, 1u);
|
||||
}
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && (auto_size || data.size == compact_size)) {
|
||||
data.size = mip_layout[0].size = compact_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageInfo::DataAlignment() const {
|
||||
if (!IsTiled()) {
|
||||
return 256;
|
||||
}
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && data.size == compact_size && mip_layout[0].size == compact_size) {
|
||||
return std::max(256u, compact_size);
|
||||
}
|
||||
return tiling.block.block_size;
|
||||
}
|
||||
|
||||
int32_t ImageInfo::SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
|
||||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
|
||||
container.resources.layers == 0 || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
|
||||
data.End() > container.data.End()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
(HasStencil() && (stencil.address < container.stencil.address ||
|
||||
stencil.End() > container.stencil.End()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u) ||
|
||||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
|
||||
mip_layout[0].height != layout.height || layout.size == 0 ||
|
||||
layout.size % container.resources.layers != 0 ||
|
||||
container.data.size % container.resources.layers != 0 ||
|
||||
data.size % resources.layers != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// PS5 block slices contain the entire reversed mip chain. A rebased tail
|
||||
// descriptor covers only its active bytes within the shared tail block.
|
||||
const auto slice_stride = container.data.size / container.resources.layers;
|
||||
const auto child_stride = data.size / resources.layers;
|
||||
auto mip_offset = layout.offset;
|
||||
auto mip_size = layout.size / container.resources.layers;
|
||||
const auto compact_size = CompactMipSize(*this);
|
||||
if (compact_size != 0 && child_stride == compact_size && mip_layout[0].size == compact_size) {
|
||||
const auto range = ContiguousMipRange(container, level);
|
||||
if (range.size != compact_size) {
|
||||
return -1;
|
||||
}
|
||||
mip_offset = range.address;
|
||||
mip_size = range.size;
|
||||
} else if (mip_layout[0].tail_x != layout.tail_x || mip_layout[0].tail_y != layout.tail_y) {
|
||||
return -1;
|
||||
}
|
||||
if (child_stride != mip_size || mip_offset >= slice_stride ||
|
||||
child_stride > slice_stride - mip_offset ||
|
||||
(resources.layers > 1 && child_stride != slice_stride)) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - container.data.address;
|
||||
if (address_delta < mip_offset || (address_delta - mip_offset) % slice_stride != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto layer = (address_delta - mip_offset) / slice_stride;
|
||||
if (layer > INT32_MAX || layer >= container.resources.layers ||
|
||||
resources.layers > container.resources.layers - layer) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(layer);
|
||||
}
|
||||
|
||||
uint64_t ImageInfo::LinearSize(uint32_t levels) const {
|
||||
levels = levels == 0 ? resources.levels : levels;
|
||||
EXIT_IF(levels == 0 || levels > resources.levels || levels > mip_layout.size());
|
||||
|
||||
@@ -88,6 +88,7 @@ struct ImageInfo {
|
||||
uint32_t first_tail_level = 16;
|
||||
|
||||
void UpdateSize();
|
||||
[[nodiscard]] uint32_t DataAlignment() const;
|
||||
[[nodiscard]] uint64_t LinearSize(uint32_t levels = 0) const;
|
||||
[[nodiscard]] vk::Extent3D MipExtent(uint32_t level) const;
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BufferCopies(uint64_t buffer_offset = 0,
|
||||
@@ -136,53 +137,7 @@ struct ImageInfo {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
|
||||
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
|
||||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
|
||||
container.resources.layers == 0 || mip < 0 ||
|
||||
static_cast<uint32_t>(mip) >= container.resources.levels ||
|
||||
container.resources.levels > container.mip_layout.size() ||
|
||||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
|
||||
data.End() > container.data.End()) {
|
||||
return -1;
|
||||
}
|
||||
if (HasStencil() != container.HasStencil() ||
|
||||
(HasStencil() && (stencil.address < container.stencil.address ||
|
||||
stencil.End() > container.stencil.End()))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto level = static_cast<uint32_t>(mip);
|
||||
const auto& layout = container.mip_layout[level];
|
||||
if (extent.width != std::max(container.extent.width >> level, 1u) ||
|
||||
extent.height != std::max(container.extent.height >> level, 1u) ||
|
||||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
|
||||
mip_layout[0].height != layout.height || layout.size == 0 ||
|
||||
layout.size % container.resources.layers != 0 ||
|
||||
container.data.size % container.resources.layers != 0 ||
|
||||
data.size % resources.layers != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// PS5 block slices contain the entire reversed mip chain.
|
||||
const auto slice_stride = container.data.size / container.resources.layers;
|
||||
const auto child_stride = data.size / resources.layers;
|
||||
if (child_stride != layout.size / container.resources.layers ||
|
||||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
|
||||
(resources.layers > 1 && child_stride != slice_stride)) {
|
||||
return -1;
|
||||
}
|
||||
const auto address_delta = data.address - container.data.address;
|
||||
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto layer = (address_delta - layout.offset) / slice_stride;
|
||||
if (layer > INT32_MAX || layer >= container.resources.layers ||
|
||||
resources.layers > container.resources.layers - layer) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int32_t>(layer);
|
||||
}
|
||||
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept;
|
||||
};
|
||||
|
||||
struct ImageViewInfo {
|
||||
|
||||
@@ -608,6 +608,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format);
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = tile;
|
||||
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth) {
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
}
|
||||
if (levels > physical_levels) {
|
||||
if (!ResolveTextureMipView(desc.info, physical_levels,
|
||||
!resource.r128 && descriptor.MetaCompress(), view_levels,
|
||||
@@ -620,8 +623,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
} else {
|
||||
desc.info.UpdateSize();
|
||||
}
|
||||
const auto alignment =
|
||||
tile == Prospero::TileMode::kLinear ? 256u : desc.info.tiling.block.block_size;
|
||||
const auto alignment = desc.info.DataAlignment();
|
||||
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
|
||||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
|
||||
if (storage) {
|
||||
@@ -632,7 +634,6 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
|
||||
desc.info.bytes_per_block, physical_levels, tile, metadata_size,
|
||||
std::countr_zero(samples));
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
|
||||
desc.info.metadata.dcc_alpha_msb = descriptor.DccAlphaPos();
|
||||
}
|
||||
|
||||
@@ -18673,6 +18673,36 @@ public:
|
||||
check_round_trip("odd multi-mip", total.size, layout);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
|
||||
constexpr auto tile = Prospero::TileMode::kStandard4KB;
|
||||
auto view = MakeTilingImage(format, 16, 16, 1, 1, tile, 0, false);
|
||||
view.data.address = 0x5032b23800;
|
||||
view.UpdateSize();
|
||||
Require(name, "rebased mip footprint",
|
||||
view.data.size == 0x800 && view.DataAlignment() == 0x800 &&
|
||||
view.mip_layout[0].pitch == 32 &&
|
||||
view.mip_layout[0].height == 16 &&
|
||||
view.tiled_slice_stride == 0x1000,
|
||||
"a compact view changed its allocation geometry");
|
||||
check_round_trip("rebased mip exact range", view.data.size, view);
|
||||
check_round_trip("rebased mip adjacent canary", 0x1000, view);
|
||||
|
||||
std::vector<uint8_t> linear(view.LinearSize());
|
||||
fill(&linear, ++case_index);
|
||||
std::vector<uint8_t> expected(0x1000, 0xab), observed;
|
||||
convert_reference(true, &expected, linear, view);
|
||||
gpu_tile(linear, &observed, 0x1000, linear.size(), view, nullptr, 0,
|
||||
0x100, 0x800);
|
||||
compare("rebased mip prefix and neighboring mip", expected, observed);
|
||||
std::vector<uint8_t> round_trip;
|
||||
gpu_detile(observed, &round_trip, observed.size(), linear.size(), view,
|
||||
nullptr, 0x800);
|
||||
std::vector<uint8_t> expected_linear(linear.size(), 0);
|
||||
convert_reference(false, &expected_linear, expected, view);
|
||||
compare("rebased mip nonzero source", expected_linear, round_trip);
|
||||
}
|
||||
|
||||
struct TailMode {
|
||||
Prospero::TileMode tile;
|
||||
TileBlockFamily family;
|
||||
|
||||
@@ -1296,6 +1296,100 @@ void TestNormalizedImageContracts() {
|
||||
"Vulkan image-view compatibility classes diverged from production");
|
||||
}
|
||||
|
||||
void TestRebasedTextureMipViews() {
|
||||
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
|
||||
constexpr auto tile = Prospero::TileMode::kStandard4KB;
|
||||
TileSizeAlign allocation{};
|
||||
TileGetTextureSize(format, 16, 16, 1, tile, &allocation, nullptr, nullptr);
|
||||
Check(allocation.size == 0x1000 && allocation.align == 0x1000,
|
||||
"rebased texture views changed guest allocation size/alignment");
|
||||
|
||||
ImageInfo chain{};
|
||||
chain.data.address = 0x5032b23000;
|
||||
chain.pixel_format = vk::Format::eR16G16B16A16Sfloat;
|
||||
chain.guest_format = format;
|
||||
chain.extent = {256, 256, 1};
|
||||
chain.resources = {9, 2};
|
||||
chain.tile_mode = tile;
|
||||
chain.UpdateSize();
|
||||
Check(chain.tiled_slice_stride == 0xab000 && chain.data.size == 0x156000 &&
|
||||
chain.first_tail_level == 4 && chain.DataAlignment() == 0x1000,
|
||||
"full texture chain lost its guest allocation layout");
|
||||
const std::array<uint64_t, 9> offsets{
|
||||
0x2b000, 0xb000, 0x3000, 0x1000, 0x800, 0x600, 0x500, 0x400, 0x300};
|
||||
const auto make_view = [&](uint32_t level, uint32_t layer) {
|
||||
auto view = chain;
|
||||
view.data = {chain.data.address + layer * chain.tiled_slice_stride +
|
||||
offsets[level], 0};
|
||||
view.extent = {256u >> level, 256u >> level, 1};
|
||||
view.resources = {1, 1};
|
||||
view.pitch = 0;
|
||||
view.UpdateSize();
|
||||
return view;
|
||||
};
|
||||
for (uint32_t layer = 0; layer < 2; ++layer) {
|
||||
for (uint32_t level = 0; level < offsets.size(); ++level) {
|
||||
auto view = make_view(level, layer);
|
||||
const uint64_t bytes = uint64_t{view.extent.width} * view.extent.height * 8;
|
||||
Check(view.data.size == bytes && view.mip_layout[0].size == bytes &&
|
||||
view.tiled_slice_stride == std::max<uint64_t>(0x1000, bytes) &&
|
||||
view.DataAlignment() == std::min<uint64_t>(0x1000,
|
||||
std::max<uint64_t>(0x100, bytes)) &&
|
||||
view.MipOf(chain) == static_cast<int32_t>(level) &&
|
||||
view.SliceOf(chain, static_cast<int32_t>(level)) ==
|
||||
static_cast<int32_t>(layer),
|
||||
"rebased mip footprint/origin or parent layer stride was incorrect");
|
||||
view.UpdateSize();
|
||||
Check(view.data.size == bytes && view.mip_layout[0].size == bytes &&
|
||||
view.MipOf(chain) == static_cast<int32_t>(level),
|
||||
"resizing a normalized view lost its compact footprint");
|
||||
}
|
||||
}
|
||||
auto view = make_view(4, 0);
|
||||
Check(view.data.address == 0x5032b23800 && view.mip_layout[0].pitch == 32 &&
|
||||
view.mip_layout[0].height == 16 &&
|
||||
view.mip_layout[0].linear_pitch == 32 && view.LinearSize() == 0x1000,
|
||||
"captured 16x16 view changed its padded tiling or linear transfer size");
|
||||
view.data.address += 0x800;
|
||||
Check(view.MipOf(chain) == -1, "tail view accepted the following mip's address");
|
||||
view = make_view(4, 0);
|
||||
view.mip_layout[0].pitch /= 2;
|
||||
Check(view.MipOf(chain) == -1, "tail view accepted a different padded pitch");
|
||||
view = make_view(4, 0);
|
||||
view.resources.layers = 2;
|
||||
view.data.size *= 2;
|
||||
view.mip_layout[0].size *= 2;
|
||||
Check(view.MipOf(chain) == -1, "tail array view ignored the parent chain stride");
|
||||
view = make_view(4, 0);
|
||||
view.data = {chain.data.address, 0x1000};
|
||||
view.UpdateSize();
|
||||
Check(view.MipOf(chain) == -1,
|
||||
"full-block child ignored the parent mip's nonzero tail origin");
|
||||
|
||||
for (uint32_t variant = 0; variant < 7; ++variant) {
|
||||
view = make_view(4, 0);
|
||||
view.data = {chain.data.address, 0};
|
||||
view.pitch = 0;
|
||||
switch (variant) {
|
||||
case 0: view.extent = {8, 16, 1}; break; // Holes in the Standard4KB span.
|
||||
case 1: view.resources.levels = 2; break;
|
||||
case 2: view.resources.layers = 2; break;
|
||||
case 3: view.type = Prospero::ImageType::kColor3D; break;
|
||||
case 4: view.tile_mode = Prospero::TileMode::kStandard64KB; break;
|
||||
case 5: view.metadata.kind = ImageMetadataKind::Dcc; break;
|
||||
case 6: view.data = {0x5032b23800, 0x1000}; break;
|
||||
}
|
||||
view.UpdateSize();
|
||||
Check(view.data.size >= 0x1000 && view.DataAlignment() >= 0x1000,
|
||||
"unsupported or explicitly sized texture became a compact tail view");
|
||||
}
|
||||
view = make_view(4, 0);
|
||||
view.data = {0x5032b23900, 0};
|
||||
view.UpdateSize();
|
||||
Check(view.data.size == 0x1000 && view.DataAlignment() == 0x1000,
|
||||
"misaligned descriptor received compact tail alignment");
|
||||
}
|
||||
|
||||
void TestShaderFeatureCollection() {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
|
||||
@@ -15725,6 +15819,7 @@ int main() {
|
||||
TestShaderBufferResourceSize();
|
||||
TestNativeShaderResourceDependencies();
|
||||
TestNormalizedImageContracts();
|
||||
TestRebasedTextureMipViews();
|
||||
TestShaderFeatureCollection();
|
||||
TestTypedSpirvSerialization();
|
||||
TestDeferredSpirvPhiPatching();
|
||||
|
||||
Reference in New Issue
Block a user