Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include <algorithm>
#include <array>
#include <optional>
#include <stdexcept>
#include <string>

Expand Down Expand Up @@ -50,6 +52,45 @@ TextureDimension resolveDimension(std::uint32_t raw) {
}
}

bool ZeroTail(std::span<const std::uint32_t> words) {
return words[4] == 0u && words[5] == 0u && words[6] == 0u && words[7] == 0u;
}

bool SameLevels(const TileMipLayout& a, const TileMipLayout& b) {
return a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.pitchBytes == b.pitchBytes && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY;
}

std::optional<std::array<std::uint32_t, 8>> R128ResidentLevelWords(std::span<const std::uint32_t> words) {
const auto baseLevel = (words[3] >> 12u) & 0xfu;
const auto lastLevel = (words[3] >> 16u) & 0xfu;
const auto typeRaw = (words[3] >> 28u) & 0xfu;
if (baseLevel == 0u || baseLevel > lastLevel || (typeRaw != 8u && typeRaw != 9u)) return std::nullopt;
const std::array<std::uint32_t, 8> chainWords{words[0], words[1], words[2], words[3], 0u, lastLevel << 4u, 0u, 0u};
const auto width = (((words[1] >> 30u) & 0x3u) | ((words[2] & 0xfffu) << 2u)) + 1u;
const auto height = ((words[2] >> 14u) & 0x3fffu) + 1u;
const auto minLod = (words[1] >> 8u) & 0xfffu;
const auto widthMinus1 = std::max(width >> baseLevel, 1u) - 1u;
const auto heightMinus1 = std::max(height >> baseLevel, 1u) - 1u;
const auto rebasedMinLod = minLod > baseLevel * 256u ? minLod - baseLevel * 256u : 0u;
const auto levels = lastLevel - baseLevel;
auto rebased = chainWords;
rebased[1] = (words[1] & ~((0xfffu << 8u) | (0x3u << 30u))) | (rebasedMinLod << 8u) | ((widthMinus1 & 0x3u) << 30u);
rebased[2] = (words[2] & ~(0xfffu | (0x3fffu << 14u))) | ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u);
rebased[3] = (words[3] & ~(0xffu << 12u)) | (levels << 16u);
rebased[5] = levels << 4u;
try {
const auto chain = DescribeSurface(DecodeTextureResource(chainWords));
const auto view = DescribeSurface(DecodeTextureResource(rebased));
if (chain.mips.size() != lastLevel + 1u || view.mips.size() != levels + 1u || chain.layers != view.layers) return std::nullopt;
for (std::uint32_t level = baseLevel; level <= lastLevel; ++level) {
if (!SameLevels(chain.mips[level], view.mips[level - baseLevel])) return std::nullopt;
}
} catch (const std::exception&) {
return std::nullopt;
}
return rebased;
}

}

