Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
15 changes: 6 additions & 9 deletions core/libs/prx/libSceAgc/DcbState/src/Init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

static constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);

extern "C" {

int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(state), alignof(std::uint32_t), __func__);
Agc::Command::Require(version < 14, __func__, "unsupported AGC initialization version");
return 0;
}

int APS5_VABI sceAgcInit(std::uint32_t version) {
if (version >= 14) return SCE_AGC_ERROR_INVALID_ARGUMENT;
if (version >= 14) version = 13;
Agc::Command::GetRegisterDefaults(version, false, __func__);
Agc::Command::GetRegisterDefaults(version, true, __func__);
return 0;
}

int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(state), alignof(std::uint32_t), __func__);
return sceAgcInit(version);
}

}
10 changes: 4 additions & 6 deletions core/libs/prx/libSceAgc/tests/Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ extern "C" int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId);

namespace {

constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);

void check(bool condition, const char* message) {
if (!condition) {
throw std::runtime_error(message);
Expand Down Expand Up @@ -538,12 +536,12 @@ void testDefaults() {
check(sceAgcInit_0090(&state, 8) == 0 && state == 0x12345678, "AGC initialization failed or modified caller state");
check(sceAgcInit_0090(&state, 13) == 0 && state == 0x12345678, "AGC version 13 initialization changed caller state");
expectFailure([] { sceAgcInit_0090(nullptr, 8); });
expectFailure([&] { sceAgcInit_0090(&state, 14); });
check(sceAgcInit_0090(&state, 14) == 0 && state == 0x12345678, "out-of-range AGC initialization version changed caller state");
check(sceAgcInit(8) == 0, "AGC version initialization failed");
check(sceAgcInit(13) == 0, "AGC version 13 initialization failed");
check(sceAgcInit(14) == SCE_AGC_ERROR_INVALID_ARGUMENT, "unsupported AGC version did not return invalid argument");
check(sceAgcInit(0xffffffffu) == SCE_AGC_ERROR_INVALID_ARGUMENT, "maximum AGC version did not return invalid argument");
check(sceAgcInit(0xffffc278u) == SCE_AGC_ERROR_INVALID_ARGUMENT, "pointer-valued AGC argument did not return invalid argument");
check(sceAgcInit(14) == 0, "out-of-range AGC version did not fall back");
check(sceAgcInit(0xffffffffu) == 0, "maximum AGC version did not fall back");
check(sceAgcInit(0xffffc278u) == 0, "pointer-valued AGC argument did not fall back");
for (std::uint32_t version = 0; version < 14; ++version) {
for (const bool internal : {false, true}) {
auto* first = Agc::Command::GetRegisterDefaults(version, internal, __func__);
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ std::string Name(std::uint32_t header);
// A PM4 type-2 packet is a one-dword filler (command-buffer padding); type 3 and type 0 carry a
// dword count in bits 29:16. Type 1 is undefined.
inline bool FillerPacket(std::uint32_t header) { return (header >> 30u) == 2u || header == 0xffff1000u; }
inline bool PureNopPacket(std::uint32_t header) { return (header & 0xc000ffffu) == 0xc0001000u && header != 0xffff1000u; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip pure NOPs in the Metal packet loop

On Metal, Execution/Metal/MetalPacketExecution.mm:739-745 still sends every counted NOP through Pm4::Validate and later Pm4::Execute instead of checking this predicate. Besides missing the intended fast path, a pure NOP whose ignored payload begins with 0x6875 still triggers the typed-user-data rejection in Validate, so command streams accepted by the shared executor can fail on Metal; apply PureNopPacket before Metal validation as well.

Useful? React with 👍 / 👎.

inline std::size_t PacketWords(std::uint32_t header) { return FillerPacket(header) ? 1u : static_cast<std::size_t>((header >> 16u) & 0x3fffu) + 2u; }
std::string_view UnsupportedReason(std::uint32_t header);
void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
Expand Down Expand Up @@ -149,6 +150,7 @@ std::uint32_t DmaDestination(std::span<const std::uint32_t> packet);
// packet made by reading them there (through the checked guest memory path, which waits for
// recorded GPU work that writes them), and both steps in one for the packet.
std::uint64_t DispatchArgumentAddress(std::span<const std::uint32_t> packet, const QueueState& queue);
void NoteDispatchRegisters(std::span<const std::uint32_t> packet, QueueState& queue);
std::array<std::uint32_t, 5> ReadDispatchArguments(std::uint64_t arguments, std::uint32_t initiator);
std::array<std::uint32_t, 3> PartialGroupThreads(const std::array<std::uint32_t, 3>& groups, const std::array<std::uint32_t, 3>& numThreads);
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ inline Registers BuildInitialContextRegisters() {
{0xc, 0}, {0xd, 0x40004000}, {0x81, 0x80000000}, {0x82, 0x40004000},
{0x90, 0x80000000}, {0x91, 0x40004000},
{0x105, 0}, {0x106, 0}, {0x107, 0}, {0x108, 0},
{0x10b, 0}, {0x10c, 0}, {0x10d, 0},
{0x1b1, 0}, {0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x1c3, 0}, {0x1c4, 0}, {0x1c5, 0},
{0x1ff, 0}, {0x292, 2}, {0x293, 0}, {0x29b, 0},
{0x2ce, 0}, {0x2d3, 0}, {0x2d5, 0}, {0x2d6, 0}, {0x2db, 0},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
}
const auto count = Pm4::PacketWords(header);
if (cursor + count > commands.size()) break;
if (Pm4::PureNopPacket(header)) {
cursor += count;
continue;
}
const auto packet = std::span(commands).subspan(cursor, count);
const auto opcode = (header >> 8u) & 0xffu;
const auto next = cursor + count;
Expand Down Expand Up @@ -154,6 +158,7 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
try {
if (opcode == 0x15) dispatch(scratch, packet, submission);
else dispatchIndirect(scratch, packet, submission);
Pm4::NoteDispatchRegisters(packet, scratch);
} catch (const std::exception&) {
++stats.failures;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ void Driver::execute(const Submission& submission) {
const auto header = submission.commands[cursor];
if (Pm4::FillerPacket(header)) { ++cursor; continue; }
const auto count = Pm4::PacketWords(header);
if (Pm4::PureNopPacket(header)) { cursor += count; continue; }
const auto packet = std::span(submission.commands).subspan(cursor, count);
const auto opcode = (header >> 8u) & 0xffu;
auto nextCursor = cursor + count;
Expand Down Expand Up @@ -245,6 +246,7 @@ void Driver::execute(const Submission& submission) {
}
if (opcode == 0x15) timed(&WorkerProfile::dispatchMs, [&] { dispatch(queue, packet, submission); });
else timed(&WorkerProfile::dispatchMs, [&] { dispatchIndirect(queue, packet, submission); });
Pm4::NoteDispatchRegisters(packet, queue);
Graphics::Recorder::CountRecordedWork();
finishDispatchPacket(opcode == 0x16);
} else if (opcode == 0x3c || opcode == 0x93) {
Expand Down
41 changes: 37 additions & 4 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,12 @@ void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t byt
GuestMemory::Write(destination, copySource(source, bytes, immediate));
}

constexpr std::uint32_t CopyDataRegisterSource = 0;
constexpr std::uint32_t CopyDataGpuClockSource = 18;
constexpr std::uint32_t CopyDataCachePolicy = (3u << 13u) | (3u << 25u);
constexpr std::uint32_t ShaderRegisterBase = 0x2c00;
constexpr std::uint32_t ContextRegisterBase = 0xa000;
constexpr std::uint32_t UserConfigRegisterBase = 0xc000;

bool copyDataToRegister(std::span<const std::uint32_t> packet) {
return ((packet[1] >> 8u) & 0xfu) == 0;
Expand All @@ -149,8 +152,27 @@ std::uint64_t gpuClockCount() {
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count() / 10);
}

std::vector<std::byte> copyDataSource(std::span<const std::uint32_t> packet, std::size_t bytes) {
std::uint32_t registerBankBase(std::uint32_t offset) {
if (offset - ShaderRegisterBase < 0x400u) return ShaderRegisterBase;
if (offset - ContextRegisterBase < 0x400u) return ContextRegisterBase;
if (offset - UserConfigRegisterBase < 0x4000u) return UserConfigRegisterBase;
return 0;
}

std::vector<std::byte> copyDataSource(std::span<const std::uint32_t> packet, const QueueState& queue, std::size_t bytes) {
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
if (source == CopyDataRegisterSource) {
std::vector<std::byte> data(bytes);
for (std::size_t word = 0; word < bytes / 4; ++word) {
const auto offset = packet[2] + static_cast<std::uint32_t>(word);
const auto base = registerBankBase(offset);
const auto& bank = base == ShaderRegisterBase ? queue.shader : base == ContextRegisterBase ? queue.context : queue.userConfig;
if (base == 0 || !bank.contains(offset - base)) throw std::runtime_error("COPY_DATA source register 0x" + ToHex(offset) + " has no known value");
const auto value = bank.at(offset - base);
for (std::size_t i = 0; i < 4; ++i) data[word * 4 + i] = static_cast<std::byte>(value >> (i * 8));
}
return data;
}
if (source != CopyDataGpuClockSource) return copySource(address(packet[2], packet[3]), bytes, source >= 10);
const auto value = gpuClockCount();
std::vector<std::byte> data(bytes);
Expand Down Expand Up @@ -467,7 +489,8 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
break;
}
require(destination == 2 || destination == 4 || destination == 10, "COPY_DATA GDS or performance-counter destination is not implemented");
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11 || source == CopyDataGpuClockSource, "COPY_DATA register, GDS or reference-clock source is not implemented");
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11 || source == CopyDataGpuClockSource || source == CopyDataRegisterSource, "COPY_DATA GDS or reference-clock source is not implemented");
require(source != CopyDataRegisterSource || (packet[3] == 0 && registerBankBase(packet[2]) != 0 && registerBankBase(packet[2]) == registerBankBase(packet[2] + copyDataCount(packet) - 1)), "COPY_DATA from a register outside the shader, context and user config registers is not implemented");
require(source < 10 || source == CopyDataGpuClockSource || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented");
break;
}
Expand Down Expand Up @@ -594,10 +617,11 @@ std::optional<StoreWrite> ResolveStore(std::span<const std::uint32_t> packet, co
case 0x40: {
if (packet.size() < 6 || copyDataToRegister(packet)) return std::nullopt;
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
if (source == CopyDataRegisterSource) return std::nullopt;
const std::size_t bytes = (packet[1] & 0x10000u) != 0 ? 8 : 4;
const auto destination = address(packet[4], packet[5]);
if (!fits(destination, bytes)) return std::nullopt;
return StoreWrite{destination, {}, copyDataSource(packet, bytes)};
return StoreWrite{destination, {}, copyDataSource(packet, queue, bytes)};
}
case 0x50: {
if (packet.size() < 7 || dmaDestination(packet) == DmaSelectGds) return std::nullopt;
Expand Down Expand Up @@ -816,6 +840,15 @@ std::vector<std::uint32_t> ReadIndirectRegisters(std::span<const std::uint32_t>
return pairs;
}

void NoteDispatchRegisters(std::span<const std::uint32_t> packet, QueueState& queue) {
const bool direct = ((packet[0] >> 8u) & 0xffu) == 0x15u;
queue.shader.insert_or_assign(0x200, packet.back());
Comment on lines +843 to +845

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use dispatch-register tracking in the Metal executor

When the Metal backend is enabled, Execution/Metal/MetalPacketExecution.mm:828-836 handles direct and indirect dispatches without calling this new helper. A subsequent source-register COPY_DATA from COMPUTE_DISPATCH_INITIATOR or the direct group-count registers therefore reaches copyDataSource with those queue entries absent or stale and throws instead of copying the dispatched values; invoke NoteDispatchRegisters after both Metal dispatch paths as the shared executor now does.

Useful? React with 👍 / 👎.

for (std::uint32_t i = 0; i < 3; ++i) {
if (direct) queue.shader.insert_or_assign(0x201 + i, packet[1 + i]);
else queue.shader.erase(0x201 + i);
}
}

void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue) {
const auto opcode = (packet[0] >> 8u) & 0xffu;
require(packet.size() == 5 && IndirectRegisterOpcode(opcode), "expected indirect register packet");
Expand Down Expand Up @@ -945,7 +978,7 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
return;
}
const auto destination = address(packet[4], packet[5]);
const auto data = copyDataSource(packet, (packet[1] & 0x10000u) != 0 ? 8 : 4);
const auto data = copyDataSource(packet, queue, (packet[1] & 0x10000u) != 0 ? 8 : 4);
GuestMemory::CheckRange(reinterpret_cast<void*>(destination), data.size(), 1, true);
GuestMemory::Write(destination, data);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ struct GuestTextureResource {

float EffectiveMinLod(const GuestTextureResource& resource);

bool IsNullTextureResource(std::span<const std::uint32_t> words);
GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words);
bool DescriptorSingleLevel(std::span<const std::uint32_t> words);
bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ TextureDimension resolveDimension(std::uint32_t raw) {

}

bool IsNullTextureResource(std::span<const std::uint32_t> words) {
Require(words.size() == 8, "guest texture descriptor must contain 8 dwords");
const auto base40 = (static_cast<std::uint64_t>(words[0]) | (static_cast<std::uint64_t>(words[1]) << 32u)) & 0xffffffffffull;
const auto type = (words[3] >> 28u) & 0xfu;
return base40 == 0 || type == 0;
Comment on lines +53 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse null-texture detection in the Metal backend

For Metal draws containing a type-0 texture descriptor with a nonzero base, Graphics/Metal/MetalSampler.hpp:30-31 still recognizes only zero-base descriptors as null. The Metal binding paths consequently pass this descriptor to DecodeTextureResource, which rejects image type 0, while the Vulkan/shared paths now leave it unbound; make NullImageDescriptor delegate to this shared predicate so the fix applies to Metal too.

Useful? React with 👍 / 👎.

}

GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words) {
Require(words.size() == 8, "guest texture descriptor must contain 8 dwords");

Expand Down
16 changes: 8 additions & 8 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2162,7 +2162,7 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span
if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage) {
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
if (IsNullTextureResource(words)) continue;
if (textureIndex >= textures.size()) return OwnRefreshFallback::Uncached;
const auto index = textureIndex++;
const auto overlap = listed(index, false);
Expand All @@ -2172,7 +2172,7 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span
} else if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) {
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
if (IsNullTextureResource(words)) continue;
if (storageIndex >= storageTextures.size()) return OwnRefreshFallback::Uncached;
const auto index = storageIndex++;
if (listed(index, true) == overlapping.end()) continue;
Expand Down Expand Up @@ -2215,7 +2215,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
const auto elementWords = binding.count != 0 ? binding.guestDescriptor.size() / binding.count : 0;
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
if (IsNullTextureResource(words)) continue;
const auto resource = DecodeTextureResource(words);
const VkComponentMapping components = ViewComponents(resource);
if (textureIndex >= textures.size() || cachedTexture(context, words, resource, components, 0, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element)) != textures[textureIndex]) return false;
Expand All @@ -2224,7 +2224,7 @@ bool ShaderResources::Revalidate(std::span<const CompiledShader> shaders, ProofR
} else if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) {
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8u, 8u);
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
if (IsNullTextureResource(words)) continue;
if (storageIndex >= storageTextures.size()) return false;
std::shared_ptr<StorageTexture> expected;
if (SameAsPreviousStorageElement(binding, element) && StorageDedupeEnabled()) expected = storageTextures[storageIndex - 1];
Expand Down Expand Up @@ -3054,7 +3054,7 @@ bool ShaderResources::precollectImages() {
}
ImageRecord record;
record.sampled = sampled;
if (words[0] != 0u || (words[1] & 0xffu) != 0u) {
if (!IsNullTextureResource(words)) {
const auto* memo = words.size() == 8 ? &decodedImage(context.device, words) : nullptr;
record.resource = memo != nullptr ? memo->resource : DecodeTextureResource(words);
record.guestBytes = memo != nullptr ? memo->guestBytes : DescribeSurface(record.resource).guestBytes;
Expand Down Expand Up @@ -3163,7 +3163,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
for (std::uint32_t element = 0; element < binding.count; ++element) {
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
const auto* record = nextRecord();
if (words[0] == 0u && (words[1] & 0xffu) == 0u) {
if (IsNullTextureResource(words)) {
item.imageAllocations.push_back(std::numeric_limits<std::size_t>::max());
--plannedSampledImages;
continue;
Expand Down Expand Up @@ -3228,7 +3228,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
if (sameAsPrevious) ++mipOffset;
else mipOffset = 0;
const auto* record = nextRecord();
if (words[0] == 0u && (words[1] & 0xffu) == 0u) {
if (IsNullTextureResource(words)) {
item.imageAllocations.push_back(std::numeric_limits<std::size_t>::max());
--plannedStorageImages;
continue;
Expand Down Expand Up @@ -3296,7 +3296,7 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> ShaderResources::PresyncSur
// Stage A has not run (an address-based build, or the pass is off): decoded from the shader.
if (deferredCompute.program == nullptr) return surfaces;
forEachImageElement(*deferredCompute.program, [&](const ShaderRecompiler::DescriptorBinding& binding, std::uint32_t, std::span<const std::uint32_t> words) {
if (words[0] == 0u && (words[1] & 0xffu) == 0u) return;
if (IsNullTextureResource(words)) return;
const auto resource = DecodeTextureResource(words);
consider(resource.format, resource.baseAddress, DescribeSurface(resource).guestBytes, binding.kind == ShaderRecompiler::DescriptorKind::SampledImage);
});
Expand Down
Loading
Loading