mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
gpu: fix separate alpha blending on swizzled render targets
Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
This commit is contained in:
@@ -15272,7 +15272,8 @@ public:
|
||||
PipelineCache::GraphicsPrograms{{vertex_shader}, pixel_shader});
|
||||
};
|
||||
auto &filled = pipeline(true, 2, 2);
|
||||
const auto draw = [&](const PipelineCache::Pipeline &selected, uint32_t vertex_count = 3) {
|
||||
const auto draw = [&](const PipelineCache::Pipeline &selected, uint32_t vertex_count = 3,
|
||||
std::array<u32, 4> clear_value = {}) {
|
||||
RenderExecutorTestAccess::BindRenderTarget(executor, color.image_id);
|
||||
if (depth.image_id) {
|
||||
RenderExecutorTestAccess::BindRenderTarget(executor, depth.image_id);
|
||||
@@ -15299,6 +15300,7 @@ public:
|
||||
RenderExecutorTestAccess::CommitBindings(
|
||||
executor, command, selected, bindings.vertex[0], *bindings.pixel);
|
||||
rendering.color_attachments[0].is_clear = true;
|
||||
rendering.color_attachments[0].clear_value = clear_value;
|
||||
command.BeginRendering(rendering);
|
||||
auto cmd = command.Handle();
|
||||
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, selected.pipeline);
|
||||
@@ -15642,6 +15644,68 @@ public:
|
||||
wave_ids[0] != wave_ids[1] && wave_keys[0] != wave_keys[1],
|
||||
"wave32 and wave64 pixel programs shared a cache key");
|
||||
|
||||
// Reversed RGBA targets must use logical Sa for RGB, with a separate
|
||||
// unit source factor when alpha accumulates as Sa + Da * (1 - Sa).
|
||||
static const auto blend_pixel = [] {
|
||||
std::vector<u32> code;
|
||||
constexpr std::array<float, 4> source{0.8f, 0.6f, 0.4f, 0.5f};
|
||||
for (u32 component = 0; component < source.size(); component++) {
|
||||
AppendVMovLiteral(&code, component, std::bit_cast<u32>(source[component]));
|
||||
}
|
||||
code.push_back(EncodeExp0(0, 0xf));
|
||||
code.push_back(EncodeExp1(0, 1, 2, 3));
|
||||
AppendEnd(&code);
|
||||
return code;
|
||||
}();
|
||||
native_pixel_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(blend_pixel.data());
|
||||
ShaderMapUserData(native_pixel_regs.ps_regs.data_addr,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<uint32_t>(blend_pixel.size() * sizeof(u32))});
|
||||
const auto saved_blend = registers.GetBlendControl(0);
|
||||
const auto saved_target_info = registers.GetRenderTarget(0).info;
|
||||
const auto saved_output_mode = registers.GetShaderRegisters().target_output_mode[0];
|
||||
registers.SetTargetOutputMode(0, 4);
|
||||
auto blend_target_info = saved_target_info;
|
||||
blend_target_info.blend_bypass = false;
|
||||
registers.SetColorInfo(0, blend_target_info);
|
||||
export_mapping[0] = color.export_mapping = Prospero::ColorMappingAbgr;
|
||||
HW::BlendControl alpha_blend{};
|
||||
alpha_blend.enable = true;
|
||||
alpha_blend.color_srcblend = static_cast<uint8_t>(Prospero::BlendFactor::kSrcAlpha);
|
||||
alpha_blend.alpha_srcblend = static_cast<uint8_t>(Prospero::BlendFactor::kOne);
|
||||
alpha_blend.color_destblend = alpha_blend.alpha_destblend =
|
||||
static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusSrcAlpha);
|
||||
for (const bool separate : {false, true}) {
|
||||
alpha_blend.separate_alpha_blend = separate;
|
||||
registers.SetBlendControl(0, alpha_blend);
|
||||
const auto programs = context.GetPipelineCache().GetGraphicsPrograms(
|
||||
native_vertex_regs, native_pixel_regs, registers.GetShaderRegisters(),
|
||||
registers, user_config, export_mapping, true, native_vertex_info, pixel);
|
||||
const auto mode = separate ? ShaderAlphaBlendSource::SourceAlphaOne
|
||||
: ShaderAlphaBlendSource::SourceAlpha;
|
||||
Require(name, "logical alpha blend program", pixel.alpha_blend_source == mode &&
|
||||
pixel.dual_source_blending,
|
||||
"the separate alpha equation did not reach the compiled pixel program");
|
||||
vertex_shader = programs.vertex[0];
|
||||
pixel_shader = programs.pixel;
|
||||
vertex = native_vertex_info[0];
|
||||
constexpr std::array<float, 4> destination{0.3f, 0.3f, 0.1f, 0.2f};
|
||||
draw(pipeline(true, 2, 2), 3, std::bit_cast<std::array<u32, 4>>(destination));
|
||||
const auto blend_pixels = read_color();
|
||||
const std::array<float, 4> expected{separate ? 0.65f : 0.4f, 0.35f, 0.35f, 0.5f};
|
||||
for (size_t component = 0; component < blend_pixels.size(); component++) {
|
||||
Require(name, "logical alpha blend readback",
|
||||
std::abs(std::bit_cast<float>(blend_pixels[component]) -
|
||||
expected[component % 4]) < 0.00001f,
|
||||
"reversed blending changed logical RGB or the separate alpha equation");
|
||||
}
|
||||
}
|
||||
native_pixel_regs.ps_regs.data_addr = pixel_address;
|
||||
export_mapping[0] = color.export_mapping = {};
|
||||
registers.SetBlendControl(0, saved_blend);
|
||||
registers.SetColorInfo(0, saved_target_info);
|
||||
registers.SetTargetOutputMode(0, saved_output_mode);
|
||||
|
||||
// Captured Playroom strips contain the fullscreen triangle followed by an
|
||||
// out-of-bounds fetch exporting (0,0,0,0). Additive color reveals any extra triangle.
|
||||
static constexpr std::array<std::array<u32, 4>, 4> fourth_positions{{
|
||||
|
||||
+84
-39
@@ -12851,6 +12851,28 @@ void TestBlendMappingClassification() {
|
||||
blend.color_srcblend = static_cast<uint8_t>(Factor::kDstAlpha);
|
||||
Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
||||
"destination alpha incorrectly used the physical alpha channel");
|
||||
|
||||
blend.separate_alpha_blend = true;
|
||||
blend.color_srcblend = static_cast<uint8_t>(Factor::kSrcAlpha);
|
||||
blend.alpha_srcblend = static_cast<uint8_t>(Factor::kOne);
|
||||
blend.color_destblend = blend.alpha_destblend =
|
||||
static_cast<uint8_t>(Factor::kOneMinusSrcAlpha);
|
||||
Check(classify(Prospero::ColorMappingAbgr) == Support::SourceAlphaOne,
|
||||
"separate alpha accumulation did not preserve its unit source factor");
|
||||
const auto exact = blend;
|
||||
for (uint32_t changed = 0; changed < 6; changed++) {
|
||||
blend = exact;
|
||||
switch (changed) {
|
||||
case 0: blend.color_srcblend = static_cast<uint8_t>(Factor::kZero); break;
|
||||
case 1: blend.color_destblend = static_cast<uint8_t>(Factor::kOne); break;
|
||||
case 2: blend.alpha_srcblend = static_cast<uint8_t>(Factor::kZero); break;
|
||||
case 3: blend.alpha_destblend = static_cast<uint8_t>(Factor::kOne); break;
|
||||
case 4: blend.color_comb_fcn = static_cast<uint8_t>(Prospero::BlendOp::kSubtract); break;
|
||||
case 5: blend.alpha_comb_fcn = static_cast<uint8_t>(Prospero::BlendOp::kSubtract); break;
|
||||
}
|
||||
Check(classify(Prospero::ColorMappingAbgr) == Support::Unsupported,
|
||||
"separate alpha lowering accepted a different blend equation");
|
||||
}
|
||||
}
|
||||
|
||||
void TestLogicalAlphaBlendExport() {
|
||||
@@ -12860,58 +12882,81 @@ void TestLogicalAlphaBlendExport() {
|
||||
const auto ordinary_key = MakeStageStaticKey(pixel);
|
||||
pixel.dual_source_blending = true;
|
||||
const auto guest_key = MakeStageStaticKey(pixel);
|
||||
pixel.alpha_blend_source_remap = true;
|
||||
pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlpha;
|
||||
const auto remapped_key = MakeStageStaticKey(pixel);
|
||||
pixel.alpha_blend_source = ShaderAlphaBlendSource::SourceAlphaOne;
|
||||
const auto separate_key = MakeStageStaticKey(pixel);
|
||||
Check(ordinary_key != guest_key && guest_key != remapped_key &&
|
||||
ordinary_key != remapped_key,
|
||||
"ordinary, guest dual-source, and logical-alpha shaders share a cache key");
|
||||
ordinary_key != remapped_key && separate_key != ordinary_key &&
|
||||
separate_key != guest_key && separate_key != remapped_key,
|
||||
"ordinary, guest dual-source, and logical-alpha modes share a cache key");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.input_info.pixel = &pixel;
|
||||
for (const bool compressed : {false, true}) {
|
||||
const uint32_t shader[] = {
|
||||
// RGBA = (1000, 2, 3, 0.25), packed into two half pairs when compressed.
|
||||
EncodeVop1(0x01, 0, 255), compressed ? 0x400063d0u : 0x447a0000u,
|
||||
EncodeVop1(0x01, 1, 255), compressed ? 0x34004200u : 0x40000000u,
|
||||
EncodeVop1(0x01, 2, 255), 0x40400000u,
|
||||
EncodeVop1(0x01, 3, 255), 0x3e800000u,
|
||||
EncodeExp0(0, 0xf, false, compressed), EncodeExp1(0, 1, 2, 3),
|
||||
EncodeExp0(1, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeExp0(2, 0xf, true, false, true), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos &&
|
||||
source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos &&
|
||||
CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 &&
|
||||
source.find("out_mrt_2") == std::string::npos,
|
||||
"inactive MRT exports overwrote the logical-alpha output");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 252u) != 0,
|
||||
"ignoring inactive MRT stores discarded their valid-mask export");
|
||||
uint32_t alpha_input = 0;
|
||||
uint32_t color_input = 0;
|
||||
for (size_t i = 5; i < result.spirv.size(); i += result.spirv[i] >> 16u) {
|
||||
if ((result.spirv[i] & 0xffffu) != 79u || (result.spirv[i] >> 16u) != 9u) {
|
||||
continue;
|
||||
}
|
||||
const auto selectors = std::span(result.spirv).subspan(i + 5, 4);
|
||||
if (std::ranges::equal(selectors, std::array{3u, 3u, 3u, 3u})) {
|
||||
alpha_input = result.spirv[i + 3];
|
||||
} else if (std::ranges::equal(selectors, std::array{3u, 2u, 1u, 0u})) {
|
||||
color_input = result.spirv[i + 3];
|
||||
for (const auto mode : {ShaderAlphaBlendSource::SourceAlpha,
|
||||
ShaderAlphaBlendSource::SourceAlphaOne}) {
|
||||
pixel.alpha_blend_source = mode;
|
||||
for (const bool compressed : {false, true}) {
|
||||
const uint32_t shader[] = {
|
||||
// RGBA = (1000, 2, 3, 0.25), packed into two half pairs when compressed.
|
||||
EncodeVop1(0x01, 0, 255), compressed ? 0x400063d0u : 0x447a0000u,
|
||||
EncodeVop1(0x01, 1, 255), compressed ? 0x34004200u : 0x40000000u,
|
||||
EncodeVop1(0x01, 2, 255), 0x40400000u,
|
||||
EncodeVop1(0x01, 3, 255), 0x3e800000u,
|
||||
EncodeExp0(0, 0xf, false, compressed), EncodeExp1(0, 1, 2, 3),
|
||||
EncodeExp0(1, 0xf, false), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeExp0(2, 0xf, true, false, true), EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const auto result = RecompileForTest(shader, options);
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
const auto source = DisassembleSpirvBinary(result.spirv);
|
||||
Check(source.find("OpDecorate %out_mrt_1 Location 0") != std::string::npos &&
|
||||
source.find("OpDecorate %out_mrt_1 Index 1") != std::string::npos &&
|
||||
CountSourceOccurrences(source, "OpStore %out_mrt_1 ") == 1 &&
|
||||
source.find("out_mrt_2") == std::string::npos,
|
||||
"inactive MRT exports overwrote the logical-alpha output");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 252u) != 0,
|
||||
"ignoring inactive MRT stores discarded their valid-mask export");
|
||||
uint32_t alpha_input = 0;
|
||||
uint32_t alpha_broadcast = 0;
|
||||
uint32_t color_input = 0;
|
||||
uint32_t one = 0;
|
||||
bool unit_alpha_factor = false;
|
||||
for (size_t i = 5; i < result.spirv.size(); i += result.spirv[i] >> 16u) {
|
||||
const auto opcode = result.spirv[i] & 0xffffu;
|
||||
if (opcode == 43u && (result.spirv[i] >> 16u) == 4u &&
|
||||
result.spirv[i + 3] == 0x3f800000u) {
|
||||
one = result.spirv[i + 2];
|
||||
}
|
||||
if (opcode == 82u && (result.spirv[i] >> 16u) == 6u &&
|
||||
result.spirv[i + 3] == one && result.spirv[i + 4] == alpha_broadcast &&
|
||||
result.spirv[i + 5] == 0u) {
|
||||
unit_alpha_factor = true;
|
||||
}
|
||||
if ((result.spirv[i] & 0xffffu) != 79u || (result.spirv[i] >> 16u) != 9u) {
|
||||
continue;
|
||||
}
|
||||
const auto selectors = std::span(result.spirv).subspan(i + 5, 4);
|
||||
if (std::ranges::equal(selectors, std::array{3u, 3u, 3u, 3u})) {
|
||||
alpha_input = result.spirv[i + 3];
|
||||
alpha_broadcast = result.spirv[i + 2];
|
||||
} else if (std::ranges::equal(selectors, std::array{3u, 2u, 1u, 0u})) {
|
||||
color_input = result.spirv[i + 3];
|
||||
}
|
||||
}
|
||||
Check(alpha_input != 0 && alpha_input == color_input,
|
||||
"blend source did not broadcast logical alpha before the physical export swizzle");
|
||||
Check(unit_alpha_factor == (mode == ShaderAlphaBlendSource::SourceAlphaOne),
|
||||
"secondary ABGR output must be (1, Sa, Sa, Sa) only for separate alpha accumulation");
|
||||
}
|
||||
Check(alpha_input != 0 && alpha_input == color_input,
|
||||
"blend source did not broadcast logical alpha before the physical export swizzle");
|
||||
}
|
||||
|
||||
const uint32_t guest_shader[] = {
|
||||
EncodeExp0(0, 0xf, false), EncodeExp1(0, 1, 2, 3),
|
||||
EncodeExp0(1, 0xf), EncodeExp1(4, 5, 6, 7), EncodeSopp(0x01),
|
||||
};
|
||||
pixel.alpha_blend_source_remap = false;
|
||||
pixel.alpha_blend_source = ShaderAlphaBlendSource::None;
|
||||
pixel.target_output_mode[1] = pixel.target_output_mode[0];
|
||||
pixel.target_export_mapping = {};
|
||||
const auto guest = RecompileForTest(guest_shader, options);
|
||||
|
||||
Reference in New Issue
Block a user