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 @@ -61,6 +61,7 @@ struct GuestTextureResource {
std::uint64_t dccAddress = 0;
bool dccAlphaOnMsb = false;
std::uint32_t minLod = 0;
std::uint32_t allocatedMipCount = 0;
};

float EffectiveMinLod(const GuestTextureResource& resource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct SurfaceGeometry {
std::int32_t CopyDepth(std::uint32_t layer) const { return imageDepth > 1 ? static_cast<std::int32_t>(layer) : 0; }
};
SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor);
bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels);

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <cstdio>
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include <algorithm>
#include <stdexcept>
#include <string>
Expand Down Expand Up @@ -129,10 +130,9 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
} catch (const std::exception& error) {
throw std::runtime_error(std::string(error.what()) + describe());
}
Require(baseLevel <= maxMip, "guest texture descriptor starts past the surface's last mip level" + describe());
// Views may name levels past MAX_MIP (a 512x512 view through 1x1 over a 9-level surface); the
// hardware never addresses them, so the view ends at the surface's last level.
lastLevel = std::min(lastLevel, maxMip);
if (baseLevel <= maxMip) lastLevel = std::min(lastLevel, maxMip);
// XOR swizzles fold a pipe/bank XOR into the low address bits; only unmodified 64 KiB bases are modeled.
Require(XorSwizzleMode(tileMode) == 0 || (baseAddress & 0xffffu) == 0, "guest texture descriptor combines an XOR swizzle with a pipe/bank XOR base which is not implemented");

Expand Down Expand Up @@ -164,6 +164,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.depthOrLastArray = depth;
result.baseArray = baseArray;
result.mipCount = maxMip + 1u;
result.allocatedMipCount = maxMip + 1u;
result.baseLevel = baseLevel;
result.lastLevel = lastLevel;
result.tileMode = tileMode;
Expand All @@ -176,6 +177,10 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.dccAddress = metaCompress ? metaAddr << 8u : 0u;
result.dccAlphaOnMsb = dccAlphaPos;
result.minLod = minLod;
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());
result.mipCount = lastLevel + 1u;
}
return result;
}

Expand Down
50 changes: 46 additions & 4 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,22 +511,64 @@ std::array<std::uint32_t, 8> SurfaceKey(const Context& context, const GuestTextu
return {static_cast<std::uint32_t>(resource.baseAddress), static_cast<std::uint32_t>(resource.baseAddress >> 32u), resource.width, resource.height, (resource.depthOrLastArray << 16u) | (resource.mipCount & 0xffffu), (static_cast<std::uint32_t>(resource.tileMode) << 12u) | (static_cast<std::uint32_t>(resource.dimension) << 20u), resource.baseArray, static_cast<std::uint32_t>(StorageFormatForGuest(context, resource.format))};
}

struct ExtendedSurfaces {
std::mutex mutex;
std::map<std::array<std::uint32_t, 8>, std::uint32_t> levels;
std::atomic<bool> any{false};
};

ExtendedSurfaces& ExtendedChains() {
static ExtendedSurfaces surfaces;
return surfaces;
}

std::uint32_t AllocatedLevels(const GuestTextureResource& resource) {
return resource.allocatedMipCount != 0 ? resource.allocatedMipCount : resource.mipCount;
}

GuestTextureResource StorageSurface(const Context& context, const GuestTextureResource& viewed) {
auto& surfaces = ExtendedChains();
const bool extended = viewed.mipCount > AllocatedLevels(viewed);
if (!extended && !surfaces.any.load(std::memory_order_acquire)) return viewed;
auto surface = viewed;
surface.mipCount = AllocatedLevels(viewed);
const auto identity = SurfaceKey(context, surface);
std::lock_guard lock(surfaces.mutex);
if (extended) {
auto& levels = surfaces.levels[identity];
levels = std::max(levels, viewed.mipCount);
surfaces.any.store(true, std::memory_order_release);
surface.mipCount = levels;
} else if (const auto it = surfaces.levels.find(identity); it != surfaces.levels.end()) {
surface.mipCount = it->second;
} else {
surface.mipCount = viewed.mipCount;
}
return surface;
}