bool IsNullTextureResource(std::span<const std::uint32_t> words) {
Expand Down Expand Up @@ -236,7 +277,11 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.minLod = minLod;
result.samples = samples;
if (baseLevel > maxMip) {
Require(LevelsFitAllocation(result, lastLevel + 1u), "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe());
const bool fits = LevelsFitAllocation(result, lastLevel + 1u);
if (!fits && ZeroTail(words)) {
if (const auto resident = R128ResidentLevelWords(words)) return DecodeTextureResource(*resident);
}
Require(fits, "guest texture descriptor starts past the surface's last mip level at levels that would move the surface's own" + describe());
result.mipCount = lastLevel + 1u;
}
return result;
Expand Down
49 changes: 49 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,52 @@ void rejectFields(const Fields& f, std::string_view reason) {

}

static void RunR128TextureTests() {
constexpr std::array<std::uint32_t, 8> streamed{0xa0c18800u, 0xcad00000u, 0x01ffc1ffu, 0x905b2facu, 0u, 0u, 0u, 0u};
const auto view = DecodeTextureResource(streamed);
Require(view.baseAddress == 0xa0c1880000ull && view.format == 173u && view.tileMode == TextureTileMode::kStandard4KB && view.width == 512u && view.height == 512u && view.baseLevel == 0u && view.lastLevel == 9u && view.mipCount == 10u && view.allocatedMipCount == 10u && view.minLod == 0u,
"a 128-bit BC3 T# (2048x2048, levels 2..11, words 4-7 zero) must read as the 512x512 chain of its 10 resident levels, got " + std::to_string(view.width) + "x" + std::to_string(view.height) + " levels " + std::to_string(view.baseLevel) + ".." + std::to_string(view.lastLevel) + " of " + std::to_string(view.mipCount));
auto chainWords = streamed;
chainWords[3] &= ~(0xfu << 12u);
chainWords[5] = 11u << 4u;
const auto full = DecodeTextureResource(chainWords);
Require(full.width == 2048u && full.mipCount == 12u, "the same surface with MAX_MIP 11 must be the 12-level 2048x2048 chain");
const auto chain = DescribeSurface(full);
const auto resident = DescribeSurface(view);
Require(chain.mips.size() == 12u && resident.mips.size() == 10u, "the chain and the resident levels must have 12 and 10 levels");
for (std::uint32_t level = 0; level < 10u; ++level) {
const auto& a = chain.mips[level + 2u];
const auto& b = resident.mips[level];
Require(a.tiledOffset == b.tiledOffset && a.tiledSize == b.tiledSize && a.width == b.width && a.height == b.height && a.blocksPerRow == b.blocksPerRow && a.tail == b.tail && a.tailX == b.tailX && a.tailY == b.tailY, "resident level " + std::to_string(level) + " must be level " + std::to_string(level + 2u) + " of the 12-level chain");
}
Require(resident.guestBytes <= chain.mips[1].tiledOffset && chain.mips[0].tiledOffset > chain.mips[1].tiledOffset, "the resident levels must end before the chain's levels 1 and 0, which come last in a GFX10 swizzled chain");
auto levelZero = streamed;
levelZero[3] &= ~(0xfu << 12u);
const auto single = DecodeTextureResource(levelZero);
Require(single.mipCount == 1u && single.lastLevel == 0u, "a T# with words 4-7 zero that starts at level 0 must stay one level");
auto tailed = streamed;
tailed[5] = 4u << 20u;
reject([&] { DecodeTextureResource(tailed); }, "would move the surface's own");

constexpr std::array<std::uint32_t, 8> tall{0xa0c1de00u, 0xcad00000u, 0x01ffc0ffu, 0x905b4facu, 0u, 0u, 0u, 0u};
const auto tallView = DecodeTextureResource(tall);
Require(tallView.width == 64u && tallView.height == 128u && tallView.lastLevel == 7u && tallView.mipCount == 8u, "a 128-bit 1024x2048 T# of levels 4..11 must read as its 64x128 chain of 8 levels");

Fields clamped;
clamped.base40 = 0x55ea000ull;
clamped.format = 20;
clamped.width = 1024;
clamped.height = 1024;
clamped.tileModeRaw = 0x1b;
clamped.baseLevel = 2;
clamped.lastLevel = 10;
clamped.minLod = 2u * 256u + 64u;
const auto clampedView = DecodeTextureResource(pack(clamped));
Require(clampedView.width == 256u && clampedView.mipCount == 9u && clampedView.minLod == 64u, "reading a 128-bit T# from its resident levels must move its MIN_LOD clamp with the base level");
clamped.minLod = 256u;
Require(DecodeTextureResource(pack(clamped)).minLod == 0u, "a MIN_LOD clamp below the base level must vanish when read from the resident levels");
}

void RunGuestTextureResourceTests() {
Fields base;
const auto baseWords = pack(base);
Expand Down Expand Up @@ -620,13 +666,16 @@ void RunGuestTextureResourceTests() {
pastUntailed.maxMip = 0;
pastUntailed.baseLevel = 1;
pastUntailed.lastLevel = 1;
pastUntailed.perfMod = 4;
rejectFields(pastUntailed, "would move the surface's own");
auto untailedChain = tailView;
untailedChain.mipCount = 1;
const auto untailedAllocated = DescribeSurface(untailedChain).guestBytes;
untailedChain.mipCount = 2;
Require(untailedAllocated == 0xff0000u && DescribeSurface(untailedChain).guestBytes == 0x1470000u, "a second level must grow a 1920x1080 64 bpp SW_64KB_R_X surface from addrlib's 0xff0000 to 0x1470000 bytes");

RunR128TextureTests();

std::array<std::uint32_t, 4> shortWords{};
reject([&] { DecodeTextureResource(shortWords); }, "8 dwords");

Expand Down
Loading