Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words,
Require(mipFilter != 2u || reductionMode == VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT, "guest sampler descriptor combines a min or max reduction with a linear mip filter, which is not implemented");

const auto aniso = isAnisoFilter(xyMagFilter) || isAnisoFilter(xyMinFilter);
Require(!aniso || reductionMode == VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT, "guest sampler descriptor combines a min or max reduction with anisotropic filtering, which is not implemented");
auto anisoRatio = 1.0f;
if (aniso) {
if (maxAnisoRatio > 4u) Require(false, "guest sampler descriptor uses an unknown anisotropy ratio " + std::to_string(maxAnisoRatio));
Expand Down
33 changes: 29 additions & 4 deletions core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ struct SamplerCapture {
const void* next = nullptr;
VkSamplerReductionMode reductionMode = VK_SAMPLER_REDUCTION_MODE_MAX_ENUM;
VkSamplerCreateFlags flags = 0;
bool anisotropyEnable = false;
float maxAnisotropy = 0.0f;
};
SamplerCapture samplerCapture;

Expand All @@ -107,6 +109,8 @@ VKAPI_ATTR VkResult VKAPI_CALL captureCreateSampler(VkDevice, const VkSamplerCre
samplerCapture.created = true;
samplerCapture.next = info->pNext;
samplerCapture.flags = info->flags;
samplerCapture.anisotropyEnable = info->anisotropyEnable == VK_TRUE;
samplerCapture.maxAnisotropy = info->maxAnisotropy;
const auto* reduction = static_cast<const VkSamplerReductionModeCreateInfoEXT*>(info->pNext);
if (reduction != nullptr && reduction->sType == VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT && reduction->pNext == nullptr) samplerCapture.reductionMode = reduction->reductionMode;
*sampler = fakeHandle<VkSampler>(0x5a);
Expand Down Expand Up @@ -174,14 +178,30 @@ void RunSamplerReductionTests(const Fields& base) {
badFilterMode.filterMode = 3;
rejectFields(badFilterMode, "unknown reduction filter mode 3");
Fields anisoReduction = base;
anisoReduction.filterMode = 1;
anisoReduction.xyMagFilter = 3;
anisoReduction.xyMinFilter = 3;
anisoReduction.maxAnisoRatio = 2;
anisoReduction.mipFilter = 1;
rejectFields(anisoReduction, "min or max reduction with anisotropic filtering");
context.samplerAnisotropy = true;
context.limits.maxSamplerAnisotropy = 16.0f;
for (std::uint32_t mode = 1; mode < modes.size(); ++mode) {
for (const std::uint32_t filter : {2u, 3u}) {
anisoReduction.filterMode = mode;
anisoReduction.xyMagFilter = filter;
anisoReduction.xyMinFilter = filter;
const auto decoded = DecodeSamplerResource(pack(anisoReduction));
Require(decoded.reductionMode == modes.at(mode) && decoded.anisotropyEnable && decoded.maxAnisotropy == 4.0f, "anisotropic min or max sampler decoded incorrectly");
const auto created = createSampler(context, decoded);
Require(created.reductionMode == modes.at(mode), "an anisotropic min or max sampler did not chain its reduction mode");
Require(created.anisotropyEnable && created.maxAnisotropy == 4.0f, "an anisotropic min or max sampler lost its anisotropy");
Require(Sampler(context, decoded).RequiresFilterMinmax() == (filter == 3u), "anisotropic sampler min/max format requirement is wrong");
}
}
context.samplerAnisotropy = false;
context.limits.maxSamplerAnisotropy = 1.0f;
anisoReduction.filterMode = 0;
Require(DecodeSamplerResource(pack(anisoReduction)).anisotropyEnable, "anisotropic weighted-average sampler was rejected");
anisoReduction.filterMode = 1;
anisoReduction.mipFilter = 2;
rejectFields(anisoReduction, "min or max reduction with a linear mip filter");

auto compared = captured;
compared.compareEnable = true;
Expand Down Expand Up @@ -235,6 +255,11 @@ void RunSamplerReductionTests(const Fields& base) {
maxLinearMip.filterMode = 2;
maxLinearMip.mipFilter = 2;
reject([&] { pointFiltered(pack(maxLinearMip)); }, "needs point filtering");
Fields minAnisoPoint = minPoint;
minAnisoPoint.xyMagFilter = 2;
minAnisoPoint.xyMinFilter = 2;
minAnisoPoint.maxAnisoRatio = 1;
reject([&] { pointFiltered(pack(minAnisoPoint)); }, "needs point filtering");
}

void RunNonSeamlessCubeTests(const Fields& base) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ constexpr std::uint32_t ReductionMin = 1;
constexpr std::uint32_t ReductionMax = 2;
constexpr std::uint32_t FilterPoint = 0;
constexpr std::uint32_t FilterBilinear = 1;
constexpr std::uint32_t FilterAnisoBilinear = 3;
constexpr std::uint32_t Aniso2x = 1;
constexpr std::uint32_t MipNone = 0;
constexpr std::uint32_t MipPoint = 1;
constexpr std::uint32_t MipLinear = 2;
Expand Down Expand Up @@ -67,6 +69,7 @@ struct Sampler {
std::uint32_t reduction;
std::uint32_t filter;
std::uint32_t mip;
std::uint32_t anisoRatio = 0;
};

std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
Expand All @@ -89,7 +92,7 @@ std::array<std::uint32_t, 8> TextureDescriptor(std::uint32_t format, const void*
}

std::array<std::uint32_t, 4> SamplerDescriptor(const Sampler& sampler) {
return {0x92u | (sampler.reduction << 29u), 0x00fff000u, (sampler.filter << 20u) | (sampler.filter << 22u) | (1u << 24u) | (sampler.mip << 26u), 0u};
return {0x92u | (sampler.anisoRatio << 9u) | (sampler.reduction << 29u), 0x00fff000u, (sampler.filter << 20u) | (sampler.filter << 22u) | (1u << 24u) | (sampler.mip << 26u), 0u};
}

void FillTexels() {
Expand Down Expand Up @@ -301,6 +304,8 @@ int main() {
Run(*device, {ReductionMax, FilterPoint, MipPoint}, "max, point, point mip");
Run(*device, {ReductionMin, FilterBilinear, MipNone}, "min, bilinear, no mip");
Run(*device, {ReductionMax, FilterPoint, MipNone}, "max, point, no mip");
Run(*device, {ReductionMin, FilterAnisoBilinear, MipPoint, Aniso2x}, "min, 2x anisotropic bilinear, point mip");
Run(*device, {ReductionMax, FilterAnisoBilinear, MipPoint, Aniso2x}, "max, 2x anisotropic bilinear, point mip");
Gather(*device, {ReductionMin, FilterBilinear, MipPoint}, "gather4_lz, min");
Gather(*device, {ReductionMax, FilterBilinear, MipPoint}, "gather4_lz, max");
RunWithColors(*device, loaded, LoadedCode, false, "min 32_FLOAT, image_load 8_8_8_8");
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
- [Local programs](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) that the hull header leaves at address 0 are prepared at their first draw, one artifact per tessellation configuration, instead of at registration.
- [Packed 16-bit textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) - `5_6_5`, `1_5_5_5` and `4_4_4_4` are viewed as R5G6B5, A1R5G5B5 and B4G4R4A4 (the 4-bit format every Vulkan device can sample; R4G4B4A4 is optional and lavapipe lacks it) with a swizzle that reads X from the low bits, and a DST_SEL of the missing W of `5_6_5` reads X. Measured on RDNA2 (gfx1035), not on the console: through `image_load` and `image_sample_lz`, X, Y, Z (and W of `1_5_5_5` and `4_4_4_4`) come from the low, middle and high fields, and W of `5_6_5` returns X (the low 5 bits), not 1.
- [Min/max S# reduction](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) - `FILTER_MODE` 1 and 2 use Vulkan's min and max reduction (the same `SQ_IMG_FILTER_MODE` values RADV programs). Measured on RDNA2 (gfx1035), not on the console: only texels of non-zero weight are reduced, and the weight is taken after rounding to the 8-bit subtexel grid (a sample within 1/512 texel of a centre reduces that texel alone); a driver with other `subTexelPrecisionBits` reduces a different set there. Gathers ignore the mode.
- [Min/max S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver, shader recompiler) - throws with anisotropy, a linear mip filter, depth or color-texture compare, or bilinear filtering of an image the recompiler point-samples (sint, converted, depth-bits).
- [Min/max S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver, shader recompiler) - throws with a linear mip filter, depth or color-texture compare, or bilinear or anisotropic filtering of an image the recompiler point-samples (sint, converted, depth-bits). With anisotropy the host sampler reduces over its own footprint; [RADV](https://github.com/openbsd/xenocara/blob/1b39033df8d928ec247ca64448c4a63c72302db7/lib/mesa/src/amd/vulkan/radv_sampler.c#L185-L232) programs the same S# for such a Vulkan sampler. Measured on RDNA2 (gfx1030 in [#5156](https://github.com/boykopovar/AnyPS5/pull/5156#issuecomment-6155265616), gfx1035 in #5821), not on the console: without derivatives (`image_sample_lz`), min or max with anisotropic bilinear returns the bilinear minimum or maximum and with anisotropic point the point result, at every ratio field 0 to 4; with derivatives (`image_sample_d`, a 4:1 footprint), anisotropic bilinear at ratio field 2 or 4 reduces over the texels of all its anisotropic taps (the minimum or maximum along the major axis), not one bilinear footprint. A host that is not AMD reduces over its own anisotropic footprint, and Vulkan leaves nearest filtering with anisotropy to the host driver.
- [FORCE_DEGAMMA](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp) (libSceAgcDriver) - an S# that forces sRGB decoding adds nothing to an sRGB texture, as in shadPS4 (PS4). Measured on RDNA2 (gfx1035), not on the console: `8`, `8_8` and `8_8_8_8` sRGB textures sample bit for bit the same with and without the bit (point and bilinear; BC formats not measured). Other textures throw; on the same hardware their UNORM `8`, `8_8` and `8_8_8_8` forms sample bit for bit as the sRGB format (decoded before filtering).
- [Tessellation factor ring](../../../core/libs/prx/libSceAgcDriver/State/src/Configuration.cpp) (libSceAgcDriver) - `sceAgcDriverSetTFRing` records the ring and `sceAgcDriverGetTFRing` reports it. Before a title sets one, it reports the console's default, 0xFF0000000 and 128 KiB, as measured in [PS5_Vulkan's hardware findings](https://github.com/mihawk-99/PS5_Vulkan/blob/fde9e3798c2ec1d8fccc90d5067cd6060490607f/docs/HARDWARE_FINDINGS.md). Nothing is written to the recorded ring: tessellated draws run as Vulkan tessellation stages, which keep their factors on the host, so a title that reads tessellation factors from the ring finds none. A null base or a zero size throws; [ps5-opengl's AGC runtime](https://github.com/blackbearreloaded/ps5-opengl/blob/67c873f985eaefe0a72f713529e5632eaa01139c/src/platform/ps5_agc_native_runtime.c) calls `sceAgcDriverSetTFRing(0, 0)` in its cleanup, and what that does on the console is unknown.
- [VertexBufferReadSize](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) and the [vertex staging copy](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's `num_records` no longer throws for `OOB_SELECT` 0 and 1. The copy still spans the whole fetch, but the bytes from `num_records * stride` onward read as zeros, so the out-of-range vertex is `(0, 0, 0, 0)`. Measured with `buffer_load_format_xyzw` (`idxen`) through the [hardware oracle](../HW_ORACLE.md) on RDNA2 gfx1036 for PPSA01325's word 3 `0x0004dfac` (`OOB_SELECT` 0) in #2241: indices 3, 4 and 5 over 3 records each return `00000000 00000000 00000000 00000000`, W included, unlike Vulkan `robustBufferAccess`, which returns `(0, 0, 0, 1)`. The zeros come from the record check, not from memory: stored values placed past the records are ignored, and a `num_records` 6 control returns them. `RESOURCE_LEVEL` 1, `OOB_SELECT` 1 and FORMAT 74 `32_32_32_FLOAT` give the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured. `OOB_SELECT` 2 returns the stored values past the records and `OOB_SELECT` 3 compares `num_records` against the byte offset, both measured on the same setup in #2241; the two keep throwing past `num_records` here, since each needs its own rule. Zero-stride descriptors (`stride` 0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read.
Expand Down
Loading