From 5fddd12b788d140dcf3c1037a18b023bb4fd5187 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 28 Sep 2026 00:33:15 +0400 Subject: [PATCH] renderer: fix sampler border colors --- .../host_gpu/renderer/cache/samplerCache.cpp | 18 +-- .../host_gpu/renderer/cache/samplerCache.h | 4 +- .../renderer/pipeline/descriptors.cpp | 9 +- src/graphics/shader/recompiler/ir/ShaderIR.h | 1 + .../ir/passes/ResourceMaterialization.cpp | 91 ++++++------ tests/ShaderRecompilerComputeTests.cpp | 130 +++++++++++++++--- 6 files changed, 175 insertions(+), 78 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/samplerCache.cpp b/src/graphics/host_gpu/renderer/cache/samplerCache.cpp index 8eeddbc47..259ec463e 100644 --- a/src/graphics/host_gpu/renderer/cache/samplerCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/samplerCache.cpp @@ -14,10 +14,10 @@ SamplerCache::~SamplerCache() { } } -vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) { +vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r, bool integer_border) { Common::LockGuard lock(m_mutex); - const SamplerKey key {r.fields[0], r.fields[1], r.fields[2], r.fields[3]}; + const SamplerKey key {r.fields[0], r.fields[1], r.fields[2], r.fields[3], integer_border}; if (auto iter = m_samplers.find(key); iter != m_samplers.end()) { return iter->second; } @@ -93,23 +93,23 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) { return vk::SamplerAddressMode::eClampToBorder; }; - vk::BorderColor border = vk::BorderColor::eIntTransparentBlack; + vk::BorderColor border = integer_border ? vk::BorderColor::eIntTransparentBlack + : vk::BorderColor::eFloatTransparentBlack; switch (static_cast(r.BorderColorType())) { - case Prospero::SamplerBorderColor::kTransBlack: - border = vk::BorderColor::eIntTransparentBlack; - break; + case Prospero::SamplerBorderColor::kTransBlack: break; case Prospero::SamplerBorderColor::kOpaqueBlack: - border = vk::BorderColor::eIntOpaqueBlack; + border = integer_border ? vk::BorderColor::eIntOpaqueBlack + : vk::BorderColor::eFloatOpaqueBlack; break; case Prospero::SamplerBorderColor::kOpaqueWhite: - border = vk::BorderColor::eIntOpaqueWhite; + border = integer_border ? vk::BorderColor::eIntOpaqueWhite + : vk::BorderColor::eFloatOpaqueWhite; break; case Prospero::SamplerBorderColor::kFromTable: LOGF( "temporary: approximating table border color as transparent black, index = %" PRIu16 "\n", r.BorderColorPtr()); - border = vk::BorderColor::eIntTransparentBlack; break; default: EXIT("unknown border color: %d", static_cast(r.BorderColorType())); } diff --git a/src/graphics/host_gpu/renderer/cache/samplerCache.h b/src/graphics/host_gpu/renderer/cache/samplerCache.h index 4121c29fb..9c8199136 100644 --- a/src/graphics/host_gpu/renderer/cache/samplerCache.h +++ b/src/graphics/host_gpu/renderer/cache/samplerCache.h @@ -24,10 +24,10 @@ public: ~SamplerCache(); KYTY_CLASS_NO_COPY(SamplerCache); - vk::Sampler GetSampler(const ShaderSamplerResource& r); + vk::Sampler GetSampler(const ShaderSamplerResource& r, bool integer_border); private: - using SamplerKey = std::array; + using SamplerKey = std::array; struct SamplerKeyHash { std::size_t operator()(const SamplerKey& key) const { diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index d986ddf19..cf13cbb9b 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -738,14 +738,15 @@ static vk::Sampler NativeSampler(RenderContext& context, const ShaderRecompiler::IR::CompiledShaderInfo& program, uint32_t index, const ShaderRecompiler::IR::DescriptorValue& value) { - auto descriptor = DecodeNativeDescriptor(value); - if (!program.info.samplers[index].depth_compare) { + auto descriptor = DecodeNativeDescriptor(value); + const auto& sampler = program.info.samplers[index]; + if (!sampler.depth_compare) { descriptor.fields[0] &= ~(0x7u << 12u); } - if (program.info.samplers[index].force_point_filtering) { + if (sampler.force_point_filtering) { descriptor.SetPointFiltering(); } - return context.GetSamplerCache().GetSampler(descriptor); + return context.GetSamplerCache().GetSampler(descriptor, sampler.integer_border); } static vk::DescriptorBufferInfo NativeUpload(RenderContext& context, diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index e9827605b..6c45aa72e 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -147,6 +147,7 @@ struct SamplerResource { uint32_t first_use_pc = 0; bool force_point_filtering = false; bool depth_compare = false; + bool integer_border = false; bool operator==(const SamplerResource& other) const = default; }; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index b6590f644..ebe2da6c0 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -107,14 +107,16 @@ Prospero::BufferFormat ImageConversionFormat(Prospero::BufferFormat format) { : Prospero::BufferFormat::kInvalid; } -bool RequiresPointSampler(const ImageResource& image) { - return image.numeric_class == Prospero::TextureNumericClass::Sint || - image.conversion_format != Prospero::BufferFormat::kInvalid; -} +enum class SamplerClass : uint8_t { Float, Integer, PointInteger }; -bool RequiresPointSampler(const ResourceSpecialization::Image& image) { - return image.numeric_class == Prospero::TextureNumericClass::Sint || - image.conversion_format != Prospero::BufferFormat::kInvalid; +template +SamplerClass ClassifySampler(const Image& image) { + if (image.numeric_class == Prospero::TextureNumericClass::Sint || + image.conversion_format != Prospero::BufferFormat::kInvalid) { + return SamplerClass::PointInteger; + } + return image.numeric_class == Prospero::TextureNumericClass::Uint ? SamplerClass::Integer + : SamplerClass::Float; } bool DescriptorIsCube(const DescriptorValue& descriptor) { @@ -380,8 +382,13 @@ bool MaterializeIndirectImage(const ResourcePlan& program, } // namespace struct SamplerPlan { - std::array point_sampler {}; - uint32_t sampler_count = 0; + struct Binding { + uint32_t source; + SamplerClass type; + }; + std::array, ShaderInfo::MaxSamplers> mapping; + std::array bindings; + uint32_t sampler_count = 0; }; struct ImageRemap { @@ -595,11 +602,9 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna if (!BuildSamplerPlan(program.info, specialization.images, sampler_plan)) { return SpecializationFail("specialized sampler layout exceeds its resource limit"); } - for (uint32_t index = 0; index < program.info.samplers.size(); index++) { - const auto target = sampler_plan.point_sampler[index]; - if (target != UINT32_MAX && target >= program.info.samplers.size()) { - snapshot.samplers.push_back(snapshot.samplers[index]); - } + for (uint32_t index = static_cast(program.info.samplers.size()); + index < sampler_plan.sampler_count; index++) { + snapshot.samplers.push_back(snapshot.samplers[sampler_plan.bindings[index].source]); } ImageRemap(specialization).Apply(snapshot.images); return true; @@ -607,29 +612,29 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna template bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan) { - if (base.samplers.size() > plan.point_sampler.size()) { + if (base.samplers.size() > plan.mapping.size()) { return false; } std::array usage {}; - plan.point_sampler.fill(UINT32_MAX); plan.sampler_count = static_cast(base.samplers.size()); for (const auto& pair: base.sampled_pairs) { if (pair.image >= images.size() || pair.sampler >= base.samplers.size()) { return false; } - usage[pair.sampler] |= RequiresPointSampler(images[pair.image]) ? 2u : 1u; + usage[pair.sampler] |= 1u << static_cast(ClassifySampler(images[pair.image])); } for (uint32_t index = 0; index < base.samplers.size(); index++) { - if ((usage[index] & 2u) == 0u) { - continue; - } - if ((usage[index] & 1u) == 0u) { - plan.point_sampler[index] = index; - } else { - if (plan.sampler_count >= ShaderInfo::MaxSamplers) { - return false; - } - plan.point_sampler[index] = plan.sampler_count++; + auto& mapping = plan.mapping[index]; + mapping.fill(UINT32_MAX); + const auto classes = usage[index] == 0u ? 1u : usage[index]; + bool first = true; + for (uint32_t type = 0; type < mapping.size(); type++) { + if ((classes & (1u << type)) == 0u) continue; + const auto target = first ? index : plan.sampler_count++; + if (target >= ShaderInfo::MaxSamplers) return false; + mapping[type] = target; + plan.bindings[target] = {index, static_cast(type)}; + first = false; } } return true; @@ -1105,25 +1110,18 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& auto samplers = program.info.samplers; auto sampled_pairs = program.info.sampled_pairs; samplers.reserve(sampler_plan.sampler_count); - for (uint32_t index = 0; index < program.info.samplers.size(); index++) { - const auto target = sampler_plan.point_sampler[index]; - if (target == UINT32_MAX) { - continue; - } - if (target == index) { - samplers[index].force_point_filtering = true; - } else { - EXIT_IF(target != samplers.size()); - auto sampler = samplers[index]; - sampler.force_point_filtering = true; - samplers.push_back(std::move(sampler)); + for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) { + const auto& binding = sampler_plan.bindings[index]; + if (index >= program.info.samplers.size()) { + samplers.push_back(program.info.samplers[binding.source]); } + samplers[index].force_point_filtering = binding.type == SamplerClass::PointInteger; + samplers[index].integer_border = binding.type != SamplerClass::Float; } for (auto& pair: sampled_pairs) { - if (RequiresPointSampler(images[pair.image])) { - EXIT_IF(sampler_plan.point_sampler[pair.sampler] == UINT32_MAX); - pair.sampler = sampler_plan.point_sampler[pair.sampler]; - } + const auto type = static_cast(ClassifySampler(images[pair.image])); + pair.sampler = sampler_plan.mapping[pair.sampler][type]; + EXIT_IF(pair.sampler == UINT32_MAX); samplers[pair.sampler].depth_compare |= images[pair.image].depth_compare; } @@ -1201,10 +1199,11 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& inst.ReplaceUsesWith(Value(&*result)); continue; } - if (image_opcode.needs_sampler && RequiresPointSampler(image) && + if (image_opcode.needs_sampler && memory.sampler < program.info.samplers.size()) { - EXIT_IF(sampler_plan.point_sampler[memory.sampler] == UINT32_MAX); - memory.sampler = sampler_plan.point_sampler[memory.sampler]; + const auto type = static_cast(ClassifySampler(image)); + memory.sampler = sampler_plan.mapping[memory.sampler][type]; + EXIT_IF(memory.sampler == UINT32_MAX); } EXIT_IF(image.indirect_root == memory.resource && inst.GetOpcode() != ValueOpcode::ImageSampleRaw); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b83fcbb45..47f2ef8b4 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -9584,6 +9584,83 @@ public: std::printf("[gpu] %-32s ok\n", name); } + void CheckSamplerBorderColors() { + constexpr const char *name = "SamplerBorderColors"; + auto &context = RuntimeRenderer(); + constexpr std::array, 5> coordinates{{ + {{0.5f, -1.f}}, {{-1.f, 0.5f}}, {{2.f, 0.5f}}, + {{0.5f, 2.f}}, {{0.5f, 0.5f}}}}; + std::array float_samplers{}; + for (const bool integer : {false, true}) { + auto image = CreateImage2D(name, 2, 2, + integer ? vk::Format::eR8G8B8A8Uint : vk::Format::eR8G8B8A8Unorm, + vk::ImageUsageFlagBits::eSampled, std::vector(4, 0xffffffffu), 1, + vk::ImageLayout::eShaderReadOnlyOptimal); + for (const auto border : {Prospero::SamplerBorderColor::kOpaqueWhite, + Prospero::SamplerBorderColor::kOpaqueBlack, + Prospero::SamplerBorderColor::kTransBlack}) { + ShaderSamplerResource sampler_descriptor{}; + const auto clamp = static_cast(Prospero::SamplerClampMode::kClampBorder); + sampler_descriptor.fields[0] = clamp | (clamp << 3u); + sampler_descriptor.fields[2] = 1u << 24u; + sampler_descriptor.fields[3] = static_cast(border) << 30u; + TestCase test; + test.name = name; + test.has_user_data = true; + test.user_data = MakeSampledTextureData(integer ? Prospero::BufferFormat::k8_8_8_8UInt + : Prospero::BufferFormat::k8_8_8_8UNorm); + test.user_data[2] = 1u | (1u << 14u); + std::copy_n(sampler_descriptor.fields, 4, test.user_data.begin() + 8); + test.user_data[50] = coordinates.size() * 4u * sizeof(u32); + test.user_data[51] = 3u << 28u; + test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_SAMPLE, + ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM}; + for (u32 sample = 0; sample < coordinates.size(); ++sample) { + AppendVMovLiteral(&test.code, 20, std::bit_cast(coordinates[sample][0])); + AppendVMovLiteral(&test.code, 21, std::bit_cast(coordinates[sample][1])); + test.code.push_back(EncodeMimg0(0x20, 0xf)); + test.code.push_back(EncodeMimg1(0, 20, 0, 2)); + for (u32 channel = 0; channel < 4; ++channel) { + AppendStoreVgpr(&test.code, channel, sample * 4u + channel); + const bool inside = sample == coordinates.size() - 1u; + const bool one = inside || border == Prospero::SamplerBorderColor::kOpaqueWhite || + (border == Prospero::SamplerBorderColor::kOpaqueBlack && channel == 3u); + test.expected.push_back(one ? (integer ? (inside ? 255u : 1u) : 0x3f800000u) : 0u); + } + } + AppendEnd(&test.code); + const auto compiled = CompileCase(test, SubgroupSize()); + const auto &sampler_info = compiled.program.info.samplers[0]; + Require(name, "sampler specialization", sampler_info.integer_border == integer, + "sampled image numeric type did not select its sampler border class"); + auto output = CreateStorageBuffer(name, {}, test.expected.size()); + const auto sampler = context.GetSamplerCache().GetSampler( + sampler_descriptor, sampler_info.integer_border); + Require(name, "sampler reuse", + sampler == context.GetSamplerCache().GetSampler(sampler_descriptor, integer), + "identical typed sampler descriptors did not reuse the cached sampler"); + if (integer) { + Require(name, "typed border cache key", sampler != float_samplers[static_cast(border)], + "integer image reused the floating-point border sampler"); + } else { + float_samplers[static_cast(border)] = sampler; + } + Dispatch(test, compiled, output, nullptr, &image, nullptr, nullptr, sampler); + const auto actual = ReadBuffer(name, output, test.expected.size()); + for (u32 i = 0; i < actual.size(); ++i) { + if (actual[i] != test.expected[i]) { + Fail(name, "native border sample", "integer=" + std::to_string(integer) + + " border=" + std::to_string(static_cast(border)) + " word=" + std::to_string(i) + + " expected=" + std::to_string(test.expected[i]) + " actual=" + std::to_string(actual[i])); + } + } + DestroyBuffer(&output); + } + DestroyImage(&image); + } + std::printf("[gpu] %-32s ok\n", name); + } + void CheckComparisonDepthTexture() { constexpr const char *name = "ComparisonDepthTexture"; constexpr uintptr_t base = 0x0000000204200000ull; @@ -9702,7 +9779,8 @@ public: scheduler.Finish(); auto output = CreateStorageBuffer(name, {}, test.expected.size()); - const auto sampler = context.GetSamplerCache().GetSampler(sampler_descriptor); + const auto sampler = context.GetSamplerCache().GetSampler( + sampler_descriptor, compiled.program.info.samplers[0].integer_border); Image sampled; sampled.view = view; sampled.layout = image.backing.state.layout; @@ -10713,7 +10791,8 @@ public: texture_cache, scheduler.Current(), lod_binding.image_id, {vk::ImageAspectFlagBits::eColor, mip, 1, 0, 1}, clear); } - const auto sampler = context.GetSamplerCache().GetSampler(lod_sampler); + const auto sampler = context.GetSamplerCache().GetSampler( + lod_sampler, lod_program.program.info.samplers[0].integer_border); auto output = CreateStorageBuffer(lod_test.name, {}, 1); struct LodCase { uint32_t base_level; @@ -32244,10 +32323,10 @@ void CheckImageSamplerSpecialization() { sampled_image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; sampled_image.read = true; mixed_sampler_program.info.images = {sampled_image, sampled_image, - sampled_image}; + sampled_image, sampled_image}; mixed_sampler_program.info.samplers.push_back({0u, 4u}); mixed_sampler_program.info.sampled_pairs = { - {0u, 0u, 8u}, {1u, 0u, 12u}, {2u, 0u, 16u}}; + {0u, 0u, 8u}, {1u, 0u, 12u}, {2u, 0u, 16u}, {3u, 0u, 20u}}; ShaderRecompiler::IR::MemoryInfo signed_memory; signed_memory.kind = ShaderRecompiler::IR::ResourceKind::Image; signed_memory.resource = 2u; @@ -32297,11 +32376,16 @@ void CheckImageSamplerSpecialization() { signed_image_descriptor.dwords[0] = 0x3000u; signed_image_descriptor.dwords[1] = static_cast(Prospero::BufferFormat::k32SInt) << 20u; + auto unsigned_image_descriptor = native_image_descriptor; + unsigned_image_descriptor.dwords[0] = 0x4000u; + unsigned_image_descriptor.dwords[1] = + static_cast(Prospero::BufferFormat::k32UInt) << 20u; ShaderRecompiler::IR::DescriptorValue sampler_descriptor{}; sampler_descriptor.dword_count = 4; const std::array descriptors{native_image_descriptor, packed_image_descriptor, - signed_image_descriptor, sampler_descriptor}; + signed_image_descriptor, unsigned_image_descriptor, + sampler_descriptor}; mixed_sampler_program.descriptor_sources.resize(descriptors.size()); for (u32 source = 0; source < descriptors.size(); source++) { auto &destination = mixed_sampler_program.descriptor_sources[source]; @@ -32313,7 +32397,7 @@ void CheckImageSamplerSpecialization() { for (u32 image = 0; image < mixed_sampler_program.info.images.size(); image++) { mixed_sampler_program.info.images[image].source = image; } - mixed_sampler_program.info.samplers[0].source = 3; + mixed_sampler_program.info.samplers[0].source = 4; mixed_sampler_program.srt_plan_complete = true; auto mixed_sampler_plan = ShaderRecompiler::IR::ExtractResourcePlan(mixed_sampler_program); @@ -32327,16 +32411,21 @@ void CheckImageSamplerSpecialization() { ShaderRecompiler::IR::ApplyResourceSpecialization( mixed_sampler_program, mixed_sampler_specialization); Require("ImageSamplerSpecialization", "mixed sampler variant", - mixed_sampler_program.info.samplers.size() == 2u && + mixed_sampler_program.info.samplers.size() == 3u && !mixed_sampler_program.info.samplers[0].force_point_filtering && - mixed_sampler_program.info.samplers[1].force_point_filtering && + !mixed_sampler_program.info.samplers[0].integer_border && + !mixed_sampler_program.info.samplers[1].force_point_filtering && + mixed_sampler_program.info.samplers[1].integer_border && + mixed_sampler_program.info.samplers[2].force_point_filtering && + mixed_sampler_program.info.samplers[2].integer_border && mixed_sampler_program.info.sampled_pairs[0].sampler == 0u && - mixed_sampler_program.info.sampled_pairs[1].sampler == 1u && - mixed_sampler_program.info.sampled_pairs[2].sampler == 1u && - mixed_sampler_program.memory_info[0].sampler == 1u && - mixed_sampler_snapshot.samplers.size() == 2u, - "a shared float/integer sampler was not split into point and native " - "variants or the signed instruction retained the native sampler"); + mixed_sampler_program.info.sampled_pairs[1].sampler == 2u && + mixed_sampler_program.info.sampled_pairs[2].sampler == 2u && + mixed_sampler_program.info.sampled_pairs[3].sampler == 1u && + mixed_sampler_program.memory_info[0].sampler == 2u && + mixed_sampler_snapshot.samplers.size() == 3u, + "a shared float/unsigned/signed sampler lost its border or filtering " + "class in the compiled bindings, instruction or descriptor snapshot"); std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId"); } @@ -33368,7 +33457,7 @@ void CheckPm4Predication(RenderContext &renderer) { const auto predicated_address = reinterpret_cast(&predicated); const auto unconditional_address = reinterpret_cast(&unconditional); return std::array{ - // Captured AGC form: c0022000 00010100 . + // Captured packet form: c0022000 00010100 . 0xc0022000u, (op << 16u) | (wait << 12u) | (condition << 8u), static_cast(address), static_cast(address >> 32u), KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0) | 1u, 0, @@ -34310,7 +34399,7 @@ void CheckPm4ContextStateOperations(RenderContext &renderer) { processor.GetCtx().GetRenderTargetMask() == 0x0badc0de, "CLEAR_STATE discarded the pushed Cx state"); - // AGC CLEAR_STATE restores the viewport, scissor, and guard-band register defaults. + // CLEAR_STATE restores the viewport, scissor, and guard-band register defaults. processor.GetCtx().SetScreenScissor(1, 2, 3, 4); processor.GetCtx().SetGuardBands(2.0f, 3.0f, 4.0f, 5.0f); Pm4Execution scissor_reset; @@ -34369,7 +34458,7 @@ void CheckPm4ContextStateOperations(RenderContext &renderer) { Gen5::AgcDcbContextStateOpGetSize(2) == 108 && Gen5::AgcDcbContextStateOpGetSize(3) == 128 && Gen5::AgcDcbContextStateOpGetSize(4) == 0, - "context-state HLE sizes do not match libSceAgc"); + "context-state HLE sizes do not match the expected packet layout"); std::printf("[host] %-32s ok\n", "Pm4ContextState"); } @@ -35102,6 +35191,12 @@ int main(int argc, char **argv) { vulkan.CheckBufferCacheDirtyGarbageCollection(); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--sampler-border-only") == 0) { + CheckImageSamplerSpecialization(); + VulkanHarness vulkan; + vulkan.CheckSamplerBorderColors(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--sampled-depth-resource-only") == 0) { VulkanHarness vulkan; CheckSampledDepthResource(); @@ -35192,6 +35287,7 @@ int main(int argc, char **argv) { CheckImageTransitionState(vulkan.RuntimeRenderer()); CheckSampledDepthResource(); CheckDepthTextureEncoding(); + vulkan.CheckSamplerBorderColors(); vulkan.CheckComparisonDepthTexture(); vulkan.CheckRasterization(true); CheckBasicStorageTextureDescriptor();