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
2 changes: 1 addition & 1 deletion core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ if(BUILD_TESTING)
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(${multisampledDccClearTests} PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage agc_driver_alpha_to_coverage_sample_mask_export agc_driver_two_sample_alpha_to_coverage_sample_mask_export ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ if(BUILD_TESTING)
add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-compared-import ${samples})
list(APPEND multisampledDccClearTests ${testName})
endforeach()
set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage PROPERTIES
add_test(NAME agc_driver_alpha_to_coverage_sample_mask_export COMMAND agc_driver_alpha_to_coverage_tests --sample-mask8)
add_test(NAME agc_driver_two_sample_alpha_to_coverage_sample_mask_export COMMAND agc_driver_alpha_to_coverage_tests --sample-mask2)
set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage agc_driver_alpha_to_coverage_sample_mask_export agc_driver_two_sample_alpha_to_coverage_sample_mask_export PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
endif()
68 changes: 68 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,62 @@ constexpr std::array<DitherCase, 56> TwoSampleDitherCases{{
{0x00007e00u, 4u, {0x00u, 0x00u, 0x00u, 0x00u}},
}};

void SampleMaskExportCoverage(AgcDriver::VulkanDevice& device, std::uint32_t samples, std::span<const DitherCase> cases) {
const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 8);
const auto all = (1u << samples) - 1u;
std::vector<DitherCase> undithered;
for (const std::uint32_t bits : {0x3e800000u, 0x3f000000u, 0x3f400000u}) {
const auto mask = std::bit_cast<float>(bits) < 0.5f ? 0u : all;
undithered.push_back({bits, Float32Export, {mask, mask, mask, mask}});
}
std::vector<std::unique_ptr<Block>> blocks;
for (const bool dithered : {true, false}) {
for (const auto& test : dithered ? cases : std::span<const DitherCase>(undithered)) {
for (const std::uint32_t exported : {0x00000001u, 0xaaaaaaaau, 0xffffff0fu}) {
blocks.push_back(std::make_unique<Block>(layout.Bytes() * samples));
auto& source = *blocks.back();
std::memset(source.data, 0, source.bytes);
const auto multisampled = DecodeTarget(source, samples);
for (std::uint32_t sample = 0; sample < samples; ++sample) {
const Color reference{std::ldexp(1.0f, static_cast<int>(sample) - static_cast<int>(samples - 1u)), 0, 0, 1};
Draw(device, multisampled, false, Float32Export, Program(reference, Float32Export, 1u << sample), false, true);
}
const auto alpha = test.format == Float32Export ? std::bit_cast<float>(test.bits) : 0.0f;
const Color color{0, 0, 0, alpha};
const auto half = test.format == Float16Export ? std::optional<std::uint16_t>(static_cast<std::uint16_t>(test.bits)) : std::nullopt;
const auto code = Program(color, test.format, exported, half);
if (dithered) {
Draw(device, multisampled, true, test.format, code, true, true, static_cast<std::uint8_t>(samples));
} else {
Draw(device, multisampled, true, test.format, code, false, true);
}
blocks.push_back(std::make_unique<Block>(layout.Bytes()));
auto& resolved = *blocks.back();
std::memset(resolved.data, 0, resolved.bytes);
AgcDriver::Graphics::ColorMetadataPass pass{};
pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve;
pass.source = multisampled;
pass.targets = {DecodeTarget(resolved)};
device.ColorMetadataPass(pass);
const auto stored = ReadBack(device, resolved);
for (std::uint32_t y = 0; y < Height; ++y) {
for (std::uint32_t x = 0; x < Width; ++x) {
const auto mask = test.masks[2u * (x & 1u) + (y & 1u)] & exported & all;
const auto expected = x < ScissorWidth ? static_cast<float>(all - mask) / static_cast<float>((1u << (samples - 1u)) * samples) : 0.0f;
const auto actual = FromHalf(stored[layout.Offset(x, y) / 2u]);
if (actual == expected) continue;
char detail[224];
std::snprintf(detail, sizeof(detail), "%s coverage export %u alpha 0x%08x with exported mask 0x%08x pixel (%u,%u): resolved %.9g, expected %.9g for mask 0x%02x", dithered ? "dithered" : "undithered", test.format, test.bits, exported, x, y, actual, expected, mask);
throw std::runtime_error(detail);
}
}
programCount = 0;
}
}
}
AgcDriver::Graphics::ClearMultisampleTargets(device.Device());
}

