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 @@ -24,12 +24,13 @@ struct GuestSamplerResource {
float lodBias;
VkBorderColor borderColor;
VkSamplerReductionMode reductionMode = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
bool forceDegamma = false;
bool compareEnable = false;
VkCompareOp compareOp = VK_COMPARE_OP_NEVER;
bool unnormalizedCoordinates = false;
};

GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words, bool unnormalizedProven = false);
GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words, bool unnormalizedProven = false, bool forceDegammaPaired = false);

}

Expand Down
5 changes: 4 additions & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ class Sampler {
Sampler(const Sampler&) = delete;
Sampler& operator=(const Sampler&) = delete;

bool ForcesDegamma() const;
VkSampler Handle() const;
bool RequiresFilterMinmax() const;

private:
void release() noexcept;

Context context;
bool forcesDegamma = false;
VkSampler sampler = VK_NULL_HANDLE;
bool requiresFilterMinmax = false;
};
Expand All @@ -41,7 +43,7 @@ class SamplerCache {
explicit SamplerCache(std::size_t capacity = 1024);
SamplerCache(const SamplerCache&) = delete;
SamplerCache& operator=(const SamplerCache&) = delete;
std::shared_ptr<Sampler> Get(const Context& context, std::span<const std::uint32_t> words, bool compareEnable, bool unnormalizedProven = false);
std::shared_ptr<Sampler> Get(const Context& context, std::span<const std::uint32_t> words, bool compareEnable, bool unnormalizedProven = false, bool forceDegammaPaired = false);
// APS5_PROFILE_DRAW counters: lookups served by an existing sampler, and samplers created.
std::uint64_t Hits() const { return hits; }
std::uint64_t Misses() const { return misses; }
Expand All @@ -60,6 +62,7 @@ class SamplerCache {
};

void RequireFilterMinmax(const Context& context, VkFormat format, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);
void RequireDegammaFormat(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct Context;
VkFormat ResolveTextureFormat(std::uint32_t guestFormat);
std::uint32_t SrgbDecodeFormats(PFN_vkGetPhysicalDeviceFormatProperties formatProperties, VkPhysicalDevice physical);
VkFormat SampledTextureFormat(const Context& context, std::uint32_t guestFormat);
bool IsSrgbTextureFormat(std::uint32_t guestFormat);
std::optional<std::uint32_t> FindGuestTextureFormat(VkFormat format, std::uint32_t elementBytes);
std::optional<std::uint32_t> FindGuestColorTargetFormat(VkFormat format, std::uint32_t elementBytes);
std::uint32_t BytesPerElement(std::uint32_t guestFormat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ float toSignedLodBias(std::uint32_t raw) {

}

GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words, bool unnormalizedProven) {
GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words, bool unnormalizedProven, bool forceDegammaPaired) {
Require(words.size() == 4, "guest sampler descriptor must contain 4 dwords");

const auto clampX = (words[0] >> 0u) & 0x7u;
Expand Down Expand Up @@ -99,7 +99,7 @@ GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words,
} else {
Require(!unnormalizedProven, "guest sampler descriptor is bound as unnormalized without FORCE_UNNORMALIZED");
}
Require(!forceSrgb, "guest sampler descriptor forces sRGB decoding which is not implemented");
Require(!forceSrgb || forceDegammaPaired, "guest sampler descriptor forces sRGB decoding which is not implemented");
// TRUNC_COORD picks point-sampled texels by truncation instead of rounding, and the perf fields
// trade mip/depth precision for speed; Vulkan's nearest filtering already floors, so these only
// move texel selection by half a texel at most and are accepted as is. ANISO_THRESHOLD and
Expand Down Expand Up @@ -161,6 +161,7 @@ GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words,
result.reductionMode = reductionMode;
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};
result.compareOp = compareOps.at(depthCompareFunc);
result.forceDegamma = forceSrgb;
if (forceUnormCoords) {
result.unnormalizedCoordinates = true;
result.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
Expand Down
17 changes: 15 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include <algorithm>
#include <cstdlib>
#include <string>
Expand All @@ -10,6 +11,7 @@ namespace AgcDriver::Graphics {
Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit");
Require(descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias, "guest sampler descriptor requests a LOD bias beyond the device limit");
Require(!(descriptor.unnormalizedCoordinates && descriptor.compareEnable), "guest sampler descriptor with unnormalized coordinates enables depth comparison, which is not implemented");
forcesDegamma = descriptor.forceDegamma;

VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
VkSamplerReductionModeCreateInfoEXT reduction{VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, nullptr, descriptor.reductionMode};
Expand Down Expand Up @@ -55,18 +57,19 @@ namespace AgcDriver::Graphics {

SamplerCache::SamplerCache(std::size_t capacity) : capacity(std::max<std::size_t>(capacity, 1)) {}

std::shared_ptr<Sampler> SamplerCache::Get(const Context& context, std::span<const std::uint32_t> words, bool compareEnable, bool unnormalizedProven) {
std::shared_ptr<Sampler> SamplerCache::Get(const Context& context, std::span<const std::uint32_t> words, bool compareEnable, bool unnormalizedProven, bool forceDegammaPaired) {
Require(words.size() == 4, "guest sampler descriptor must contain 4 dwords");
const std::array<std::uint32_t, 5> key{words[0], words[1], words[2], words[3], (compareEnable ? 1u : 0u) | (unnormalizedProven ? 2u : 0u)};
std::lock_guard lock(mutex);
++clock;
if (const auto found = entries.find(key); found != entries.end()) {
Require(forceDegammaPaired || !found->second.sampler->ForcesDegamma(), "guest sampler descriptor forces sRGB decoding which is not implemented");
++hits;
found->second.lastUse = clock;
return found->second.sampler;
}
++misses;
auto resource = DecodeSamplerResource(words, unnormalizedProven);
auto resource = DecodeSamplerResource(words, unnormalizedProven, forceDegammaPaired);
resource.compareEnable = compareEnable;
auto sampler = std::make_shared<Sampler>(context, resource);
// The cap keeps live samplers well below the device's limit (NVIDIA: ~4000); a set in flight
Expand All @@ -92,4 +95,14 @@ namespace AgcDriver::Graphics {
if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT) == 0) Require(false, "a sampled texture whose format " + std::to_string(format) + " does not support min/max filtering is sampled through a min or max reduction sampler with linear filtering");
}

bool Sampler::ForcesDegamma() const {
return forcesDegamma;
}

void RequireDegammaFormat(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span<const std::shared_ptr<Sampler>> samplers) {
for (std::uint32_t element = 0; element < 32u && element < samplers.size(); ++element) {
if (((samplerMask >> element) & 1u) != 0u && samplers[element]->ForcesDegamma() && !IsSrgbTextureFormat(guestFormat)) Require(false, "a texture of guest format " + std::to_string(guestFormat) + ", which is not sRGB, is sampled through a sampler that forces sRGB decoding, which is not implemented");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2711,9 +2711,9 @@ void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding&
const bool unnormalized = element < binding.samplerUnnormalized.size() && binding.samplerUnnormalized[element];
static const bool noSamplerCache = std::getenv("APS5_NO_SAMPLER_CACHE") != nullptr;
if (context.samplerCache != nullptr && !noSamplerCache) {
samplers.push_back(context.samplerCache->Get(context, words, compareEnable, unnormalized));
samplers.push_back(context.samplerCache->Get(context, words, compareEnable, unnormalized, true));
} else {
auto resource = DecodeSamplerResource(words, unnormalized);
auto resource = DecodeSamplerResource(words, unnormalized, true);
resource.compareEnable = compareEnable;
samplers.push_back(std::make_shared<Sampler>(context, resource));
}
Expand Down Expand Up @@ -2866,6 +2866,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
continue;
}
const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words);
RequireDegammaFormat(resource.format, binding.imageSamplers[element], shaderSamplers);
const bool firstLayer = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k2DArray;
if (!firstLayer && !MatchesGuestDimension(*binding.imageShape, resource.dimension)) throw std::runtime_error("AGC graphics: guest texture dimension disagrees with the shader's declared image shape (shape " + std::to_string(static_cast<int>(*binding.imageShape)) + ", dimension " + std::to_string(static_cast<int>(resource.dimension)) + ")");
const VkComponentMapping components = ViewComponents(resource);
Expand Down
13 changes: 13 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ VkFormat SampledTextureFormat(const Context& context, std::uint32_t guestFormat)
return ResolveTextureFormat(static_cast<std::uint32_t>(ShaderRecompiler::SrgbUnormFormat(format)));
}

bool IsSrgbTextureFormat(std::uint32_t guestFormat) {
switch (findFormatEntry(guestFormat).vkFormat) {
case VK_FORMAT_R8_SRGB:
case VK_FORMAT_R8G8_SRGB:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
case VK_FORMAT_BC2_SRGB_BLOCK:
case VK_FORMAT_BC3_SRGB_BLOCK:
case VK_FORMAT_BC7_SRGB_BLOCK: return true;
default: return false;
}
}

std::optional<std::uint32_t> FindGuestTextureFormat(VkFormat format, std::uint32_t elementBytes) {
for (const auto& entry : kFormatLookup) {
if (!entry.blockCompressed && entry.vkFormat == format && entry.bytesPerElement == elementBytes) return entry.guestFormat;
Expand Down
26 changes: 23 additions & 3 deletions core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,21 @@ void RunSamplerReductionTests(const Fields& base) {
reject([&] { pointFiltered(pack(maxLinearMip)); }, "needs point filtering");
}

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() {
Expand Down Expand Up @@ -360,9 +375,14 @@ void RunGuestSamplerResourceTests() {
reject([&] { DecodeSamplerResource(truncatedBlend, true); }, "unnormalized coordinates with MC_COORD_TRUNC");
rejectUnnormalized(base, "bound as unnormalized without FORCE_UNNORMALIZED");

Fields badSrgb = base;
badSrgb.forceSrgb = true;
rejectFields(badSrgb, "forces sRGB decoding");
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;
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/TextureFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,7 @@ void RunTextureFormatTests() {
reject([] { ResolveTextureFormat(9999); }, "unsupported guest texture format");
reject([] { BytesPerElement(2); }, "unsupported guest texture format");
reject([] { IsBlockCompressed(200); }, "unsupported guest texture format");
for (const std::uint32_t format : {128u, 129u, 130u, 170u, 172u, 174u, 182u}) Require(IsSrgbTextureFormat(format), "guest format " + std::to_string(format) + " must be an sRGB texture format");
for (const std::uint32_t format : {1u, 14u, 56u, 71u, 169u, 171u, 173u, 181u}) Require(!IsSrgbTextureFormat(format), "guest format " + std::to_string(format) + " must not be an sRGB texture format");
convertedDccClearTests();
}
Loading
Loading