mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-01 12:45:56 +00:00
Merge remote-tracking branch 'origin/main' into fix/one-sample-ancillary-unsampled
# Conflicts: # docs/dev/debt/libSceAgcDriver.md
This commit is contained in:
commit
5c19aa5e42
11 files changed
+146
-14
No files matched your search
@@ -3671,10 +3671,11 @@ bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped&
|
||||
} else if (state.refreshedGeneration > generation) {
|
||||
return true;
|
||||
}
|
||||
if (!adopting) current = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
||||
for (const auto& mapped : ranges) {
|
||||
const auto range = mapped.lock();
|
||||
if (range == nullptr || leasedRangeOwner(current, range->address) != range) continue;
|
||||
if (range == nullptr || range->bytes <= 0x10000) continue;
|
||||
if (current.empty()) current = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
||||
if (leasedRangeOwner(current, range->address) != range) continue;
|
||||
try {
|
||||
if (!GuestMemory::GpuMutex().HeldByThisThread()) {
|
||||
const auto run = importRun(current, range->address, std::numeric_limits<std::uint64_t>::max(), effectiveBudget(context, state));
|
||||
|
||||
@@ -633,7 +633,7 @@ private:
|
||||
if (decoded) loaded.indexModules.insert(identity);
|
||||
} else if (record.kind == GraphicsRecord || record.kind == ComputeRecord) {
|
||||
if (decode && !loaded.indexRecords.contains(record.payloadHash)) {
|
||||
LoadedEntry entry;
|
||||
LoadedEntry entry{};
|
||||
entry.kind = record.kind;
|
||||
bool decoded = false;
|
||||
if (record.kind == GraphicsRecord) {
|
||||
|
||||
@@ -1493,9 +1493,10 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
color.uintExport = slotExport == 7;
|
||||
if (color.samples == 1 && result.samples > 1 && result.colors.empty() && !result.depth && (read(cx, 0x292) & 1u) == 0) {
|
||||
const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage));
|
||||
Require(!result.perSampleShading && sampleInputs == 0 && (read(cx, 0x203) & 0x100u) == 0, "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export");
|
||||
Require(sampleInputs == 0 && (read(cx, 0x203) & 0x100u) == 0, "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with a sample index or coverage input, or a sample-mask export");
|
||||
result.samples = 1;
|
||||
result.customSampleLocations = false;
|
||||
result.perSampleShading = false;
|
||||
}
|
||||
if (color.samples > 1 && result.samples == 1 && result.colors.empty() && !result.depth && !result.alphaToCoverage && (read(cx, 0x292) & 1u) == 0) {
|
||||
Require(result.conservativeRasterization == VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT || (read(cx, 0x1b3) & 0x44u) == 0, "conservative rasterization with centroid interpolation of a draw raised to a multisampled target is unsupported");
|
||||
|
||||
@@ -880,7 +880,7 @@ private:
|
||||
|
||||
void importMappedFailureGenerationTests(const Context& context) {
|
||||
ClearHostImports(context.device);
|
||||
WindowedRange range(context, WindowedRange::piece, 1);
|
||||
WindowedRange range(context, 2 * WindowedRange::piece, 1);
|
||||
ImportAllocationProbe probe(range.importing);
|
||||
probe.failure = VK_ERROR_INVALID_EXTERNAL_HANDLE;
|
||||
GuestAllocations::Mapped mapped;
|
||||
@@ -1081,7 +1081,7 @@ void importNeighbouringEdgesTests(const Context& context) {
|
||||
// observer holds no GPU lock, so it must not evict (before, it evicted the least recently used import to fit).
|
||||
void importAtMapOverBudgetTests(const Context& context) {
|
||||
ClearHostImports(context.device);
|
||||
constexpr std::size_t bytes = 65536;
|
||||
constexpr std::size_t bytes = 2 * 65536;
|
||||
auto importing = context;
|
||||
importing.hostImportAlignment = bytes;
|
||||
void* kept = ::operator new(bytes, std::align_val_t{bytes});
|
||||
@@ -1142,17 +1142,32 @@ void importRequesterTests(const Context& context, const BdaTestAccess& access) {
|
||||
const auto counted = [](ImportRequester requester) { return HostImportCounters().requesters[static_cast<std::size_t>(requester)]; };
|
||||
const auto made = [&](ImportRequester requester) { return counted(requester).made; };
|
||||
Require(std::string(ImportRequesterName(ImportRequester::Map)) == "map" && std::string(ImportRequesterName(ImportRequester::Build)) == "build" && std::string(ImportRequesterName(ImportRequester::Access)) == "access" && std::string(ImportRequesterName(ImportRequester::Join)) == "join" && std::string(ImportRequesterName(ImportRequester::Segment)) == "segment", "an import requester has the wrong name");
|
||||
change(0, true);
|
||||
{
|
||||
const auto mappedRange = [&](std::size_t index) {
|
||||
GuestAllocations::Mapped ranges;
|
||||
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
||||
for (const auto& range : lease) {
|
||||
if (range->address == address(0)) ranges.push_back(range);
|
||||
if (range->address == address(index)) ranges.push_back(range);
|
||||
}
|
||||
Require(ranges.size() == 1, "the requester test's mapped range is not registered");
|
||||
return ranges;
|
||||
};
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(block + 6 * piece, 2 * piece, true, true, true);
|
||||
}
|
||||
{
|
||||
const auto before = made(ImportRequester::Map);
|
||||
static_cast<void>(ImportMappedRanges(importing, ranges, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true));
|
||||
Require(made(ImportRequester::Map) == before + 1 && HostImportCovers(importing, address(0), piece), "a range imported at map time was not counted for the map requester");
|
||||
static_cast<void>(ImportMappedRanges(importing, mappedRange(6), GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true));
|
||||
Require(made(ImportRequester::Map) == before + 1 && HostImportCovers(importing, address(6), 2 * piece), "a range imported at map time was not counted for the map requester");
|
||||
}
|
||||
change(0, true);
|
||||
{
|
||||
const auto map = made(ImportRequester::Map);
|
||||
const auto access = made(ImportRequester::Access);
|
||||
static_cast<void>(ImportMappedRanges(importing, mappedRange(0), GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true));
|
||||
Require(made(ImportRequester::Map) == map && !HostImportCovers(importing, address(0), 16), "a range of 64 KiB was imported at map time");
|
||||
Require(HostImportFor(importing, address(0), 16) != nullptr && HostImportCovers(importing, address(0), piece) && made(ImportRequester::Access) == access + 1 && made(ImportRequester::Map) == map,
|
||||
"a range of 64 KiB left to its first GPU use was not imported there, or not counted for that use's requester");
|
||||
}
|
||||
change(1, true);
|
||||
{
|
||||
@@ -1230,6 +1245,10 @@ void importRequesterTests(const Context& context, const BdaTestAccess& access) {
|
||||
const auto counts = HostImportCounters();
|
||||
Require(counts.live >= 4 && counts.liveBytes == HostImportLiveBytes(), "the live import counts disagree with the live bytes");
|
||||
for (std::size_t index = 0; index < 6; ++index) change(index, false);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(block + 6 * piece);
|
||||
}
|
||||
ClearHostImports(importing.device);
|
||||
::operator delete(block, std::align_val_t{piece});
|
||||
}
|
||||
|
||||
@@ -824,7 +824,7 @@ void msaaEnableClearOneSampleTargetTests() {
|
||||
queue = drawC();
|
||||
queue.context[0x1b3] |= ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary);
|
||||
requireOneSample(queue, "draw C with an enabled but unaddressed ANCILLARY input");
|
||||
constexpr std::string_view refused = "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with PS_ITER_SAMPLE, a sample index or coverage input, or a sample-mask export";
|
||||
constexpr std::string_view refused = "a one-sample color target under a multisampled rasterizer without MSAA_ENABLE is unsupported with a sample index or coverage input, or a sample-mask export";
|
||||
for (const auto input : {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::SampleCoverage}) {
|
||||
queue = drawC();
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(input);
|
||||
@@ -832,6 +832,10 @@ void msaaEnableClearOneSampleTargetTests() {
|
||||
}
|
||||
queue = drawC();
|
||||
queue.context[0x293] = 0x10000u;
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveSample);
|
||||
requireOneSample(queue, "draw C with PS_ITER_SAMPLE and a sample-interpolated input");
|
||||
Require(!DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE into a one-sample color target without MSAA_ENABLE asked for sample shading");
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary);
|
||||
expectFailure([&] { DecodeState(queue); }, refused);
|
||||
queue = drawC();
|
||||
queue.context[0x1c4] = 9;
|
||||
|
||||
@@ -2561,7 +2561,7 @@ void remappedImportTests(const Device& device) {
|
||||
|
||||
void hostImportRetirementTests(const Device& device) {
|
||||
const auto& context = device.GetContext();
|
||||
constexpr std::size_t UnitBytes = 65536;
|
||||
constexpr std::size_t UnitBytes = 2 * 65536;
|
||||
constexpr std::size_t AllocationBytes = 4 * UnitBytes;
|
||||
constexpr std::size_t CopyBytes = 256;
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct VulkanDeviceTestAccess {
|
||||
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Graphics = AgcDriver::Graphics;
|
||||
@@ -163,6 +171,41 @@ Outcome ResidentMappingsImportAfterCollection() {
|
||||
return Outcome::Passed;
|
||||
}
|
||||
|
||||
Outcome SmallMappingsImportOnFirstUse() {
|
||||
GuestBlock small(GuestAlignment);
|
||||
GuestBlock large(2 * GuestAlignment);
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return Outcome::NoDevice;
|
||||
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
|
||||
if (context.hostImportAlignment == 0) return Outcome::NoImport;
|
||||
for (auto* guest : {&small, &large}) {
|
||||
Graphics::ClearHostImports(device->Device());
|
||||
GuestAllocations::Mapped ranges;
|
||||
for (const auto& range : GuestAllocations::GuestAllocationsAcquire_nid_postfix()) {
|
||||
if (range->address == guest->Address()) ranges.push_back(range);
|
||||
}
|
||||
Require(ranges.size() == 1, "mapped import test lost its registered range");
|
||||
{
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(Graphics::ImportMappedRanges(context, ranges, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "mapped import test could not adopt the device");
|
||||
}
|
||||
Require(Graphics::HostImportCovers(context, guest->Address(), ProbeBytes) == (guest == &large), "mapping did not defer exactly the small import");
|
||||
Require(Fill(*device, guest->Address()), "first GPU use could not import the mapped buffer");
|
||||
Require(Graphics::HostImportCovers(context, guest->Address(), ProbeBytes), "first GPU use left the mapped buffer unimported");
|
||||
constexpr std::array<std::uint32_t, 4> pattern{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
||||
for (std::size_t offset = 0; offset < ProbeBytes; offset += sizeof(std::uint32_t)) {
|
||||
std::uint32_t actual = 0;
|
||||
std::memcpy(&actual, guest->Data() + offset, sizeof(actual));
|
||||
Require(actual == pattern[(offset / sizeof(actual)) % pattern.size()], "first GPU use changed the fill result");
|
||||
}
|
||||
for (std::size_t offset = ProbeBytes; offset < GuestAlignment; ++offset) {
|
||||
Require(guest->Data()[offset] == 0, "first GPU use overwrote bytes outside the fill");
|
||||
}
|
||||
}
|
||||
Graphics::ClearHostImports(device->Device());
|
||||
return Outcome::Passed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -176,6 +219,7 @@ int main() {
|
||||
const auto windows = WindowsFollowTheirRange();
|
||||
Require(windows == Outcome::Passed, "host import windows: the device stopped importing guest memory between two scenarios");
|
||||
Require(ResidentMappingsImportAfterCollection() == Outcome::Passed, "resident import test failed");
|
||||
Require(SmallMappingsImportOnFirstUse() == Outcome::Passed, "mapped import test lost its Vulkan device or host import support");
|
||||
std::puts("host import windows tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -643,6 +643,7 @@ if(BUILD_TESTING)
|
||||
add_asan_test_options(guest_module_args verify_asan_link_order=0)
|
||||
add_asan_test_options(guest_deferred_dependencies verify_asan_link_order=0)
|
||||
add_asan_test_options(guest_module_fini verify_asan_link_order=0)
|
||||
add_asan_test_options(guest_unused_import_dependencies verify_asan_link_order=0)
|
||||
add_test(NAME guest_needed_nested_modules COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_nested_modules.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_zero_initializer COMMAND ${Python3_EXECUTABLE}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- [Point geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 0` emits mesh points with `OutputPoints` and scalar `PrimitivePointIndicesEXT` as required by [SPV_EXT_mesh_shader](https://github.khronos.org/SPIRV-Registry/extensions/EXT/SPV_EXT_mesh_shader.html). Each packed primitive export contributes its first 9-bit vertex index and its null bit, using the same NGG primitive packing as line and triangle outputs. The subgroup reserves at least `GE_MAX_OUTPUT_PER_SUBGROUP` point slots, as the line path does; the console budget is not measured. Points use the register size or the misc-X point-size export and clamp interpreted by the vertex-point path; those register, clamp and rasterization inferences remain unmeasured on the console. Point sprites remain unsupported. Point output from a vertex workgroup still throws. Cocoon (PPSA08766) reaches this path with triangle-strip input.
|
||||
- [Line-strip geometry output](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - a geometry program with `VGT_GS_OUT_PRIM_TYPE = 1` (line strip) runs as a mesh shader with `OutputLinesEXT`: each primitive export is one line whose two vertex indices are packed like the three of a triangle, as Mesa's NGG lowering exports line strips (`ac_nir_lower_ngg_gs.c`), and each input primitive gets `VGT_GS_MAX_VERT_OUT - 1` lines where a triangle strip gets `- 2` triangles. A subgroup gets at least `GE_MAX_OUTPUT_PER_SUBGROUP` line slots; whether the console does the same is unmeasured. A fast-launch subgroup takes its line count from `GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR`. Line output counts as lines for polygon offset decode. The lines are drawn 1 pixel wide with Vulkan default rasterization; `PA_SU_LINE_CNTL` is not read, and console line rules are not measured.
|
||||
- [Alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - only `DB_ALPHA_TO_MASK = 0xaa01` is accepted: the enable bit with the four quad offsets at 2 and no `OFFSET_ROUND`, the undithered setting [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/gallium/drivers/radeonsi/si_state.c#L371-L381) programs. Draws are single-sampled, so the pixel shader [writes](../../../core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp) `SampleMask` 0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV with `RADV_DEBUG=noatocdithering`, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in [AlphaToCoverage.cpp](../../../core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp); it is not measured on gfx1013. Vulkan's `alphaToCoverageEnable` is not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets, `OFFSET_ROUND`, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as [radeonsi exports MRT0 as `32_AR` for alpha-to-coverage without a color buffer](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-24.0.0/src/gallium/drivers/radeonsi/si_state_shaders.cpp#L2425-L2431). A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered `DB_ALPHA_TO_MASK = 0x18701` additionally uses a per-quad sample-mask table [measured on gfx1036](https://github.com/boykopovar/AnyPS5/pull/4451#issuecomment-6124848921) for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013.
|
||||
- [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy, and for a one-sample depth target an extension of it. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. An `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw in a pixel program that runs (with a one-sample depth target, `ANCILLARY` only under `PS_ITER_SAMPLE`), and so does `PS_ITER_SAMPLE` with one-sample color targets and no depth target, because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its `PS_ITER_SAMPLE` and pixel input bits are ignored. With a one-sample depth target, a pixel program with `PS_ITER_SAMPLE` and neither input runs once per pixel; if the console runs it once per sample, its atomics and append or consume counters count differently. Without `PS_ITER_SAMPLE`, the [packed ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) gives such a program sample index 0, inferred for a pixel the guest does not iterate, and that is the sample the one-sample draw keeps. Points and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, `DB_EQAA.INTERPOLATE_COMP_Z` is accepted: with every sample at the pixel center, Z interpolated to any sample position is the center's Z, which the one-sample draw tests. That rests on the same inference; neither [PAL](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9MsaaState.cpp#L117-L118) nor [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/761d5c8a54f93b3d43608f718910ed742983dd0b/src/gallium/drivers/radeonsi/si_state.c#L3314-L3316) sets the bit, and its meaning is not documented. Line and point lists (`VGT_PRIMITIVE_TYPE` 1-3, even when a geometry program turns them into triangles), `INTERPOLATE_SRC_Z` and overrasterization (`OVERRASTERIZATION_AMOUNT`, `ENABLE_POSTZ_OVERRASTERIZATION`) still throw. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration. Gran Turismo 7 (PPSA01317) draws its main pass with a one-sample depth target, `DB_EQAA` 0x00050000 and a 4x configuration.
|
||||
- [A one-sample target under a multisampled rasterizer without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule [`VK_AMD_mixed_attachment_samples`](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/fragops.adoc#L2510-L2516) gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count ([VUID-VkGraphicsPipelineCreateInfo-subpass-01505](https://github.com/KhronosGroup/Vulkan-Docs/blob/193ebe195f47ffc5004683d1659e71c725658c4d/chapters/pipelines.adoc#L3033-L3041)), so here it is an analogy, and for a one-sample depth target an extension of it. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp#L127-L128) also rasterizes with the largest sample count of the bound depth and color targets ([L416-L417](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L416-L417), [L477-L480](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L477-L480)), and reads `PA_SC_AA_CONFIG` only for a `SAMPLE_COVERAGE` input ([L174-L177](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp#L174-L177)). This is not measured on hardware. An `ANCILLARY` or `SAMPLE_COVERAGE` pixel input and a sample-mask export throw in a pixel program that runs (with a one-sample depth target, `ANCILLARY` only under `PS_ITER_SAMPLE`), because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its `PS_ITER_SAMPLE` and pixel input bits are ignored. A pixel program with `PS_ITER_SAMPLE` and neither input runs once per pixel; if the console runs it once per sample, its atomics and append or consume counters count differently. With a one-sample depth target and without `PS_ITER_SAMPLE`, the [packed ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) gives a program that reads `ANCILLARY` sample index 0, inferred for a pixel the guest does not iterate, and that is the sample the one-sample draw keeps. Points and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, `DB_EQAA.INTERPOLATE_COMP_Z` is accepted: with every sample at the pixel center, Z interpolated to any sample position is the center's Z, which the one-sample draw tests. That rests on the same inference; neither [PAL](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9MsaaState.cpp#L117-L118) nor [radeonsi](https://gitlab.freedesktop.org/mesa/mesa/-/blob/761d5c8a54f93b3d43608f718910ed742983dd0b/src/gallium/drivers/radeonsi/si_state.c#L3314-L3316) sets the bit, and its meaning is not documented. Line and point lists (`VGT_PRIMITIVE_TYPE` 1-3, even when a geometry program turns them into triangles), `INTERPOLATE_SRC_Z` and overrasterization (`OVERRASTERIZATION_AMOUNT`, `ENABLE_POSTZ_OVERRASTERIZATION`) still throw. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration. Gran Turismo 7 (PPSA01317) draws its main pass with a one-sample depth target, `DB_EQAA` 0x00050000 and a 4x configuration.
|
||||
- [MSAA_ENABLE on a single-sample target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `PA_SC_MODE_CNTL_0.MSAA_ENABLE` with `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0` is drawn like the same state without the bit when sample 0 of the four `PA_SC_AA_SAMPLE_LOCS_PIXEL_*_0` registers is at the pixel center and the draw rasterizes filled triangles or rectangles (`VGT_PRIMITIVE_TYPE` 7 or 17 as well as the triangle types) with alpha-to-coverage and conservative rasterization off. [RADV](https://gitlab.freedesktop.org/mesa/mesa/-/blob/745709a000482a4a24c91faef5c5a40bd653a863/src/amd/vulkan/radv_cmd_buffer.c#L13606-13609) programs this state for one-sample custom sample locations on GFX10 and later, where the bit makes the rasterizer use the programmed location of sample 0 ([061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11597f720535f00e79a5752e24aff6)); that the bit changes nothing else for such a draw is inferred from that and is not measured on hardware. EA Sports UFC 5 (PPSA03541) reaches a rect-list draw in this state. Other locations of sample 0, absent location registers, points, lines, alpha-to-coverage and conservative rasterization throw
|
||||
- [A multisampled target rasterized without MSAA_ENABLE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - not measured on hardware: `PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES` > 0 with `PA_SC_MODE_CNTL_0.MSAA_ENABLE` clear puts every color, depth and stencil sample at the pixel center, as in GL without multisampling ([ARB_multisample](https://github.com/KhronosGroup/OpenGL-Registry/blob/6af574a14089/extensions/ARB/ARB_multisample.txt#L185), [RADV 061bc615](https://gitlab.freedesktop.org/mesa/mesa/-/commit/061bc6151a11)). Lines cover whole pixels by the host's line rule, not the console's aliased one ([D3D11.3](https://github.com/microsoft/DirectX-Specs/blob/f37b0d6033a1/d3d/archive/D3D11_3_FunctionalSpec.htm#L13705)). Depth sample tests and occlusion counts are unknown. Rasterized points, alpha-to-coverage, and Z interpolation, overrasterization or a sample-mask export with depth throw. PPSA06888 draws a 2x target, PPSA18216 a 4x depth and color pair and 4x lines.
|
||||
- [sceAgcDriverGetEqContextId](../../../core/libs/prx/libSceAgcDriver/Eq/src/Query.cpp) (libSceAgcDriver) - returns `ident`: an end-of-pipe interrupt is delivered only to the registration whose id equals the raising queue, so `ident` is that context's id (titles register 0 or 1 for graphics and `0x20 + 8 * pipe + queue` for compute). The console's value is not known; KytyPS5 returns the event's `data`, which it fills with the interrupt's context id, whereas `data` here is the EV_CLEAR trigger count. Other event filters throw
|
||||
|
||||
@@ -163,6 +163,9 @@ def check(found, doc, prefixes, out):
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
if hasattr(stream, "reconfigure"):
|
||||
stream.reconfigure(encoding="utf-8", errors="backslashreplace")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="list the environment names the code reads, or check them against a reference document")
|
||||
parser.add_argument("--check", action="store_true",
|
||||
|
||||
@@ -3,6 +3,7 @@ import io
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
@@ -213,6 +214,64 @@ class EnvReferenceTests(unittest.TestCase):
|
||||
(core / "3rdparty" / "Ignored.cpp").write_text('static bool b = IsEnvironmentSet("APS5_IGNORED");\n', encoding="utf-8")
|
||||
self.assertEqual(set(env_reference.reads([core], BOTH)), {"APS5_TRACE_GPU"})
|
||||
|
||||
def test_main_reconfigures_streams_for_unicode_report_paths(self):
|
||||
class Stream:
|
||||
def __init__(self):
|
||||
self.encoding = "ascii"
|
||||
self.errors = "strict"
|
||||
self.text = ""
|
||||
|
||||
def reconfigure(self, encoding=None, errors=None):
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
|
||||
def write(self, text):
|
||||
text.encode(self.encoding, self.errors)
|
||||
self.text += text
|
||||
return len(text)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
root = self.fixture() / "source-Ж"
|
||||
root.mkdir()
|
||||
core = root / "core"
|
||||
core.mkdir()
|
||||
(core / "Sample.cpp").write_text('static bool a = std::getenv("APS5_TRACE_GPU") != nullptr;\n', encoding="utf-8")
|
||||
out, err = Stream(), Stream()
|
||||
with mock.patch.object(env_reference.sys, "stdout", out), mock.patch.object(env_reference.sys, "stderr", err):
|
||||
self.assertEqual(env_reference.main(["--root", str(core)]), 0)
|
||||
self.assertEqual((out.encoding, out.errors), ("utf-8", "backslashreplace"))
|
||||
self.assertEqual((err.encoding, err.errors), ("utf-8", "backslashreplace"))
|
||||
self.assertIn((core / "Sample.cpp").as_posix(), out.text)
|
||||
|
||||
def test_main_reports_unicode_scan_root_with_non_utf8_stderr(self):
|
||||
class Stream:
|
||||
def __init__(self):
|
||||
self.encoding = "ascii"
|
||||
self.errors = "strict"
|
||||
self.text = ""
|
||||
|
||||
def reconfigure(self, encoding=None, errors=None):
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
|
||||
def write(self, text):
|
||||
text.encode(self.encoding, self.errors)
|
||||
self.text += text
|
||||
return len(text)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
root = Path(tempfile.gettempdir()) / "missing-source-Ж"
|
||||
out, err = Stream(), Stream()
|
||||
with mock.patch.object(env_reference.sys, "stdout", out), mock.patch.object(env_reference.sys, "stderr", err):
|
||||
self.assertEqual(env_reference.main(["--root", str(root)]), 1)
|
||||
self.assertEqual((out.encoding, out.errors), ("utf-8", "backslashreplace"))
|
||||
self.assertEqual((err.encoding, err.errors), ("utf-8", "backslashreplace"))
|
||||
self.assertIn(str(root), err.text)
|
||||
|
||||
def test_main_rejects_a_scan_root_that_is_not_a_directory(self):
|
||||
root = self.fixture()
|
||||
for name in ("missing", "core/Sample.cpp"):
|
||||
|
||||
Reference in new issue
Block a user