Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f6d8d9c
feat(agc): render into mipmapped 3D color targets
Amoenus Oct 17, 2026
b43b0c0
test(agc): cover non-mipmapped depth-one volume draws
Amoenus Oct 18, 2026
9b1d53c
fix(agc): preserve depth-one volume target paths
Amoenus Oct 18, 2026
028ec46
merge: refresh volume mip targets with layered array support
Amoenus Oct 19, 2026
d8118da
chore: refresh volume mip targets on current main
Amoenus Oct 21, 2026
2550e6d
chore(agc): refresh PR 7310 on current main
Amoenus Oct 22, 2026
f1742ef
chore: merge current main into volume color mip fix
Amoenus Oct 23, 2026
13f3029
chore: refresh volume color mip support on mainbfb080
Amoenus Oct 24, 2026
d3553c6
Merge main into codex/feat-volume-color-mips-mainaf
oneandonlydean Oct 25, 2026
2ec7f75
fix(agc): keep Linear volumes out of the resident surface lookup when…
oneandonlydean Oct 25, 2026
b49ce04
fix(agc): add the Windows libc PATH to the linear volume non-resident…
oneandonlydean Oct 25, 2026
60cb1fc
Merge commit 'e05ba6feebd000cfc316c1d1f00e37e030aa3d8b' into codex/pr…
Amoenus Oct 25, 2026
52e7343
test(agc): place volume mip vertices after the backing surface
Amoenus Oct 25, 2026
47c23b7
fix(agc): bound volume target checks to the backing surface
Amoenus Oct 25, 2026
e9e4bc0
test(agc): preserve volume buffer alias bounds on both sides
Amoenus Oct 25, 2026
31f4949
Merge remote-tracking branch 'origin/main' into codex/fix-volume-boun…
Amoenus Oct 25, 2026
b398f15
test(agc): cover volume metadata write intervals
Amoenus Oct 25, 2026
10716fa
fix(agc): bound volume metadata pipeline writes to backing surface
Amoenus Oct 25, 2026
a287581
chore: refresh volume mip targets on main acb900c194
Amoenus Oct 26, 2026
7139867
merge: retain volume allocation bounds in shared buffer sizing
Amoenus Oct 27, 2026
c65d6fa
test(agc): cover strided buffers inside a volume allocation
Amoenus Oct 27, 2026
d82f817
Merge commit '13926067ac6ef3290a74f79f397123d69fc76fb1' into codex/re…
Amoenus Oct 27, 2026
9a3ae67
Merge current main into mipmapped volume color targets
Amoenus Oct 27, 2026
d917278
chore: merge current main into PR7310
Amoenus Oct 28, 2026
2c1f594
merge: refresh volume mip tests after registration split
Amoenus Oct 31, 2026
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
5 changes: 5 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,18 @@ struct ColorTarget {
std::uint32_t layers = 1;
std::uint32_t exportIndex = 0;
bool uintExport = false;
bool volume = false;
std::uint32_t pipeBankXor = 0;
std::uint32_t samples = 1;
std::uint32_t coverageSamples = 1;
std::uint32_t dccControl = 0;
std::uint64_t fmaskAddress = 0;
std::uint32_t fmaskPipeBankXor = 0;
std::uint16_t partialWriteMask = 0;

std::uint64_t MemoryAddress() const {
return volume && tileMode != ColorTileMode::Linear ? surfaceAddress : address;
}
};

