fix(agc): give each slice of a depth array its own HTILE

A slice of a depth array target (DB_DEPTH_VIEW SLICE_START) offset its
depth and stencil addresses but kept the array's DB_HTILE_DATA_BASE as
its HTILE. Every slice then shared slice 0's metadata: a fast clear of
slice 0's HTILE cleared every slice, and a clear of any other slice's
HTILE matched none.

A slice's HTILE now starts at the base plus the slice times one slice's
pipe-aligned metadata, as addrlib addresses it (sliceSize * slice in
Gfx10Lib::HwlComputeHtileAddrFromCoord; HtileMetadataBytes is that slice
size). The recognized HTILE clear kernel now reports the bytes it
clears, 16 per thread (global_store_dwordx4 at the thread index << 4),
and a surface takes the clear when that range covers its HTILE, as a
DMA_DATA fill does; a clear over a whole array still clears every slice.

Uncharted: Legacy of Thieves Collection (PPSA05684) renders its sun
shadow cascades into the four slices of a 2048x2048 D16 array; each
slice's HTILE lies 256 KiB after the previous one.
This commit is contained in:
XairRrR authored and Adrià Franch committed 2026-10-14 19:45:03 +02:00
1 parent 8ba869d303
commit 8fec47a4b2
6 files changed
+57 -10

No files matched your search

