diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index 17b5f7cb3b..6d957c140a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -98,6 +98,7 @@ float EffectiveMinLod(const GuestTextureResource& resource); bool IsNullTextureResource(std::span words); GuestTextureResource DecodeTextureResource(std::span words); std::array EncodeTextureResource(const GuestTextureResource& resource); +void NoteOneFragmentFmask(std::uint64_t address); bool DescriptorSingleLevel(std::span words); bool FirstLayerDescriptor(std::span words, std::array& narrowed); bool MatchesGuestDimension(ShaderRecompiler::DescriptorImageShape shape, TextureDimension dimension); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 7cf40177ed..a0609a9d33 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -67,6 +67,7 @@ struct ColorTarget { bool uintExport = false; std::uint32_t pipeBankXor = 0; std::uint32_t samples = 1; + std::uint32_t coverageSamples = 1; std::uint32_t dccControl = 0; std::uint64_t fmaskAddress = 0; std::uint32_t fmaskPipeBankXor = 0; @@ -292,8 +293,8 @@ inline constexpr std::array DrawKeyRegisters{{ // DB_DEPTH_CONTROL .. PA_CL_VS_OUT_CNTL, PA_SC_MODE_CNTL_0/1, VGT_GS_MODE, VGT_GS_VERT_ITEMSIZE, // PA_SU_VTX_CNTL, the sample masks, PA_SC_CONSERVATIVE_RASTERIZATION_CNTL. {RegisterBank::Context, 0x200, 8}, {RegisterBank::Context, 0x286, 2}, {RegisterBank::Context, 0x292, 2}, {RegisterBank::Context, 0x29b, 1}, {RegisterBank::Context, 0x2ab, 1}, {RegisterBank::Context, 0x2af, 1}, {RegisterBank::Context, 0x2ce, 1}, {RegisterBank::Context, 0x2d5, 2}, {RegisterBank::Context, 0x2db, 2}, {RegisterBank::Context, 0x2de, 6}, {RegisterBank::Context, 0x2f8, 2}, {RegisterBank::Context, 0x2fe, 16}, {RegisterBank::Context, 0x30e, 2}, {RegisterBank::Context, 0x313, 1}, - // CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3. - {RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x398, 8}, {RegisterBank::Context, 0x3a8, 0x18}, + // CB_COLOR0..7_BASE .. DCC_BASE (15 words a slot), CB_COLOR0..7_BASE_EXT, CMASK_BASE_EXT, FMASK_BASE_EXT, DCC_BASE_EXT, ATTRIB2, ATTRIB3. + {RegisterBank::Context, 0x318, 0x78}, {RegisterBank::Context, 0x390, 8}, {RegisterBank::Context, 0x398, 0x10}, {RegisterBank::Context, 0x3a8, 0x18}, // The pixel program address, RSRC2 and user words; the geometry-back user pointer and program // address; the vertex/geometry-front RSRC1/RSRC2 and user words; the vertex program address; // the hull user pointer, program address, RSRC2 and user words; the local program address. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index b34f12db41..33e40fd787 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetTransfer.hpp" #include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp" @@ -2519,6 +2520,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara const ColorTargetLayout colorLayout(color.extent.width, color.extent.height, color.tileMode, color.elementBytes); static_cast(FlushDepthPlanes(color.address, colorLayout.Bytes(), DepthPlaneScope::Whole)); RetireDepthSurfaces(context.device, color.address, colorLayout.Bytes()); + if (color.fmaskAddress != 0) NoteOneFragmentFmask(color.fmaskAddress); constexpr VkBufferUsageFlags copies = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; timer.phase(PhaseSetup); // Debug aid: APS5_NO_RESIDENT_TARGETS=1 copies every target in and out again. @@ -3233,6 +3235,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con targets.reserve(recipe.targets.size()); for (std::size_t index = 0; index < recipe.targets.size(); ++index) { timer.phase(PhaseSetup); + if (state.colors[index].fmaskAddress != 0) NoteOneFragmentFmask(state.colors[index].fmaskAddress); auto stored = recipe.targets[index].lock(); if (stored == nullptr || !StorageImageCached(context, stored.get()) || !StorageImageServesKeys(*stored, state.colors[index].dccAddress)) return miss(DrawRecipeMiss::TargetGone); auto resident = refreshResidentTarget(context, state, state.colors[index], outcome, profile, [&] { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 7a7788c964..885e6e54f4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -15,6 +17,21 @@ namespace AgcDriver::Graphics { namespace { +std::mutex& oneFragmentFmaskMutex() { + static std::mutex mutex; + return mutex; +} + +std::unordered_set& oneFragmentFmasks() { + static std::unordered_set addresses; + return addresses; +} + +bool oneFragmentFmask(std::uint64_t address) { + std::lock_guard lock(oneFragmentFmaskMutex()); + return oneFragmentFmasks().contains(address & ~0xffffull); +} + void requireValidDstSel(std::uint32_t value) { Require(value == 0 || value == 1 || (value >= 4 && value <= 7), "guest texture descriptor has an invalid destination channel selector"); } @@ -186,6 +203,7 @@ GuestTextureResource DecodeTextureResource(std::span words) Require(!writeCompress && (!metaCompress || metaAddr != 0u), "FMASK CMASK compression is not implemented without readable metadata"); Require(!metaCompress || metaPipeAligned, "FMASK CMASK metadata must be pipe aligned"); Require(!metaCompress || format == static_cast(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "FMASK CMASK compression is implemented only for 8-sample, 8-fragment FMASK"); + Require(!oneFragmentFmask(baseAddress), "reading the FMASK of a one-fragment color target drawn on the host is not implemented (the host does not write it)"); Require((typeRaw == 9u || typeRaw == 13u) && tileModeRaw == 24u && baseLevel == 0u && lastLevel == 0u && maxMip == 0u, "FMASK reads require a single-mip 2D or 2D-array Z_64KB_X surface"); const bool identity = format != static_cast(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8) && dstSelX == 4u && dstSelY == 5u && dstSelZ == 6u && dstSelW == 7u; for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(identity || selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented"); @@ -369,4 +387,9 @@ bool ArrayLayersServeView(const GuestTextureResource& surface, const GuestTextur return view.dimension == TextureDimension::k2DArray && view.depthOrLastArray <= surface.depthOrLastArray; } +void NoteOneFragmentFmask(std::uint64_t address) { + std::lock_guard lock(oneFragmentFmaskMutex()); + oneFragmentFmasks().insert(address & ~0xffffull); +} + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index 0b0f4d6068..3d07444ad3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -35,7 +35,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec"); static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec"); #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) -static_assert(sizeof(State) == 824, "State changed: update CanonicalPipelineState and the recipe codec"); +static_assert(sizeof(State) == 832, "State changed: update CanonicalPipelineState and the recipe codec"); static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index badcb69f91..2deea42559 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -1504,8 +1504,9 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { result.customSampleLocations = true; result.sampleLocations.fill(0); } - const bool merged = color.samples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples); - if (!merged && color.samples == 1 && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result); + const bool merged = color.samples == 1u && color.coverageSamples == 1u && !result.depth && !result.perSampleShading && (result.colors.empty() || result.mergedCoverage) && overrasterized(cx, result.samples); + if (!merged && color.samples == 1 && color.coverageSamples == 1u && result.colors.empty() && !result.depth && rasterizesSampleZeroOnly(cx, result)) rasterizeSampleZero(result); + Require(color.coverageSamples == color.samples || color.coverageSamples == result.samples || (result.samples == 1u && (read(cx, 0x292) & 1u) == 0), "a one-fragment color target's sample count differs from the rasterizer's"); const bool truncated = color.samples == 1u && result.samples > 1u && result.depth && result.depth->samples == result.samples && (result.colors.empty() || result.truncatedCoverage) && !overrasterized(cx, result.samples); Require((color.samples == result.samples && !result.mergedCoverage && !result.truncatedCoverage) || merged || truncated, "a color target's sample count differs from the rasterizer's"); result.mergedCoverage = merged; @@ -1883,10 +1884,11 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo const auto decoded = DecodeColorFormat(format, number, swap); const auto attrib = read(cx, 0x31d + stride); zero(cx, 0x31d + stride, ~0x3f000u, "color destination alpha override or other CB_COLOR_ATTRIB modes"); - color.samples = 1u << ((attrib >> 12u) & 7u); - Require(color.samples <= 8u, "color targets with more than eight samples are unsupported"); - Require((1u << ((attrib >> 15u) & 3u)) == color.samples, "color targets with fewer fragments than samples (EQAA) are unsupported"); - const auto multisampleInfo = color.samples > 1 ? 0x0c006000u : 0u; + color.coverageSamples = 1u << ((attrib >> 12u) & 7u); + Require(color.coverageSamples <= 8u, "color targets with more than eight samples are unsupported"); + color.samples = 1u << ((attrib >> 15u) & 3u); + Require(color.samples == color.coverageSamples || color.samples == 1u, "color targets with more than one but fewer fragments than samples (EQAA) are unsupported"); + const auto multisampleInfo = color.coverageSamples > 1 ? 0x0c006000u : 0u; // ROUND_MODE (bit 18) only affects unorm rounding and packed float truncation. With DCC_ENABLE (bit 28) the target is written if ((info & ~(0x00039f7cu | 0x00040000u | 0x10000000u | 0x00002000u | multisampleInfo)) != 0) throw std::runtime_error("AGC graphics: color compression, DCC, endian conversion, nonstandard rounding or color optimization is unsupported (CB_COLOR_INFO 0x" + [&] { char text[16]; std::snprintf(text, sizeof(text), "%08x", info); return std::string(text); }() + ")"); Require((info & 0x8000u) != 0 || number <= 1u || number == 7 || number == 4 || number == 5, "unclamped normalized color is unsupported"); @@ -2026,6 +2028,11 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo color.fmaskAddress = address - color.fmaskPipeBankXor; } } + if (color.coverageSamples > color.samples && (info & 0x4000u) != 0) { + const auto fmaskHigh = find(cx, 0x3a0 + slot); + color.fmaskAddress = ((fmaskHigh == cx.end() ? 0ull : static_cast(fmaskHigh->second & 0xffu)) << 40u) | (static_cast(read(cx, 0x321 + stride)) << 8u); + Require(color.fmaskAddress != 0, "a compressed one-fragment color target without an FMASK address is unsupported"); + } if ((info & 0x10000000u) != 0) { if (maxMip == 0) { const auto dccHigh = find(cx, 0x3a8 + slot); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index c27a3e5e43..97cb82702a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -3832,7 +3832,7 @@ void multisampleTests() { } queue = multisampled(); queue.context[0x31d] = 0x1000; - expectFailure([&] { DecodeState(queue); }, "fewer fragments than samples"); + expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); queue.context[0x31d] = 0; queue.context[0x293] = 0x10000u; Require(DecodeState(queue).samples == 1 && !DecodeState(queue).perSampleShading, "PS_ITER_SAMPLE into a one-sample target did not rasterize sample 0 alone"); @@ -4328,6 +4328,42 @@ void multisampledDepthTests() { queue = truncatedTwoSample(); queue.context[0x31d] = 0x12000; expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); + const auto oneFragment = [&] { + auto queue = singleSampleScan(); + queue.context[0x2f8] = 0x00210002; + queue.context[0x292] = 3; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0x622ae6ae; + queue.context[0x31d] = 0x2000; + queue.context[0x201] = 0x112202; + return queue; + }; + queue = oneFragment(); + state = DecodeState(queue); + Require(state.samples == 4 && !state.customSampleLocations && state.depth && state.depth->samples == 4 && state.colors.size() == 1 && state.colors[0].samples == 1 && state.truncatedCoverage && !state.mergedCoverage && !state.perSampleShading, "a 4-sample color target with one fragment beside a 4x depth target (PPSA02015's registers) did not keep sample 0's color in its one fragment"); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck refused a 4-sample color target with one fragment beside a 4x depth target"); + queue = oneFragment(); + queue.context[0x31c] |= 0x4000u; + queue.context[0x321] = 0x00c3a1b2u; + queue.context[0x3a0] = 0x01u; + state = DecodeState(queue); + Require(state.colors.size() == 1 && state.colors[0].fmaskAddress == ((1ull << 40u) | (0x00c3a1b2ull << 8u)), "a compressed one-fragment color target did not decode its FMASK address"); + queue.context[0x321] = 0u; + queue.context[0x3a0] = 0u; + expectFailure([&] { DecodeState(queue); }, "a compressed one-fragment color target without an FMASK address is unsupported"); + queue = oneFragment(); + Require(DecodeState(queue).colors[0].fmaskAddress == 0u, "an uncompressed one-fragment color target noted an FMASK address"); + queue = oneFragment(); + queue.context[0x31d] = 0xa000; + expectFailure([&] { DecodeState(queue); }, "color targets with more than one but fewer fragments than samples (EQAA) are unsupported"); + queue = oneFragment(); + queue.context[0x2f8] = 0x00100001; + queue.context[0x010] = 0x22900987; + for (std::uint32_t pixel = 0; pixel < 4; ++pixel) queue.context[0x2fe + pixel * 4u] = 0xcc44; + expectFailure([&] { DecodeState(queue); }, "a one-fragment color target's sample count differs from the rasterizer's"); + queue = oneFragment(); + queue.context[0x010] = 0; + queue.context[0x011] = 0; + expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); queue = singleSampleScan(); queue.context[0x010] = 0x22900983; expectFailure([&] { DecodeState(queue); }, "a color target's sample count differs from the rasterizer's"); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index ab7db93b7a..1e8e0d5f3b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -178,6 +178,21 @@ void RunGuestTextureResourceTests() { rejectFields(compressedFmask, "FMASK CMASK metadata must be pipe aligned"); compressedFmask.metaPipeAligned = compressedFmask.writeCompress = true; rejectFields(compressedFmask, "FMASK CMASK compression is not implemented"); + Fields oneFragmentFmask = fmask; + oneFragmentFmask.base40 = 0x6540000ull; + static_cast(DecodeTextureResource(pack(oneFragmentFmask))); + NoteOneFragmentFmask(oneFragmentFmask.base40 << 8u); + rejectFields(oneFragmentFmask, "reading the FMASK of a one-fragment color target drawn on the host is not implemented"); + Fields xoredFmask = oneFragmentFmask; + xoredFmask.base40 |= 0xa5ull; + rejectFields(xoredFmask, "reading the FMASK of a one-fragment color target drawn on the host is not implemented"); + Require(DecodeTextureResource(pack(fmask)).baseAddress == fmaskResource.baseAddress, "an FMASK read of another surface was refused after a one-fragment target noted its FMASK"); + Fields oneFragmentColor = oneFragmentFmask; + oneFragmentColor.format = base.format; + oneFragmentColor.dstSelY = base.dstSelY; + oneFragmentColor.dstSelZ = base.dstSelZ; + oneFragmentColor.dstSelW = base.dstSelW; + Require(DecodeTextureResource(pack(oneFragmentColor)).baseAddress == (oneFragmentFmask.base40 << 8u), "a color read at a noted FMASK address was refused"); Fields fmaskArray = fmask; fmaskArray.typeRaw = 13u; fmaskArray.depth = 2u; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp index ce79a9b375..40ee390964 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelSampleCoverage.cpp @@ -67,7 +67,7 @@ constexpr std::array, 3> OtherHalfTriangle{{ {1.0f, -1.0f, 0.5f, 1.0f}, {1.0f, 1.0f, 0.5f, 1.0f}, {-1.0f, 1.0f, 0.5f, 1.0f} }}; -alignas(256) std::array, 8> Programs{}; +alignas(256) std::array, 16> Programs{}; alignas(256) std::array DepthMemory{}; std::size_t programCount = 0; @@ -164,12 +164,12 @@ struct Block { std::uint8_t* data = nullptr; }; -AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info) { +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t info, std::uint32_t attrib = 0) { AgcDriver::Registers cx; cx[0x318] = static_cast(block.Address() >> 8u); cx[0x31b] = 0; cx[0x31c] = info; - cx[0x31d] = 0; + cx[0x31d] = attrib; cx[0x323] = 0; cx[0x324] = 0; cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u); @@ -376,6 +376,51 @@ std::string ExpectTruncated(AgcDriver::VulkanDevice& device) { } +std::string ExpectOneFragment(AgcDriver::VulkanDevice& device) { + if ((AgcDriver::VulkanDeviceTestAccess::Context(device).limits.framebufferDepthSampleCounts & VK_SAMPLE_COUNT_4_BIT) == 0) return "the device has no four-sample depth targets (framebufferDepthSampleCounts)"; + constexpr std::size_t bytes = std::size_t{Width} * Height * 16u; + AgcDriver::Graphics::DepthTarget depth{reinterpret_cast(DepthMemory.data()), 0u, {Width, Height}, VK_FORMAT_D32_SFLOAT, 0.0f, 0}; + depth.samples = 4; + std::array written{}; + std::uint32_t untouched = 0; + for (const auto* triangle : {&HalfTriangle, &OtherHalfTriangle}) { + Block block(bytes); + std::fill(block.data, block.data + bytes, std::uint8_t{0xff}); + const auto color = DecodeTarget(block, Rgba32FloatInfo, 0x2000u); + Require(color.samples == 1u, "a 4-sample color target with one fragment did not decode as one stored sample"); + try { + Draw(device, color, ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage), Program(0u), *triangle, 4u, &depth); + } catch (const std::exception& error) { + if (std::string_view(error.what()).find("truncated coverage reduction") == std::string_view::npos) throw; + return "the device lacks VK_AMD_mixed_attachment_samples and VK_NV_framebuffer_mixed_samples with truncated coverage reduction"; + } + const auto stored = ReadBack(device, block.Address(), bytes); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto* texel = stored.data() + (static_cast(y) * Width + x) * 16u; + if (std::all_of(texel, texel + 16, [](std::uint8_t byte) { return byte == 0xffu; })) { + ++untouched; + continue; + } + std::array value{}; + std::memcpy(value.data(), texel, sizeof(value)); + const auto mask = static_cast(value[0]); + Require(value[0] == static_cast(mask) && mask >= 1u && mask <= 15u && value[1] == 0.0f && value[2] == 0.0f && value[3] == 1.0f, "one-fragment coverage: pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds (" + std::to_string(value[0]) + ", " + std::to_string(value[1]) + ", " + std::to_string(value[2]) + ", " + std::to_string(value[3]) + ")"); + ++written[mask]; + } + } + } + std::uint32_t withoutSampleZero = 0; + std::uint32_t partial = 0; + for (std::uint32_t mask = 1; mask < 16u; ++mask) { + if ((mask & 1u) == 0) withoutSampleZero += written[mask]; + else if (mask != 15u) partial += written[mask]; + } + Require(withoutSampleZero == 0, "one-fragment coverage: " + std::to_string(withoutSampleZero) + " pixels whose coverage lacks sample 0 were written into the one fragment"); + Require(written[15] != 0 && partial != 0 && untouched != 0, "one-fragment coverage: two half-covering triangles wrote " + std::to_string(written[15]) + " fully covered and " + std::to_string(partial) + " partly covered pixels and left " + std::to_string(untouched) + " untouched"); + return {}; +} + int main() { try { using ShaderRecompiler::PixelInput; @@ -387,6 +432,7 @@ int main() { Expect(*device, PixelInputBit(PixelInput::Ancillary) | PixelInputBit(PixelInput::SampleCoverage), 1u, "SAMPLE_COVERAGE after ANCILLARY"); if (!ExpectMerged(*device)) std::puts("merged coverage not tested: the device lacks VK_NV_framebuffer_mixed_samples with merged coverage reduction"); if (const auto skipped = ExpectTruncated(*device); !skipped.empty()) std::printf("truncated coverage not tested: %s\n", skipped.c_str()); + if (const auto skipped = ExpectOneFragment(*device); !skipped.empty()) std::printf("one-fragment coverage not tested: %s\n", skipped.c_str()); std::puts("pixel sample coverage tests passed"); return 0; } catch (const std::exception& error) {