struct DepthTarget {
Expand Down
19 changes: 10 additions & 9 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,7 @@ ResourceCache::Key DrawResourceKey(const Context& context, std::span<const Compi
// would have failed its build fails its hit.
void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarget& target, std::uint64_t indexAddress, std::uint64_t indexBytes) {
const auto overlap = [](std::uint64_t first, std::uint64_t firstSize, std::uint64_t second, std::uint64_t secondSize) { return first < second + secondSize && second < first + firstSize; };
const auto targetAddress = target.MemoryAddress();
for (const auto& shader : shaders) {
for (const auto& binding : shader.program->bindings) {
if (binding.role != ShaderRecompiler::DescriptorRole::GuestBuffers) continue;
Expand All @@ -1179,7 +1180,7 @@ void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarg
const auto element = offset / 4;
const bool written = element >= binding.bufferWritten.size() || binding.bufferWritten[element];
if (!written) size = ShaderResources::BytesUpToTarget(address, size, descriptor.Stride(), &target);
Require(!overlap(address, size, target.address, target.bytes), "shader buffer aliases the render target");
Require(!overlap(address, size, targetAddress, target.bytes), "shader buffer aliases the render target");
Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
}
}
Expand Down Expand Up @@ -1488,7 +1489,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
Require(indexBytes <= std::numeric_limits<std::size_t>::max(), "index buffer size overflow");
if (draw.indexed) GuestMemory::CheckRange(reinterpret_cast<const void*>(draw.indexAddress), static_cast<std::size_t>(indexBytes), draw.indexSize);
APS5_LOG_CHARS_OUT_DEBUG("Index buffer range OK");
Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.MemoryAddress() || state.color.MemoryAddress() + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
if (draw.indexed) RequireNoMultisampleTarget(draw.indexAddress, static_cast<std::size_t>(indexBytes), "an index buffer read");
if (state.pixelKill && !state.pixelEarlyZ && std::ranges::any_of(state.colors, [](const ColorTarget& color) { return color.partialWriteMask != 0; })) {
const auto* recorder = Recorder::Active();
Expand Down Expand Up @@ -1738,7 +1739,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
const auto& fields = attribute.resource.fields;
const auto address = fields[0] | (static_cast<std::uint64_t>(fields[1] & 0xffffu) << 32u);
const auto firstByte = args == nullptr ? VertexBufferReadOffset(attribute, draw.firstVertex, draw.firstInstance) : 0;
Require(!state.hasColorTarget || address + bytes <= state.color.address || state.color.address + state.color.bytes <= address + firstByte, "vertex buffer aliases the render target");
Require(!state.hasColorTarget || address + bytes <= state.color.MemoryAddress() || state.color.MemoryAddress() + state.color.bytes <= address + firstByte, "vertex buffer aliases the render target");
const auto stride = (fields[1] >> 16u) & 0x3fffu;
fetchOf[i] = fetches.size();
fetches.push_back({address, address + bytes, stride, attribute.fetchIndex, DecodeVertexFormat(attribute).alignment});
Expand Down Expand Up @@ -2983,7 +2984,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
}
if (resident == nullptr) resident = CachedStorageSurface(context, surface);
Require(resident->Attachable(), "storage format cannot be a color attachment");
Require(color.mipCount > 1 || color.depth > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout");
Require(color.volume || color.mipCount > 1 || color.depth > 1 || resident->GuestBytes() == colorLayout.Bytes(), "resident image layout differs from the color layout");
return resident;
});
}
Expand All @@ -2995,7 +2996,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
}
materializeCmaskClear(context, color, nullptr);
Require(!color.mipTail || binding.gpuTiling, "rendering into a packed mip tail requires the GPU tiled fallback");
Require(color.depth == 1, "rendering into a 3D color target needs the resident image of its surface");
Require(color.depth == 1 && (!color.volume || (color.mipCount == 1 && color.tileMode == ColorTileMode::Linear)), "rendering into a 3D color target needs the resident image of its surface");
Require(color.layers == 1, "layered rendering into a 2D array color target needs the resident image of its array");
Require(color.arraySlice == 0 || !ColorTileModeIsXor(color.tileMode), "rendering into a slice of a 2D array color target with an XOR tile mode needs the resident image of its array");
if (binding.gpuTiling) {
Expand Down Expand Up @@ -3065,7 +3066,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
};
for (const auto& target : targets) {
const bool imageRead = target.resident != nullptr && std::ranges::any_of(readImages, [&](const auto& image) { return image.first == target.resident->Image(); });
Require(!imageRead && !resources->ReadsImage(target.resident.get()) && !resources->ReadsOverlap(target.color.address, static_cast<std::size_t>(target.color.bytes)) && !inputOverlaps(target.color.address, target.color.bytes), "stencil replay refuses attachment feedback reads");
Require(!imageRead && !resources->ReadsImage(target.resident.get()) && !resources->ReadsOverlap(target.color.MemoryAddress(), static_cast<std::size_t>(target.color.bytes)) && !inputOverlaps(target.color.MemoryAddress(), target.color.bytes), "stencil replay refuses attachment feedback reads");
}
if (state.depth) {
const auto bytes = static_cast<std::size_t>(state.depth->extent.width) * state.depth->extent.height * 4u;
Expand Down Expand Up @@ -3613,7 +3614,7 @@ void recordPreparedDraw(const Context& context, const State& state, const Pm4::D
if (checkRecords) checkRecords();
timer.phase(PhaseSync);
APS5_LOG_CHARS_OUT_DEBUG("SubmitAndWait OK");
for (const auto& binding : targets) GuestMemory::CheckRange(reinterpret_cast<const void*>(binding.color.address), binding.color.bytes, 256, true);
for (const auto& binding : targets) GuestMemory::CheckRange(reinterpret_cast<const void*>(binding.color.MemoryAddress()), binding.color.bytes, 256, true);
for (auto& binding : targets) {
if (!binding.dump) continue;
std::lock_guard lock(dumpMutex);
Expand Down Expand Up @@ -3877,14 +3878,14 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> PipelinedColorMetadataPassW
if (!pass.source.has_value() || pass.targets.size() != 1) return writes;
const auto& destination = pass.targets.front();
if (destination.samples != 1u || destination.bytes == 0 || destination.cmaskAddress != 0) return writes;
writes.emplace_back(destination.address, destination.address + destination.bytes);
writes.emplace_back(destination.MemoryAddress(), destination.MemoryAddress() + destination.bytes);
if (destination.dccAddress != 0) writes.emplace_back(destination.dccAddress, destination.dccAddress + std::max<std::size_t>(colorKeyCount(destination, destination.bytes), 1u));
return writes;
}
if (pass.mode != ColorMetadataPass::Mode::EliminateFastClear && pass.mode != ColorMetadataPass::Mode::DccDecompress) return writes;
for (const auto& color : pass.targets) {
if (color.samples != 1u || color.bytes == 0) return {};
writes.emplace_back(color.address, color.address + color.bytes);
writes.emplace_back(color.MemoryAddress(), color.MemoryAddress() + color.bytes);
if (color.cmaskAddress != 0) writes.emplace_back(color.cmaskAddress, color.cmaskAddress + std::max<std::size_t>(color.cmaskBytes, 1u));
if (color.dccAddress != 0) writes.emplace_back(color.dccAddress, color.dccAddress + std::max<std::size_t>(colorKeyCount(color, color.bytes), 1u));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4527,8 +4527,8 @@ std::uint64_t ShaderResources::guestBufferBytes(std::span<const std::uint32_t> w
}

std::uint64_t ShaderResources::BytesUpToTarget(std::uint64_t address, std::uint64_t bytes, std::uint64_t stride, const ColorTarget* target) {
if (target == nullptr || stride == 0 || address >= target->address || address + bytes <= target->address || address + bytes - target->address >= stride) return bytes;
return target->address - address;
if (target == nullptr || stride == 0 || address >= target->MemoryAddress() || address + bytes <= target->MemoryAddress() || address + bytes - target->MemoryAddress() >= stride) return bytes;
return target->MemoryAddress() - address;
}

std::size_t ShaderResources::addGuestBuffer(std::span<const std::uint32_t> words, const ColorTarget* target, std::uint64_t indexAddress, std::size_t indexBytes, bool written, bool atomic, bool read, std::uint32_t extent) {
Expand All @@ -4544,7 +4544,7 @@ std::size_t ShaderResources::addGuestBuffer(std::span<const std::uint32_t> words
Require(byteSize <= context.limits.maxStorageBufferRange, "shader buffer exceeds descriptor range limit");
Require(byteSize <= std::numeric_limits<std::size_t>::max(), "shader buffer size exceeds host address space");
const auto size = static_cast<std::size_t>(BytesUpToTarget(address, byteSize, descriptor.Stride(), written ? nullptr : target));
Require(target == nullptr || !overlap(address, size, target->address, target->bytes), "shader buffer aliases the render target");
Require(target == nullptr || !overlap(address, size, target->MemoryAddress(), target->bytes), "shader buffer aliases the render target");
// APS5_ALL_BUFFERS_WRITTEN=1: every element is noted as written, as before bufferWritten existed.
static const bool allWritten = std::getenv("APS5_ALL_BUFFERS_WRITTEN") != nullptr;
Require(!written || !overlap(address, size, indexAddress, indexBytes), "writable shader buffer aliases the index buffer");
Expand Down
32 changes: 21 additions & 11 deletions core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2072,7 +2072,7 @@ GuestTextureResource SurfaceForTarget(const ColorTarget& color) {
surface.baseLevel = 0;
surface.lastLevel = color.mipCount - 1;
surface.tileMode = ColorTextureTileMode(color.tileMode);
surface.dimension = color.depth > 1 ? TextureDimension::k3D : TextureDimension::k2D;
surface.dimension = color.volume || color.depth > 1 ? TextureDimension::k3D : TextureDimension::k2D;
surface.format = GuestFormatFor(color.format, color.elementBytes);
surface.dstSelX = 4;
surface.dstSelY = 5;
Expand Down Expand Up @@ -2172,17 +2172,16 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
const auto attrib3 = read(cx, 0x3b8 + slot);
color.tileMode = DecodeColorTileMode(attrib3);
const bool volume = ((attrib3 >> 24u) & 3u) == 2u;
color.volume = volume;
Require(((attrib3 >> 24u) & 3u) != 0u || (attrib2 & 0x3fffu) == 0u, "1D color targets taller than one row are unsupported");
if (volume) {
Require(maxMip == 0 || (info & 0x10000000u) == 0, "DCC of mipmapped 3D color targets is unsupported");
Require(maxMip == 0 || (info & 0x2000u) == 0, "CMASK fast clears of mipmapped 3D color targets are unsupported");
color.depth = (attrib3 & 0x1fffu) + 1u;
if (maxMip != 0) {
char message[384];
std::snprintf(message, sizeof(message), "mipmapped 3D color targets are unsupported (slot %u, %ux%ux%u, viewMip %u, maxMip %u, slices %u..%u, CB_COLOR_INFO 0x%08x, ATTRIB2 0x%08x, ATTRIB3 0x%08x, VIEW 0x%08x)", slot, ((attrib2 >> 14u) & 0x3fffu) + 1u, (attrib2 & 0x3fffu) + 1u, color.depth, viewMip, maxMip, slice, lastSlice, info, attrib2, attrib3, view);
Require(false, message);
}
Require(slice < color.depth && lastSlice >= slice, "the color view slices are outside the 3D surface");
const auto viewDepth = std::max(color.depth >> viewMip, 1u);
Require(slice < viewDepth && lastSlice >= slice, "the color view slices are outside the 3D surface");
color.depthSlice = slice;
color.layers = std::min(lastSlice + 1u, color.depth) - slice;
color.layers = std::min(lastSlice + 1u, viewDepth) - slice;
} else if ((attrib3 & 0x1fffu) != 0) {
Require(lastSlice <= (attrib3 & 0x1fffu), "the color view slice is beyond the array surface");
color.arrayLastSlice = attrib3 & 0x1fffu;
Expand All @@ -2206,7 +2205,18 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
color.mip = viewMip;
std::uint32_t firstTailLevel = maxMip + 1u;
if (maxMip != 0) {
const auto mips = ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u);
const auto mips = [&] {
if (!volume) return ComputeElementMipLayout(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, maxMip + 1u);
GuestTextureResource surface{};
surface.width = color.extent.width;
surface.height = color.extent.height;
surface.depthOrLastArray = color.depth - 1u;
surface.mipCount = maxMip + 1u;
surface.tileMode = ColorTextureTileMode(color.tileMode);
surface.dimension = TextureDimension::k3D;
surface.format = GuestFormatFor(decoded.format, color.elementBytes);
return DescribeSurface(surface).mips;
}();
const auto& mip = mips.at(viewMip);
mipOffset = mip.tiledOffset;
color.mipTail = mip.tail;
Expand Down Expand Up @@ -2324,9 +2334,9 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
}
}
}
if (volume && (color.tileMode == ColorTileMode::RenderTarget || color.tileMode == ColorTileMode::Standard4KB)) {
if (volume && color.tileMode != ColorTileMode::Linear) {
color.bytes = DescribeSurface(SurfaceForTarget(color)).guestBytes;
GuestMemory::CheckRange(reinterpret_cast<const void*>(color.address), color.bytes, colorLayout.Alignment(), true);
GuestMemory::CheckRange(reinterpret_cast<const void*>(color.surfaceAddress), color.bytes, colorLayout.Alignment(), true);
}
return color;
}
Expand Down
3 changes: 2 additions & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1976,7 +1976,8 @@ VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip, s
Require(mip < descriptor.mipCount, "attachment mip exceeds the storage image");
const bool volume = descriptor.dimension == TextureDimension::k3D;
const bool array = !volume && descriptor.dimension == TextureDimension::k2DArray;
Require((depthSlice == 0 && layers == 1) || (volume && mip == 0 && layers != 0 && depthSlice + layers - 1u <= descriptor.depthOrLastArray) || (array && depthSlice == 0 && layers != 0), "attachment slice is outside the storage image");
const auto depth = volume ? std::max((descriptor.depthOrLastArray + 1u) >> mip, 1u) : 1u;
Require((depthSlice == 0 && layers == 1) || (volume && layers != 0 && depthSlice < depth && layers <= depth - depthSlice) || (array && depthSlice == 0 && layers != 0), "attachment slice is outside the storage image");
Require((layer == 0 && layers == 1) || (volume && layer == 0) || (array && descriptor.baseArray + layer + layers - 1u <= descriptor.depthOrLastArray), "attachment layer is outside the storage image");
const auto found = attachmentViews.find({format, mip, depthSlice, layers, layer});
if (found != attachmentViews.end()) return found->second;
Expand Down
Loading
Loading