void DccSrgbClearTests(AgcDriver::VulkanDevice& device, std::uint32_t samples) {
const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 4);
const ColorTargetLayout allocation(Width, Height, ColorTileMode::RenderTarget, 8);
Expand Down Expand Up @@ -1275,6 +1331,18 @@ int main(int argc, char** argv) {
std::puts("dithered eight-sample alpha-to-coverage tests passed");
return 0;
}
if (argc == 2 && (std::string_view(argv[1]) == "--sample-mask8" || std::string_view(argv[1]) == "--sample-mask2")) {
const bool eight = std::string_view(argv[1]) == "--sample-mask8";
try {
SampleMaskExportCoverage(*device, eight ? 8u : 2u, eight ? std::span<const DitherCase>(EightSampleDitherCases) : std::span<const DitherCase>(TwoSampleDitherCases));
} catch (const std::runtime_error& error) {
if (std::string_view(error.what()) != "AGC graphics: the device cannot render color with the target's sample count") throw;
std::printf("skipped, the device lacks %s-sample color attachments: %s\n", eight ? "eight" : "two", error.what());
return VulkanTestSkipped;
}
std::printf("%s-sample alpha-to-coverage with sample-mask export tests passed\n", eight ? "eight" : "two");
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--dithered2") {
try {
DitheredCoverage(*device, 2, TwoSampleDitherCases);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ struct SpirvEmitterState {
std::uint32_t sampleMaskInputVariable = 0;
std::uint32_t quadSampleCoverage = 0;
std::uint32_t sampleOrderWord = 0;
std::uint32_t alphaCoverageVariable = 0;
std::uint32_t exportedCoverageVariable = 0;
std::uint32_t subgroupLocalInvocationIdVariable = 0;
std::uint32_t subgroupIdVariable = 0;
std::uint32_t perVertexVariable = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,12 @@ void DefineOutputs(SpirvEmitterState& state) {
BuiltIn(state.clipDistanceVariable, F32ArrayType(state, clipDistanceCount), "gl_ClipDistance", spv::BuiltInClipDistance);
}
if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->psAlphaToCoverage) {
if (state.inputInfo.pixel->psSampleMaskExportEnable) throw std::runtime_error("alpha-to-coverage with a sample-mask export is unsupported");
if (state.inputInfo.pixel->psSampleMaskExportEnable) {
for (auto* variable : {&state.alphaCoverageVariable, &state.exportedCoverageVariable}) {
*variable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPrivate, TypeI32(state)), spv::StorageClassPrivate);
if (state.spirvVersion >= 0x00010400u) state.interfaceVariables.push_back(*variable);
}
}
BuiltIn(state.sampleMaskVariable, SampleMaskArrayType(state), "gl_SampleMask", spv::BuiltInSampleMask);
}
if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->QuadSampleMaskPartial()) {
Expand Down
24 changes: 20 additions & 4 deletions core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,17 @@ void StoreCoverage(SpirvEmitterState& state, std::uint32_t mask) {
state.module.AddFunction(spv::OpStore, pointer, mask);
}

void StoreCombinedCoverage(SpirvEmitterState& state, std::uint32_t coverage, std::uint32_t own, std::uint32_t other) {
if (own == 0u) {
StoreCoverage(state, coverage);
return;
}
state.module.AddFunction(spv::OpStore, own, coverage);
const auto loaded = state.module.AllocateId();
state.module.AddFunction(spv::OpLoad, TypeI32(state), loaded, other);
StoreCoverage(state, Binary(state, spv::OpBitwiseAnd, TypeI32(state), coverage, loaded));
}

void StoreDitheredAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) {
constexpr std::array<std::uint32_t, 8> twoSampleMasks{{
0x00000000u, 0x02000002u, 0x01000201u, 0x01020201u, 0x01020101u, 0x03020103u, 0x03010303u, 0x03030303u,
Expand Down Expand Up @@ -640,7 +651,7 @@ void StoreDitheredAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) {
const auto pixel = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), parity[0], ConstantU32(state, 1u)), parity[1]);
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), pixel, ConstantU32(state, 3u));
const auto mask = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), packed, shift), ConstantU32(state, 255u));
StoreCoverage(state, Unary(state, spv::OpBitcast, TypeI32(state), mask));
StoreCombinedCoverage(state, Unary(state, spv::OpBitcast, TypeI32(state), mask), state.alphaCoverageVariable, state.exportedCoverageVariable);
}

void StoreAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) {
Expand All @@ -654,7 +665,7 @@ void StoreAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) {
state.module.AddFunction(spv::OpFUnordLessThan, TypeBool(state), below, alpha, ConstantF32(state, 0x3f000000u));
const auto mask = state.module.AllocateId();
state.module.AddFunction(spv::OpSelect, TypeI32(state), mask, below, ConstantI32(state, 0), ConstantI32(state, -1));
StoreCoverage(state, mask);
StoreCombinedCoverage(state, mask, state.alphaCoverageVariable, state.exportedCoverageVariable);
}

std::uint32_t ExportRawComponent(SpirvValueEmitContext& ctx, std::uint32_t vector, std::uint32_t component) {
Expand Down Expand Up @@ -1852,7 +1863,12 @@ void EmitProgram(SpirvEmitterState& state) {
for (std::uint32_t pixel = 3u; pixel-- > 0u;) selected = Select(state, TypeU32(state), EmitCompareU32Constant(state, spv::OpIEqual, quad, pixel), word(pixel), selected);
state.sampleOrderWord = selected;
}
if (AlphaToCoverage(state)) StoreCoverage(state, ConstantI32(state, -1));
if (AlphaToCoverage(state)) {
StoreCoverage(state, ConstantI32(state, -1));
for (const auto variable : {state.alphaCoverageVariable, state.exportedCoverageVariable}) {
if (variable != 0u) state.module.AddFunction(spv::OpStore, variable, ConstantI32(state, -1));
}
}
EmitBdaAnchors(state);
if (state.pointSizeVariable != 0u && state.inputInfo.vertex != nullptr && state.inputInfo.vertex->pointList) {
const auto& vertex = *state.inputInfo.vertex;
Expand Down Expand Up @@ -2206,7 +2222,7 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
const auto raw = ExportRawComponent(ctx, data, 2);
const auto value = state.module.AllocateId();
state.module.AddFunction(spv::OpBitcast, TypeI32(state), value, raw);
StoreCoverage(state, value);
StoreCombinedCoverage(state, value, state.exportedCoverageVariable, state.alphaCoverageVariable);
}
return;
}
Expand Down
Loading
Loading