// `guestBytes` is the surface size when the caller described the surface already (0: described here).
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes) {
if (DepthSurfaceAt(resource.baseAddress)) {
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes) {
if (DepthSurfaceAt(viewed.baseAddress)) {
char text[112];
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast<unsigned long long>(resource.baseAddress));
std::snprintf(text, sizeof(text), "AGC graphics: storage image access to depth/stencil surface 0x%llx is not implemented", static_cast<unsigned long long>(viewed.baseAddress));
throw std::runtime_error(text);
}
static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr;
if (disabled) return std::make_shared<StorageTexture>(context, *context.detiler, resource, mip);
if (disabled) return std::make_shared<StorageTexture>(context, *context.detiler, viewed, mip);
static_cast<void>(words);
const bool profile = LookupOutcomes::Profiled();
auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
auto& counters = TextureCounts();
const auto resource = StorageSurface(context, viewed);
const StorageKey key{context.device, SurfaceKey(context, resource)};
auto& cache = StorageTextures();
std::lock_guard lock(cache.mutex);
if (resource.mipCount > AllocatedLevels(resource)) {
auto allocated = resource;
allocated.mipCount = AllocatedLevels(resource);
if (const auto found = findStorage(cache, {context.device, SurfaceKey(context, allocated)}); found != cache.entries.end()) evictStorage(cache, found);
}
auto it = findStorage(cache, key);
if (it != cache.entries.end() && MetadataMoved(*it->texture, resource)) {
evictStorage(cache, it);
Expand Down
19 changes: 19 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,25 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) {
return geometry;
}

bool LevelsFitAllocation(const GuestTextureResource& surface, std::uint32_t levels) {
Require(levels >= surface.mipCount && levels <= 16u, "a view's mip chain must cover the surface's levels and at most 16");
auto view = surface;
view.mipCount = levels;
const auto allocated = DescribeSurface(surface);
const auto extended = DescribeSurface(view);
if (allocated.guestBytes != extended.guestBytes || allocated.layerBytes != extended.layerBytes || allocated.layers != extended.layers) return false;
for (std::uint32_t level = 0; level < surface.mipCount; ++level) {
const auto& before = allocated.mips[level];
const auto& after = extended.mips[level];
if (before.tiledOffset != after.tiledOffset || before.tiledSize != after.tiledSize || before.blocksPerRow != after.blocksPerRow || before.tail != after.tail || before.tailX != after.tailX || before.tailY != after.tailY) return false;
}
for (auto level = surface.mipCount; level < levels; ++level) {
const auto& mip = extended.mips[level];
if (mip.tiledOffset + mip.tiledSize > extended.layerBytes) return false;
}
return true;
}

std::vector<TileMipLayout> ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain");
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount);
Expand Down
52 changes: 52 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "GraphicsTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include <array>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -269,6 +270,57 @@ void RunGuestTextureResourceTests() {
const auto partial = DecodeTextureResource(pack(partialMips));
Require(partial.lastLevel == 1 && partial.mipCount == 3, "a view over part of the mip chain decoded wrongly");

Fields pastLast = base;
pastLast.base40 = 0x55ea000ull;
pastLast.format = 71;
pastLast.width = 1920;
pastLast.height = 1080;
pastLast.tileModeRaw = 0x1b;
pastLast.maxMip = 5;
pastLast.baseLevel = 6;
pastLast.lastLevel = 6;
const auto tailView = DecodeTextureResource(pack(pastLast));
Require(tailView.baseLevel == 6 && tailView.lastLevel == 6 && tailView.mipCount == 7, "a view one level past the last mip must address that level of the chain");
auto allocated = tailView;
allocated.mipCount = 6;
const auto allocatedSurface = DescribeSurface(allocated);
const auto viewSurface = DescribeSurface(tailView);
Require(allocatedSurface.guestBytes == 0x1640000u && viewSurface.guestBytes == 0x1640000u, "a 1920x1080 64 bpp SW_64KB_R_X chain must take addrlib's 0x1640000 bytes with 6 and with 7 levels");
constexpr std::array<std::uint64_t, 7> addrlibOffsets{0x650000u, 0x1d0000u, 0x90000u, 0x30000u, 0x10000u, 0u, 0u};
for (std::uint32_t level = 0; level < 7; ++level) {
Require(viewSurface.mips[level].tiledOffset == addrlibOffsets[level] && viewSurface.mips[level].tail == (level >= 5), "the 7-level chain must place each level at its addrlib offset");
if (level < 6) Require(allocatedSurface.mips[level].tiledOffset == addrlibOffsets[level] && allocatedSurface.mips[level].tail == viewSurface.mips[level].tail && allocatedSurface.mips[level].tailX == viewSurface.mips[level].tailX && allocatedSurface.mips[level].tailY == viewSurface.mips[level].tailY, "the level past the last mip must not move the allocated levels");
}
Require(viewSurface.mips[5].tailX == 64 && viewSurface.mips[5].tailY == 0, "the last allocated level must sit in its addrlib tail slot");
Require(viewSurface.mips[6].tailX == 0 && viewSurface.mips[6].tailY == 32, "the level past the last mip must sit in its addrlib tail slot");
pastLast.lastLevel = 8;
const auto deeperView = DecodeTextureResource(pack(pastLast));
Require(deeperView.mipCount == 9, "a view past the last mip must cover every level it names");
const auto deeperSurface = DescribeSurface(deeperView);
Require(deeperSurface.guestBytes == 0x1640000u && deeperSurface.mips[7].tail && deeperSurface.mips[7].tailX == 32 && deeperSurface.mips[7].tailY == 0 && deeperSurface.mips[8].tail && deeperSurface.mips[8].tailX == 0 && deeperSurface.mips[8].tailY == 16, "levels 7 and 8 must sit in their addrlib tail slots");

