diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index 4210d52904b..613374a4f67 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -20,8 +20,17 @@ struct VertexFormat { const char* scalar; }; +inline bool NullVertexDescriptor(const ShaderRecompiler::VertexAttribute& attribute) { + const auto& fields = attribute.resource.fields; + return fields[0] == 0 && fields[1] == 0 && fields[2] == 0 && fields[3] == 0; +} + inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& attribute) { Require(attribute.components >= 1 && attribute.components <= 4, "invalid vertex attribute component count"); + if (NullVertexDescriptor(attribute)) { + const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; + return {formats[attribute.components - 1], attribute.components * 4u, 4u, "f32"}; + } const auto components = attribute.formatComponents == 0u ? attribute.components : attribute.formatComponents; Require(components >= 1u && components <= 4u, "invalid vertex format component count"); const auto format = (attribute.resource.fields[3] >> 12u) & 0x7fu; @@ -110,7 +119,7 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa Require((fields[1] & 0x80000000u) == 0 && (fields[3] & 0x00800000u) == 0 && (fields[3] >> 30u) == 0, "unsupported vertex buffer descriptor flags"); const auto stride = (fields[1] >> 16u) & 0x3fffu; const auto address = fields[0] | (static_cast(fields[1] & 0xffffu) << 32u); - Require(address != 0 && address % format.alignment == 0 && stride % format.alignment == 0, "unaligned vertex buffer"); + Require(NullVertexDescriptor(attribute) || (address != 0 && address % format.alignment == 0 && stride % format.alignment == 0), "unaligned vertex buffer"); Require(stride <= context.limits.maxVertexInputBindingStride, "vertex stride exceeds device limits"); Require(context.formatProperties != nullptr, "missing vertex format property query"); VkFormatProperties properties{}; @@ -142,6 +151,7 @@ inline std::uint64_t ZeroStrideAvailableBytes(const ShaderRecompiler::VertexAttr } inline std::size_t VertexBufferExtent(const ShaderRecompiler::VertexAttribute& attribute) { + if (NullVertexDescriptor(attribute)) return DecodeVertexFormat(attribute).bytes; const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu; const auto records = attribute.resource.fields[2]; Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); @@ -155,6 +165,7 @@ inline std::size_t VertexBufferExtent(const ShaderRecompiler::VertexAttribute& a inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t maxIndex, std::uint32_t instances, std::uint32_t firstInstance = 0) { Require(instances != 0, "vertex input requires nonzero instance count"); Require(firstInstance <= std::numeric_limits::max() - (instances - 1u), "vertex input instance range overflow"); + if (NullVertexDescriptor(attribute)) return DecodeVertexFormat(attribute).bytes; const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu; const auto records = attribute.resource.fields[2]; Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 0ebd812fdbf..a1b13f48299 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1075,7 +1075,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P std::vector> zeroed(attributes.size()); for (std::size_t i = 0; i < attributes.size(); ++i) { const auto& attribute = attributes[i]; - if (VertexFetchOutOfRange(attribute)) { + if (NullVertexDescriptor(attribute) || VertexFetchOutOfRange(attribute)) { zeroed[i] = zeroVertexBuffer(context, attribute); continue; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index e8d45f11b4b..43b77c5da92 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3479,6 +3479,21 @@ void storeAtFlipTests() { for (const char* value : {"0", "", "true", "11"}) expectFailure([&] { StorageTexture::StoreAtFlipRequested(value); }, "expected 1"); } +void nullVertexDescriptorTests() { + using AgcDriver::Graphics::DecodeVertexFormat; + constexpr std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; + ShaderRecompiler::VertexAttribute attribute{}; + attribute.formatComponents = 1; + for (std::uint32_t components = 1; components <= 4; ++components) { + attribute.components = components; + const auto format = DecodeVertexFormat(attribute); + Require(format.format == formats[components - 1] && format.bytes == components * 4u && std::string_view(format.scalar) == "f32", "a null V# did not read as zero floats of the attribute's width"); + Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100000, 3, 2) == format.bytes && AgcDriver::Graphics::VertexBufferExtent(attribute) == format.bytes, "a null V# was sized as a guest range"); + } + attribute.resource.fields = {0x1000, 0, 0, 0}; + expectFailure([&] { DecodeVertexFormat(attribute); }, "unsupported vertex format"); +} + void vertexCopyTests() { using AgcDriver::Graphics::PlanVertexCopies; using AgcDriver::Graphics::VertexFetch; @@ -3605,6 +3620,7 @@ int main() { vertexCopyTests(); highestDrawIndexTests(); storeAtFlipTests(); + nullVertexDescriptorTests(); pixelParameterSlotTests(); rectListTests(); floatControlsModeTests(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 9876e3ac49b..0d313ef87a4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -422,3 +422,4 @@ Throughout the project, every function at every stage either **does exactly what - [syslog](../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:] `, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER`, and errno is preserved. `openlog`, `setlogmask` and `closelog` are not implemented, so the identity, options and mask they set are not applied - [sigaction](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers and other signals (`SIGBUS`, `SIGHUP`, ...) are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`; `raise` of an `SA_SIGINFO` handler on a signal `signal` delivers throws instead of terminating, and `raise` of the other signals still returns `EINVAL` - [AJM](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp), [APR](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) and [NGS2](../../core/libs/prx/libSceNgs2.native/src/System.cpp) state is allocated with `new` and never freed on purpose: title threads still call into these libraries while `std::exit` runs static destructors, so ordinary statics would be destroyed under them. Keep them leaked; `guest_ajm_teardown` covers the AJM case on Unix. +- [Null vertex buffer V#](../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) (`NUM_RECORDS` 0, `DST_SEL` 0 selects 0), not measured on hardware.