#include "GraphicsTests.hpp" #include "Optimization/DescriptorBindingBuilder.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" #include #include #include #include #include #include #include namespace { using namespace AgcDriver::Graphics; struct Fields { std::uint32_t clampX = 2; std::uint32_t clampY = 2; std::uint32_t clampZ = 2; std::uint32_t maxAnisoRatio = 0; std::uint32_t depthCompareFunc = 0; bool forceUnormCoords = false; std::uint32_t anisoThreshold = 0; bool forceSrgb = false; std::uint32_t anisoBias = 0; bool truncCoord = false; bool disableCubeWrap = false; std::uint32_t filterMode = 0; bool disableDegamma = false; std::uint32_t minLodRaw = 0; std::uint32_t maxLodRaw = 0xc00; std::uint32_t perfMip = 0; std::uint32_t perfZ = 0; std::uint32_t lodBiasRaw = 0; std::uint32_t lodBiasSec = 0; std::uint32_t xyMagFilter = 1; std::uint32_t xyMinFilter = 1; std::uint32_t zFilter = 1; std::uint32_t mipFilter = 2; bool pointPreclamp = false; bool anisoOverride = false; bool blendZeroPrt = false; std::uint32_t borderColorType = 0; }; std::array pack(const Fields& f) { std::array words{}; words[0] = (f.clampX & 0x7u) | ((f.clampY & 0x7u) << 3u) | ((f.clampZ & 0x7u) << 6u) | ((f.maxAnisoRatio & 0x7u) << 9u) | ((f.depthCompareFunc & 0x7u) << 12u) | ((f.forceUnormCoords ? 1u : 0u) << 15u) | ((f.anisoThreshold & 0x7u) << 16u) | ((f.forceSrgb ? 1u : 0u) << 20u) | ((f.anisoBias & 0x3fu) << 21u) | ((f.truncCoord ? 1u : 0u) << 27u) | ((f.disableCubeWrap ? 1u : 0u) << 28u) | ((f.filterMode & 0x3u) << 29u) | ((f.disableDegamma ? 1u : 0u) << 31u); words[1] = (f.minLodRaw & 0xfffu) | ((f.maxLodRaw & 0xfffu) << 12u) | ((f.perfMip & 0xfu) << 24u) | ((f.perfZ & 0xfu) << 28u); words[2] = (f.lodBiasRaw & 0x3fffu) | ((f.lodBiasSec & 0x3fu) << 14u) | ((f.xyMagFilter & 0x3u) << 20u) | ((f.xyMinFilter & 0x3u) << 22u) | ((f.zFilter & 0x3u) << 24u) | ((f.mipFilter & 0x3u) << 26u) | ((f.pointPreclamp ? 1u : 0u) << 28u) | ((f.anisoOverride ? 1u : 0u) << 29u) | ((f.blendZeroPrt ? 1u : 0u) << 30u); words[3] = (f.borderColorType & 0x3u) << 30u; return words; } template void reject(TAction action, std::string_view reason) { try { action(); } catch (const std::runtime_error& error) { Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected guest sampler test error: ") + error.what()); return; } throw std::runtime_error(std::string("expected guest sampler rejection: ") + std::string(reason)); } void rejectFields(const Fields& f, std::string_view reason) { const auto words = pack(f); reject([&] { DecodeSamplerResource(words); }, reason); } void rejectUnnormalized(const Fields& f, std::string_view reason) { const auto words = pack(f); reject([&] { DecodeSamplerResource(words, true); }, reason); } void requireUnnormalized(const GuestSamplerResource& result, const char* what) { Require(result.unnormalizedCoordinates, std::string(what) + ": unnormalized coordinates were not decoded"); Require(result.mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST && result.minLod == 0.0f && result.maxLod == 0.0f, std::string(what) + ": unnormalized coordinates need nearest mips and a zero LOD range"); Require(result.lodBias == 0.0f && !result.anisotropyEnable && result.maxAnisotropy == 1.0f, std::string(what) + ": unnormalized coordinates need no LOD bias and no anisotropy"); } bool nearlyEqual(float a, float b) { return std::fabs(a - b) < 0.001f; } struct SamplerCapture { bool created = false; const void* next = nullptr; VkSamplerReductionMode reductionMode = VK_SAMPLER_REDUCTION_MODE_MAX_ENUM; VkSamplerCreateFlags flags = 0; }; SamplerCapture samplerCapture; template THandle fakeHandle(std::uint64_t value) { if constexpr (std::is_pointer_v) return reinterpret_cast(static_cast(value)); else return static_cast(value); } VKAPI_ATTR VkResult VKAPI_CALL captureCreateSampler(VkDevice, const VkSamplerCreateInfo* info, const VkAllocationCallbacks*, VkSampler* sampler) { samplerCapture.created = true; samplerCapture.next = info->pNext; samplerCapture.flags = info->flags; const auto* reduction = static_cast(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(0x5a); return VK_SUCCESS; } VKAPI_ATTR void VKAPI_CALL captureDestroySampler(VkDevice, VkSampler, const VkAllocationCallbacks*) {} PFN_vkVoidFunction VKAPI_CALL captureProc(VkDevice, const char* name) { if (std::strcmp(name, "vkCreateSampler") == 0) return reinterpret_cast(captureCreateSampler); if (std::strcmp(name, "vkDestroySampler") == 0) return reinterpret_cast(captureDestroySampler); return nullptr; } SamplerCapture createSampler(const Context& context, const GuestSamplerResource& resource) { samplerCapture = {}; const Sampler sampler(context, resource); Require(sampler.Handle() != VK_NULL_HANDLE, "sampler test did not create a sampler"); return samplerCapture; } void RunSamplerReductionTests(const Fields& base) { Context context{}; context.limits.maxSamplerAnisotropy = 1.0f; context.deviceProc = captureProc; context.samplerFilterMinmax = true; const std::array capturedSampler{0x20000092u, 0x00fff000u, 0x05500000u, 0u}; const auto captured = DecodeSamplerResource(capturedSampler); Require(captured.reductionMode == VK_SAMPLER_REDUCTION_MODE_MIN_EXT, "captured min-reduction sampler decoded to another reduction mode"); Require(captured.magFilter == VK_FILTER_LINEAR && captured.minFilter == VK_FILTER_LINEAR && captured.mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST, "captured min-reduction sampler filters decoded incorrectly"); Require(captured.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && captured.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && captured.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "captured min-reduction sampler address modes decoded incorrectly"); Require(!captured.anisotropyEnable && captured.compareOp == VK_COMPARE_OP_NEVER && nearlyEqual(captured.maxLod, 4095.0f / 256.0f), "captured min-reduction sampler fields decoded incorrectly"); const std::array modes{VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT, VK_SAMPLER_REDUCTION_MODE_MIN_EXT, VK_SAMPLER_REDUCTION_MODE_MAX_EXT}; for (std::uint32_t mode = 0; mode < modes.size(); ++mode) { for (const std::uint32_t filter : {0u, 1u}) { Fields reduced = base; reduced.filterMode = mode; reduced.xyMagFilter = filter; reduced.xyMinFilter = filter; reduced.mipFilter = 1; const auto decoded = DecodeSamplerResource(pack(reduced)); Require(decoded.reductionMode == modes.at(mode), "sampler reduction mode decoded incorrectly"); const auto created = createSampler(context, decoded); Require(created.created, "sampler test did not reach vkCreateSampler"); if (mode == 0u) Require(created.next == nullptr, "a weighted-average sampler chained a reduction mode"); else Require(created.reductionMode == modes.at(mode), "a min or max sampler did not chain its reduction mode"); const Sampler sampler(context, decoded); Require(sampler.RequiresFilterMinmax() == (mode != 0u && filter == 1u), "sampler min/max format requirement is wrong"); } } Fields noMip = base; noMip.filterMode = 2; noMip.mipFilter = 0; Require(DecodeSamplerResource(pack(noMip)).reductionMode == VK_SAMPLER_REDUCTION_MODE_MAX_EXT, "a max sampler without mip filtering was not decoded"); Fields linearMip = base; linearMip.filterMode = 1; rejectFields(linearMip, "min or max reduction with a linear mip filter"); linearMip.xyMagFilter = 0; linearMip.xyMinFilter = 0; rejectFields(linearMip, "min or max reduction with a linear mip filter"); Fields badFilterMode = 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"); anisoReduction.filterMode = 0; Require(DecodeSamplerResource(pack(anisoReduction)).anisotropyEnable, "anisotropic weighted-average sampler was rejected"); auto compared = captured; compared.compareEnable = true; reject([&] { createSampler(context, compared); }, "min or max reduction with depth comparison"); auto weightedCompare = DecodeSamplerResource(pack(base)); weightedCompare.compareEnable = true; Require(createSampler(context, weightedCompare).created, "weighted-average comparison sampler was rejected"); context.samplerFilterMinmax = false; reject([&] { createSampler(context, captured); }, "min or max reduction which the device does not support"); Require(createSampler(context, DecodeSamplerResource(pack(base))).created, "weighted-average sampler needs min/max support"); context.formatProperties = [](VkPhysicalDevice, VkFormat format, VkFormatProperties* properties) { *properties = {}; if (format == VK_FORMAT_R32_SFLOAT) properties->optimalTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT; if (format == VK_FORMAT_R32G32_SFLOAT) properties->optimalTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; if (format == VK_FORMAT_R8G8B8A8_UNORM) properties->linearTilingFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT; }; context.samplerFilterMinmax = true; Fields minPoint = base; minPoint.filterMode = 1; minPoint.xyMagFilter = 0; minPoint.xyMinFilter = 0; minPoint.mipFilter = 1; const std::array samplers{std::make_shared(context, DecodeSamplerResource(pack(base))), std::make_shared(context, captured), std::make_shared(context, DecodeSamplerResource(pack(minPoint)))}; RequireFilterMinmax(context, VK_FORMAT_R32_SFLOAT, 0b010u, samplers); RequireFilterMinmax(context, VK_FORMAT_R8G8B8A8_UNORM, 0u, samplers); RequireFilterMinmax(context, VK_FORMAT_R8G8B8A8_UNORM, 0b101u, samplers); reject([&] { RequireFilterMinmax(context, VK_FORMAT_R8G8B8A8_UNORM, 0b010u, samplers); }, "format 37 does not support min/max filtering is sampled through a min or max reduction sampler"); reject([&] { RequireFilterMinmax(context, VK_FORMAT_R32G32_SFLOAT, 0b011u, samplers); }, "does not support min/max filtering"); reject([&] { RequireFilterMinmax(context, VK_FORMAT_R32_SFLOAT, 0b1000u, samplers); }, "sampler element 3, which its shader does not bind"); const auto borderSampler = [&](std::uint32_t clamp, std::uint32_t borderColorType) { Fields fields = base; fields.clampY = clamp; fields.borderColorType = borderColorType; return std::make_shared(context, DecodeSamplerResource(pack(fields))); }; const std::array borderSamplers{borderSampler(6u, 0u), borderSampler(6u, 1u), borderSampler(6u, 2u), borderSampler(2u, 1u)}; Require(!borderSamplers[0]->ReadsOpaqueBlackBorder() && borderSamplers[1]->ReadsOpaqueBlackBorder() && !borderSamplers[2]->ReadsOpaqueBlackBorder() && !borderSamplers[3]->ReadsOpaqueBlackBorder(), "opaque black border detection is wrong"); for (std::uint32_t swizzle = 0; swizzle <= 5u; ++swizzle) RequireBorderSwizzle(swizzle, 0b1101u, borderSamplers); RequireBorderSwizzle(0u, 0b0010u, borderSamplers); RequireBorderSwizzle(4u, 0b0010u, borderSamplers); for (std::uint32_t swizzle : {1u, 2u, 3u, 5u}) reject([&] { RequireBorderSwizzle(swizzle, 0b0010u, borderSamplers); }, "moves the alpha channel is sampled through an opaque black border"); reject([&] { RequireBorderSwizzle(1u, 0b10000u, borderSamplers); }, "sampler element 4, which its shader does not bind"); const auto pointFiltered = [](const std::array& words) { return ShaderRecompiler::PointFilteredSamplerWord(words[0], words[2]); }; Require(pointFiltered(pack(base)) == 0x05000000u, "a point-filtered weighted-average sampler kept its bilinear or linear mip filter"); Require(pointFiltered(pack(minPoint)) == pack(minPoint)[2], "a point-filtered min sampler that already point-samples changed"); reject([&] { pointFiltered(capturedSampler); }, "needs point filtering"); Fields maxLinearMip = minPoint; maxLinearMip.filterMode = 2; maxLinearMip.mipFilter = 2; reject([&] { pointFiltered(pack(maxLinearMip)); }, "needs point filtering"); } void RunNonSeamlessCubeTests(const Fields& base) { Fields nonSeamless = base; nonSeamless.disableCubeWrap = true; const auto decoded = DecodeSamplerResource(pack(nonSeamless)); Require(decoded.nonSeamlessCube, "DISABLE_CUBE_WRAP was not decoded as a non-seamless cube sampler"); Require(!DecodeSamplerResource(pack(base)).nonSeamlessCube, "a sampler without DISABLE_CUBE_WRAP decoded as non-seamless"); Context context{}; context.limits.maxSamplerAnisotropy = 1.0f; context.deviceProc = captureProc; context.nonSeamlessCubeMap = true; Require((createSampler(context, decoded).flags & VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT) != 0u, "a non-seamless cube sampler was created without VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT"); Require(createSampler(context, DecodeSamplerResource(pack(base))).flags == 0u, "a seamless sampler was created with flags"); const std::array honored{std::make_shared(context, decoded)}; Require(!honored[0]->RequiresNonSeamlessCube(), "a sampler created with the non-seamless flag still restricts cube textures"); RequireNonSeamlessCube(true, 0b1u, honored); context.nonSeamlessCubeMap = false; Require(createSampler(context, decoded).flags == 0u, "a sampler set the non-seamless flag without the device feature"); const std::array unsupported{std::make_shared(context, DecodeSamplerResource(pack(base))), std::make_shared(context, decoded)}; Require(unsupported[1]->RequiresNonSeamlessCube(), "a non-seamless sampler without the device feature does not restrict cube textures"); RequireNonSeamlessCube(false, 0b11u, unsupported); RequireNonSeamlessCube(true, 0b01u, unsupported); reject([&] { RequireNonSeamlessCube(true, 0b10u, unsupported); }, "disables seamless cube filtering, which needs VK_EXT_non_seamless_cube_map"); } void RunSamplerCacheDegammaTests(const Fields& base) { Context context{}; context.limits.maxSamplerAnisotropy = 1.0f; context.deviceProc = captureProc; Fields forcedSrgb = base; forcedSrgb.forceSrgb = true; const auto words = pack(forcedSrgb); SamplerCache unpaired; reject([&] { unpaired.Get(context, words, false); }, "forces sRGB decoding"); SamplerCache cache; const auto paired = cache.Get(context, words, false, false, true); Require(paired->ForcesDegamma() && cache.Get(context, words, false, false, true) == paired && cache.Misses() == 1u, "a paired FORCE_DEGAMMA sampler must be created once and then served from the cache"); reject([&] { cache.Get(context, words, false); }, "forces sRGB decoding"); } } void RunGuestSamplerResourceTests() { Fields base; auto result = DecodeSamplerResource(pack(base)); Require(result.magFilter == VK_FILTER_LINEAR && result.minFilter == VK_FILTER_LINEAR, "linear filter fields decoded incorrectly"); Require(result.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR, "linear mip filter must decode to linear mipmap mode"); Require(result.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "clamp mode 2 must decode to clamp-to-edge"); Require(!result.anisotropyEnable && result.maxAnisotropy == 1.0f, "non-anisotropic filter must leave anisotropy disabled"); Require(nearlyEqual(result.maxLod, 12.0f), "max LOD decoded incorrectly"); Require(result.borderColor == VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, "border color type 0 must decode to transparent black"); Fields nearest = base; nearest.xyMagFilter = 0; nearest.xyMinFilter = 0; nearest.zFilter = 0; nearest.mipFilter = 0; result = DecodeSamplerResource(pack(nearest)); Require(result.magFilter == VK_FILTER_NEAREST && result.minFilter == VK_FILTER_NEAREST, "filter 0 must decode to nearest"); Require(result.mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST, "mip filter 0 must decode to nearest mipmap mode"); Require(result.minLod == 0.0f && result.maxLod == 0.0f, "disabled mip filtering must clear LOD range"); Fields addressModes = base; addressModes.clampX = 0; addressModes.clampY = 1; addressModes.clampZ = 4; result = DecodeSamplerResource(pack(addressModes)); Require(result.addressModeU == VK_SAMPLER_ADDRESS_MODE_REPEAT, "clamp mode 0 must decode to repeat"); Require(result.addressModeV == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, "clamp mode 1 must decode to mirrored repeat"); Require(result.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "clamp mode 4 must decode to clamp-to-border"); Fields aniso = base; aniso.xyMagFilter = 2; aniso.xyMinFilter = 2; aniso.zFilter = 2; aniso.maxAnisoRatio = 3; result = DecodeSamplerResource(pack(aniso)); Require(result.anisotropyEnable, "anisotropic filter must enable anisotropy"); Require(nearlyEqual(result.maxAnisotropy, 8.0f), "anisotropy ratio 3 must decode to 8x"); aniso.maxAnisoRatio = 5; rejectFields(aniso, "unknown anisotropy ratio"); Fields badLod = base; badLod.minLodRaw = 100; badLod.maxLodRaw = 50; rejectFields(badLod, "minimum LOD past its maximum LOD"); const std::array capturedSampler{0u, 0x00fff000u, 0x09000000u, 0u}; const auto captured = DecodeSamplerResource(capturedSampler); Require(captured.magFilter == VK_FILTER_NEAREST && captured.minFilter == VK_FILTER_NEAREST, "captured 2D sampler filters decoded incorrectly"); Require(captured.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR && nearlyEqual(captured.maxLod, 4095.0f / 256.0f), "captured 2D sampler mip settings decoded incorrectly"); Require(captured.addressModeU == VK_SAMPLER_ADDRESS_MODE_REPEAT && captured.addressModeV == VK_SAMPLER_ADDRESS_MODE_REPEAT, "captured 2D sampler address modes decoded incorrectly"); for (std::uint32_t zFilter = 0; zFilter < 4; ++zFilter) { Fields twoDimensional = base; twoDimensional.zFilter = zFilter; const auto decoded = DecodeSamplerResource(pack(twoDimensional)); Require(decoded.magFilter == VK_FILTER_LINEAR && decoded.minFilter == VK_FILTER_LINEAR, "Z filter changed 2D filtering"); } Fields badMipFilter = base; badMipFilter.mipFilter = 3; rejectFields(badMipFilter, "unknown mip filter"); const std::array compareOps{VK_COMPARE_OP_NEVER, VK_COMPARE_OP_LESS, VK_COMPARE_OP_EQUAL, VK_COMPARE_OP_LESS_OR_EQUAL, VK_COMPARE_OP_GREATER, VK_COMPARE_OP_NOT_EQUAL, VK_COMPARE_OP_GREATER_OR_EQUAL, VK_COMPARE_OP_ALWAYS}; for (std::uint32_t function = 0; function < compareOps.size(); ++function) { Fields comparison = base; comparison.depthCompareFunc = function; const auto decoded = DecodeSamplerResource(pack(comparison)); Require(decoded.compareOp == compareOps.at(function), "sampler depth comparison function decoded incorrectly"); Require(!decoded.compareEnable, "sampler descriptor enabled comparison without shader metadata"); } Fields badUnorm = base; badUnorm.forceUnormCoords = true; rejectFields(badUnorm, "unnormalized coordinates"); const std::array capturedUnnormalized{0x00008092u, 0x00fff000u, 0x05500000u, 0u}; reject([&] { DecodeSamplerResource(capturedUnnormalized); }, "uses unnormalized coordinates which are not implemented"); const auto unnormalized = DecodeSamplerResource(capturedUnnormalized, true); requireUnnormalized(unnormalized, "captured unnormalized S#"); Require(unnormalized.magFilter == VK_FILTER_LINEAR && unnormalized.minFilter == VK_FILTER_LINEAR, "captured unnormalized S# filters decoded incorrectly"); Require(unnormalized.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && unnormalized.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "captured unnormalized S# clamp modes decoded incorrectly"); Require(unnormalized.borderColor == VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, "captured unnormalized S# border color decoded incorrectly"); Require(!DecodeSamplerResource(pack(base)).unnormalizedCoordinates, "a normalized S# decoded to unnormalized coordinates"); Fields unnormalizedBase = base; unnormalizedBase.forceUnormCoords = true; Fields unnormalizedPoint = unnormalizedBase; unnormalizedPoint.xyMagFilter = 0; unnormalizedPoint.xyMinFilter = 0; const auto point = DecodeSamplerResource(pack(unnormalizedPoint), true); requireUnnormalized(point, "unnormalized point S#"); Require(point.magFilter == VK_FILTER_NEAREST && point.minFilter == VK_FILTER_NEAREST, "unnormalized point S# filters decoded incorrectly"); const std::array borders{VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE}; for (std::uint32_t type = 0; type < borders.size(); ++type) { Fields border = unnormalizedBase; border.clampX = 6; border.clampY = 6; border.borderColorType = type; const auto decoded = DecodeSamplerResource(pack(border), true); requireUnnormalized(decoded, "unnormalized clamp-to-border S#"); Require(decoded.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && decoded.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER && decoded.borderColor == borders[type], "unnormalized clamp-to-border S# decoded incorrectly"); } for (std::uint32_t mipFilter = 0; mipFilter < 3; ++mipFilter) { Fields mips = unnormalizedBase; mips.mipFilter = mipFilter; requireUnnormalized(DecodeSamplerResource(pack(mips), true), "unnormalized S# mip filter"); } Fields unnormalizedBias = unnormalizedBase; unnormalizedBias.lodBiasRaw = 256; requireUnnormalized(DecodeSamplerResource(pack(unnormalizedBias), true), "unnormalized S# with a LOD bias"); Fields unnormalizedMinLod = unnormalizedBase; unnormalizedMinLod.minLodRaw = 0x100; unnormalizedMinLod.maxLodRaw = 0x400; requireUnnormalized(DecodeSamplerResource(pack(unnormalizedMinLod), true), "unnormalized S# with a minimum LOD"); Fields unequalFilters = unnormalizedBase; unequalFilters.xyMinFilter = 0; rejectUnnormalized(unequalFilters, "unnormalized coordinates with different minification and magnification filters"); Fields unnormalizedAniso = unnormalizedBase; unnormalizedAniso.xyMagFilter = 2; unnormalizedAniso.xyMinFilter = 2; rejectUnnormalized(unnormalizedAniso, "unnormalized coordinates with anisotropic filtering"); for (const std::uint32_t clamp : {0u, 1u, 3u, 4u, 5u, 7u}) { Fields clampX = unnormalizedBase; clampX.clampX = clamp; rejectUnnormalized(clampX, "unnormalized coordinates with clamp mode " + std::to_string(clamp) + " on X"); } Fields clampY = unnormalizedBase; clampY.clampY = 0; rejectUnnormalized(clampY, "unnormalized coordinates with clamp mode 0 on Y"); Fields unnormalizedTruncated = unnormalizedBase; unnormalizedTruncated.truncCoord = true; rejectUnnormalized(unnormalizedTruncated, "unnormalized coordinates with TRUNC_COORD"); auto truncatedBlend = pack(unnormalizedBase); truncatedBlend[0] |= 1u << 19u; reject([&] { DecodeSamplerResource(truncatedBlend, true); }, "unnormalized coordinates with MC_COORD_TRUNC"); rejectUnnormalized(base, "bound as unnormalized without FORCE_UNNORMALIZED"); Fields forcedSrgb = base; forcedSrgb.forceSrgb = true; rejectFields(forcedSrgb, "forces sRGB decoding"); const auto forced = DecodeSamplerResource(pack(forcedSrgb), false, true); const auto plain = DecodeSamplerResource(pack(base)); Require(forced.forceDegamma && !plain.forceDegamma, "FORCE_DEGAMMA must be decoded into forceDegamma"); Require(forced.magFilter == plain.magFilter && forced.minFilter == plain.minFilter && forced.mipmapMode == plain.mipmapMode && forced.maxLod == plain.maxLod, "FORCE_DEGAMMA must not change the host sampler state"); RunSamplerCacheDegammaTests(base); Fields truncated = base; truncated.truncCoord = true; truncated.perfMip = 1; truncated.perfZ = 1; truncated.anisoThreshold = 1; truncated.anisoBias = 1; static_cast(DecodeSamplerResource(pack(truncated))); RunNonSeamlessCubeTests(base); RunSamplerReductionTests(base); Fields badDegamma = base; badDegamma.disableDegamma = true; rejectFields(badDegamma, "disables degamma"); Fields badLodBiasSec = base; badLodBiasSec.lodBiasSec = 1; rejectFields(badLodBiasSec, "secondary LOD bias"); Fields badPreclamp = base; badPreclamp.pointPreclamp = true; rejectFields(badPreclamp, "point preclamping"); Fields anisoOverride = base; anisoOverride.xyMagFilter = 2; anisoOverride.xyMinFilter = 2; anisoOverride.zFilter = 2; anisoOverride.maxAnisoRatio = 3; const auto withoutOverride = DecodeSamplerResource(pack(anisoOverride)); anisoOverride.anisoOverride = true; const auto withOverride = DecodeSamplerResource(pack(anisoOverride)); Require(withOverride.anisotropyEnable == withoutOverride.anisotropyEnable && withOverride.maxAnisotropy == withoutOverride.maxAnisotropy && withOverride.magFilter == withoutOverride.magFilter && withOverride.minFilter == withoutOverride.minFilter, "ANISO_OVERRIDE changed the decoded sampler"); const auto overrideWords = pack(anisoOverride); Require(!SingleLevelSamplerWords(overrideWords, false, true).has_value(), "ANISO_OVERRIDE changed a sampler of mipmapped images"); Require(!SingleLevelSamplerWords(overrideWords, false, false).has_value(), "ANISO_OVERRIDE changed a sampler with no paired image"); const auto singleLevel = SingleLevelSamplerWords(overrideWords, true, false); Require(singleLevel.has_value() && (*singleLevel)[0] == overrideWords[0] && (*singleLevel)[1] == overrideWords[1] && (*singleLevel)[3] == overrideWords[3], "ANISO_OVERRIDE did not keep the sampler's other words"); const auto singleLevelSampler = DecodeSamplerResource(*singleLevel); Require(!singleLevelSampler.anisotropyEnable && singleLevelSampler.maxAnisotropy == 1.0f && singleLevelSampler.magFilter == withOverride.magFilter && singleLevelSampler.minFilter == withOverride.minFilter && singleLevelSampler.mipmapMode == withOverride.mipmapMode, "ANISO_OVERRIDE left anisotropy on a single-level image's sampler"); reject([&] { SingleLevelSamplerWords(overrideWords, true, true); }, "both single-level and mipmapped"); Require(!SingleLevelSamplerWords(pack(base), true, false).has_value(), "a sampler without ANISO_OVERRIDE changed"); Fields linearOverride = base; linearOverride.anisoOverride = true; Require(!SingleLevelSamplerWords(pack(linearOverride), true, true).has_value(), "ANISO_OVERRIDE changed a sampler without anisotropy"); anisoOverride.xyMagFilter = 3; anisoOverride.xyMinFilter = 3; const auto linearPlain = DecodeSamplerResource(*SingleLevelSamplerWords(pack(anisoOverride), true, false)); Require(!linearPlain.anisotropyEnable && linearPlain.magFilter == VK_FILTER_LINEAR && linearPlain.minFilter == VK_FILTER_LINEAR, "ANISO_OVERRIDE did not keep linear filtering"); Fields badBlendZero = base; badBlendZero.blendZeroPrt = true; rejectFields(badBlendZero, "PRT blend-zero"); Fields unusedTable = base; unusedTable.borderColorType = 3; const auto unread = DecodeSamplerResource(pack(unusedTable)); Require(unread.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && unread.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && unread.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "a table border colour with clamp-to-edge addressing must decode"); for (std::uint32_t mode : {0u, 1u, 2u, 3u}) { Fields unreadMode = base; unreadMode.borderColorType = 3; unreadMode.clampX = mode; DecodeSamplerResource(pack(unreadMode)); } for (std::uint32_t mode : {4u, 5u, 6u, 7u}) { for (int axis = 0; axis < 3; ++axis) { Fields readTable = base; readTable.borderColorType = 3; (axis == 0 ? readTable.clampX : axis == 1 ? readTable.clampY : readTable.clampZ) = mode; rejectFields(readTable, "border color table"); } } const std::array capturedTable{0x00007092u, 0x00fff000u, 0x05000000u, 0xc0000000u}; const auto capturedUnread = DecodeSamplerResource(capturedTable); Require(capturedUnread.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && capturedUnread.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "captured table-border sampler decoded incorrectly"); Fields opaqueBlack = base; opaqueBlack.borderColorType = 1; Require(DecodeSamplerResource(pack(opaqueBlack)).borderColor == VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, "border color type 1 must decode to opaque black"); Require(DecodeSamplerResource(pack(opaqueBlack), false, false, true).borderColor == VK_BORDER_COLOR_INT_OPAQUE_BLACK, "border color type 1 of a sampler for integer images must decode to integer opaque black"); Fields opaqueWhite = base; opaqueWhite.borderColorType = 2; Require(DecodeSamplerResource(pack(opaqueWhite)).borderColor == VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, "border color type 2 must decode to opaque white"); Require(DecodeSamplerResource(pack(opaqueWhite), false, false, true).borderColor == VK_BORDER_COLOR_INT_OPAQUE_WHITE, "border color type 2 of a sampler for integer images must decode to integer opaque white"); Require(DecodeSamplerResource(pack(base), false, false, true).borderColor == VK_BORDER_COLOR_INT_TRANSPARENT_BLACK, "border color type 0 of a sampler for integer images must decode to integer transparent black"); Fields positiveBias = base; positiveBias.lodBiasRaw = 256; Require(nearlyEqual(DecodeSamplerResource(pack(positiveBias)).lodBias, 1.0f), "positive LOD bias decoded incorrectly"); Fields negativeBias = base; negativeBias.lodBiasRaw = static_cast(-256) & 0x3fffu; Require(nearlyEqual(DecodeSamplerResource(pack(negativeBias)).lodBias, -1.0f), "negative LOD bias decoded incorrectly"); std::array shortWords{}; reject([&] { DecodeSamplerResource(shortWords); }, "4 dwords"); }