Fields pastLinear = base;
pastLinear.maxMip = 1;
pastLinear.baseLevel = 2;
pastLinear.lastLevel = 2;
rejectFields(pastLinear, "would move the surface's own");
auto linearChain = partial;
linearChain.mipCount = 2;
const auto linearAllocated = DescribeSurface(linearChain).guestBytes;
linearChain.mipCount = 3;
Require(linearAllocated == 0x1800u && DescribeSurface(linearChain).guestBytes == 0x1c00u, "a third level must grow a 16x16 32 bpp linear chain from addrlib's 0x1800 to 0x1c00 bytes");

Fields pastUntailed = pastLast;
pastUntailed.maxMip = 0;
pastUntailed.baseLevel = 1;
pastUntailed.lastLevel = 1;
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");

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

Expand Down
86 changes: 86 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,91 @@ void movedMetadataTests(const Device& device, Recorder& recorder) {
Require(holds(*back, {0x55, 0x55, 0x55, 0x55}), "the image remade under the first keys does not hold the stored texels");
}

void viewPastLastMipTests(const Device& device, Recorder& recorder) {
const auto& base = device.GetContext();
if (base.hostImportAlignment == 0) {
std::cout << "host imports unavailable: views past the last mip not tested\n";
return;
}
constexpr std::uint32_t side = 256;
constexpr std::size_t bytes = 0x60000;
#ifdef _WIN32
void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
void* block = std::aligned_alloc(65536, bytes);
#endif
Require(block != nullptr, "cannot allocate the past-last-mip block");
auto* texels = static_cast<std::uint8_t*>(block);
const auto address = reinterpret_cast<std::uint64_t>(block);
{
GuestAllocations::Mutation mutation;
mutation.Add(block, bytes, true, true);
}
struct Unregister {
const Context& context;
void* block;
std::uint64_t address;
~Unregister() {
ClearCachedTextures(context.device);
{
GuestAllocations::Mutation mutation;
mutation.Remove(block);
}
HostImportFor(context, address, bytes);
}
} unregister{base, block, address};
if (HostImportFor(base, address, bytes) == nullptr) {
std::cout << "host import of the past-last-mip block refused: views past the last mip not tested\n";
return;
}
TextureDetiler detiler(base);
auto context = base;
context.detiler = &detiler;
const auto view = [&](std::uint32_t level) {
const std::array<std::uint32_t, 8> words{
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (56u << 20u) | (((side - 1u) & 3u) << 30u),
((side - 1u) >> 2u) | ((side - 1u) << 14u),
0xfacu | (level << 12u) | (level << 16u) | (0x1bu << 20u) | (9u << 28u),
0u,
2u << 4u,
0u,
0u,
};
return DecodeTextureResource(words);
};
const auto texel = [&](const StorageTexture& image, std::uint32_t level) {
Buffer readback(context, 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
const auto commands = recorder.Commands();
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
VkBufferImageCopy copy{};
copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 1};
copy.imageExtent = {1, 1, 1};
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, image.Image(), VK_IMAGE_LAYOUT_GENERAL, readback.Handle(), 1, &copy);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT);
recorder.Submit();
device.WaitQueue();
recorder.Sync();
std::uint32_t value = 0;
std::memcpy(&value, readback.Bytes().data(), sizeof(value));
return value;
};
std::memset(texels, 0x55, bytes);
std::memset(texels + 0x8400, 0x66, 4);
std::memset(texels + 0x4800, 0x77, 4);
const auto allocatedView = view(2);
const auto pastView = view(3);
Require(allocatedView.mipCount == 3 && pastView.mipCount == 4 && DescribeSurface(pastView).guestBytes == bytes, "a 256x256 32 bpp SW_64KB_R_X surface viewed past MAX_MIP 2 decoded wrongly");
const auto allocated = CachedStorageSurface(context, allocatedView);
Require(allocated->Descriptor().mipCount == 3, "the allocated levels' storage image has an unexpected level count");
const auto past = CachedStorageSurface(context, pastView);
Require(past->Descriptor().mipCount == 4, "the storage image of a view past the last mip lacks the level it names");
Require(!StorageImageCached(context, allocated.get()), "the allocated levels' image is still the surface's after a view past its last mip");
Require(CachedStorageSurface(context, allocatedView) == past, "a view of the allocated levels took another image than the view past the last mip");
Require(texel(*past, 2) == 0x66666666u, "level 2 was not read from its addrlib tail slot");
Require(texel(*past, 3) == 0x77777777u, "the level past the last mip was not read from its addrlib tail slot");
}

void resourceReadTests(const Device& device, Recorder& recorder) {
const auto& context = device.GetContext();
if (context.hostImportAlignment == 0) {
Expand Down Expand Up @@ -2455,6 +2540,7 @@ int main() {
RunResidentPresentTests(device.GetContext());
storeRunTests(device, recorder);
movedMetadataTests(device, recorder);
viewPastLastMipTests(device, recorder);
keysFillTests(device, recorder);
unitShadowTests(device, recorder);
storageRefreshTests(device, recorder, false);
Expand Down
Loading
Loading