@@ -75,7 +75,10 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
if (!partialLastGroup && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) return;
if (!partialLastGroup && indirectArguments == 0 && matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) return;
} else {
if (const auto htile = Graphics::HtileDepthClearAddress(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads); htile != 0) Graphics::NoteHtileDepthClear(htile);
if (const auto htile = Graphics::HtileDepthClearAddress(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads); htile != 0) {
const auto threads = (packet[4] & 0x20u) != 0 ? static_cast<std::uint64_t>(packet[1]) : static_cast<std::uint64_t>(packet[1]) * compute.numThreads[0];
Graphics::NoteHtileDepthClear(htile, static_cast<std::size_t>(threads * 16u));
}
if (indirectArguments == 0 && (packet[4] & 0x20u) == 0) {
if (const auto fill = Graphics::MatchMemoryValueFill(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads, {packet[1], packet[2], packet[3]})) Graphics::NoteMemoryValueFill(*fill);
}
@@ -22,7 +22,7 @@ void ClearDepthSurfaces(VkDevice device);
bool DepthSurfaceAt(std::uint64_t address);
bool DepthSurfaceAt(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource);
std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const std::array<std::uint32_t, 3>& numThreads);
void NoteHtileDepthClear(std::uint64_t htileAddress);
void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
struct MemoryValueFill {
std::uint64_t address;
std::size_t bytes;
@@ -617,11 +617,20 @@ DepthSurface* planeSurface(VkDevice device, std::uint64_t address) {
return found == list.rend() ? nullptr : found->get();
}
std::set<std::uint64_t>& clearedHtiles() {
static auto* cleared = new std::set<std::uint64_t>();
std::map<std::uint64_t, std::size_t>& clearedHtiles() {
static auto* cleared = new std::map<std::uint64_t, std::size_t>();
return *cleared;
}
bool takeClearedHtile(const DepthTarget& target) {
if (target.htileAddress == 0) return false;
auto& cleared = clearedHtiles();
const auto found = std::find_if(cleared.begin(), cleared.end(), [&](const auto& entry) { return HtileFillCovers(target.htileAddress, target.extent, entry.first, entry.second); });
if (found == cleared.end()) return false;
cleared.erase(found);
return true;
}
const DepthSurface* drawnDepth = nullptr;
std::atomic<std::uint64_t> surfacesSerial{0};
@@ -678,7 +687,7 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) {
std::lock_guard lock(surfacesMutex());
const bool cleared = target.htileAddress != 0 && clearedHtiles().erase(target.htileAddress) != 0;
const bool cleared = takeClearedHtile(target);
DepthSurface* surface = nullptr;
for (const auto& candidate : surfaces()) {
if (candidate->context.device == context.device && sameSurface(candidate->target, target)) {
@@ -736,15 +745,15 @@ void NoteMemoryValueFill(const MemoryValueFill& fill) {
NoteDepthMetadataFill(fill.address, fill.bytes, value);
}
void NoteHtileDepthClear(std::uint64_t htileAddress) {
void NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
std::lock_guard lock(surfacesMutex());
bool existing = false;
for (const auto& surface : surfaces()) {
if (surface->target.htileAddress != htileAddress) continue;
if (!HtileFillCovers(surface->target.htileAddress, surface->target.extent, htileAddress, bytes)) continue;
surface->pendingClear |= VK_IMAGE_ASPECT_DEPTH_BIT;
existing = true;
}
if (!existing) clearedHtiles().insert(htileAddress);
if (!existing) clearedHtiles()[htileAddress] = bytes;
}
void ClearDepthSurfaces(VkDevice device) {
@@ -292,7 +292,7 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, bool testValEx
depth.clearDepth = readFloat(cx, 0x00b);
depth.clearStencil = static_cast<std::uint8_t>(read(cx, 0x00a) & 0xffu);
if (const auto htile = find(cx, 0x005); zFormat != 0 && htile != cx.end() && htile->second != 0 && (read(cx, 0x010) & (1u << 29u)) != 0) {
depth.htileAddress = base(0x005, 0x01e);
depth.htileAddress = base(0x005, 0x01e) + static_cast<std::uint64_t>(view & 0x1fffu) * HtileMetadataBytes(depth.extent);
depth.htileStencil = stencil && (read(cx, 0x011) & (1u << 29u)) == 0;
}
result.depth = depth;
@@ -39,11 +39,14 @@ THandle newHandle() {
VkFormat lastImageFormat = VK_FORMAT_UNDEFINED;
std::vector<VkImage> createdImages;
std::vector<std::tuple<VkImage, VkImage, VkImageAspectFlags>> imageCopies;
VkImage lastImage = VK_NULL_HANDLE;
std::vector<VkImage> clearedImages;
VKAPI_ATTR VkResult VKAPI_CALL createImage(VkDevice, const VkImageCreateInfo* info, const VkAllocationCallbacks*, VkImage* image) {
lastImageFormat = info->format;
*image = newHandle<VkImage>();
createdImages.push_back(*image);
lastImage = *image;
return VK_SUCCESS;
}
@@ -101,7 +104,9 @@ VKAPI_ATTR void VKAPI_CALL destroyImageView(VkDevice, VkImageView, const VkAlloc
VKAPI_ATTR void VKAPI_CALL destroyImage(VkDevice, VkImage, const VkAllocationCallbacks*) {}
VKAPI_ATTR void VKAPI_CALL freeMemory(VkDevice, VkDeviceMemory, const VkAllocationCallbacks*) {}
VKAPI_ATTR void VKAPI_CALL cmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags, VkPipelineStageFlags, VkDependencyFlags, std::uint32_t, const VkMemoryBarrier*, std::uint32_t, const VkBufferMemoryBarrier*, std::uint32_t, const VkImageMemoryBarrier*) {}
VKAPI_ATTR void VKAPI_CALL cmdClearDepthStencilImage(VkCommandBuffer, VkImage, VkImageLayout, const VkClearDepthStencilValue*, std::uint32_t, const VkImageSubresourceRange*) {}
VKAPI_ATTR void VKAPI_CALL cmdClearDepthStencilImage(VkCommandBuffer, VkImage image, VkImageLayout, const VkClearDepthStencilValue*, std::uint32_t, const VkImageSubresourceRange*) {
clearedImages.push_back(image);
}
VKAPI_ATTR void VKAPI_CALL cmdCopyImage(VkCommandBuffer, VkImage source, VkImageLayout, VkImage destination, VkImageLayout, std::uint32_t count, const VkImageCopy* regions) {
for (std::uint32_t i = 0; i < count; ++i) imageCopies.emplace_back(source, destination, regions[i].dstSubresource.aspectMask);
}
@@ -307,6 +312,29 @@ void RunDepthSurfaceReuseTests() {
imageCopies.clear();
const auto otherWithA = pairing(otherDepth, stencilA);
Require(imageCopies.size() == 1 && imageCopies[0] == std::make_tuple(withA, otherWithA, static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_STENCIL_BIT)), "a stencil plane paired with another depth plane must carry its stencil over");
{
constexpr std::uint64_t arrayBase = 0x80000000;
constexpr std::uint64_t htile = 0x81000000;
constexpr VkExtent2D extent{1024, 512};
const auto sliceHtile = HtileMetadataBytes(extent);
DepthTarget first{arrayBase, 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
first.htileAddress = htile;
DepthTarget second{arrayBase + DepthSliceBytes(extent, 4u), 0, extent, VK_FORMAT_D32_SFLOAT, 1.0f, 0};
second.htileAddress = htile + sliceHtile;
DepthSurfaceView(context, first);
const auto firstImage = lastImage;
DepthSurfaceView(context, second);
const auto secondImage = lastImage;
const auto clearsAfter = [&](std::size_t bytes) {
clearedImages.clear();
NoteHtileDepthClear(htile, static_cast<std::size_t>(bytes));
DepthSurfaceView(context, first);
DepthSurfaceView(context, second);
return clearedImages;
};
Require(clearsAfter(sliceHtile) == std::vector<VkImage>{firstImage}, "an HTILE clear of the first slice must clear that slice alone");
Require(clearsAfter(2u * sliceHtile) == std::vector<VkImage>{firstImage, secondImage}, "an HTILE clear over both slices must clear both");
}
ClearDepthSurfaces(context.device);
}
@@ -1484,6 +1484,13 @@ void DepthStencilTests() {
AgcDriver::Graphics::RegisterReadLog() = nullptr;
for (const auto read : log) Require(AgcDriver::Graphics::DrawKeyCovers(read), "DrawKeyRegisters lacks an HTILE register the decoder reads: " + std::to_string(read.offset));
Require(state.depth->htileAddress == (0x30000ull | (1ull << 40u)), "the HTILE address was not decoded from DB_HTILE_DATA_BASE and its high byte");
{
const auto view = queue.context[0x002];
queue.context[0x002] = 2u | (2u << 13u);
const auto slice = AgcDriver::Graphics::DecodeState(queue);
Require(slice.depth->htileAddress == (0x30000ull | (1ull << 40u)) + 2u * AgcDriver::Graphics::HtileMetadataBytes(slice.depth->extent), "slice 2 of a depth array must use the third slice of its HTILE");
queue.context[0x002] = view;
}
queue.context[0x010] &= ~0x20000000u;
state = AgcDriver::Graphics::DecodeState(queue);
Require(state.depth->htileAddress == 0, "a surface without TILE_SURFACE_ENABLE decoded an HTILE address");