mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
feat(agc): implement 11_11_10 float color targets through 32-bit uint attachments
Color format 7 (COLOR_11_11_10, float) keeps its 10-bit component in the low bits; with the reversed component order (COMP_SWAP 2) that is blue, with red in the high 11 bits. Vulkan has no such layout, so the target was refused at decode. Render it the way 10_11_11 unorm targets are rendered: an R32_UINT attachment, the reversed export mapping 0xc6, and a new ColorExportPacking::Float11_11_10 that makes the pixel shader pack each export with the format-43 image store conversion (toward zero, overflow to the largest finite value, negatives to zero, NaN kept). Blending, partial writes, 16-bit integer exports, DCC and the other component orders throw. AI-assisted: yes
This commit is contained in:
1 parent
8690d92f07
commit
49b0e805f0
10 files changed
+434
-17
No files matched your search
@@ -1527,6 +1527,8 @@ target_include_directories(agc_driver_reversed_order_blend_tests PRIVATE $<TARGE
|
||||
|
||||
target_include_directories(agc_driver_swap_alt_blend_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_packed_float_color_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_packed_unorm_color_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
target_include_directories(agc_driver_color1555_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
|
||||
@@ -675,7 +675,7 @@ std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders,
|
||||
const auto color = std::find_if(state.colors.begin(), state.colors.end(), [&](const ColorTarget& target) { return target.exportIndex == location; });
|
||||
const bool uintExport = color != state.colors.end() && color->uintExport;
|
||||
const bool packed = color != state.colors.end() && color->packing != ShaderRecompiler::ColorExportPacking::None;
|
||||
Require(signature == (uintExport || packed ? "vertex:u32x4" : "vertex:f32x4"), packed ? "fragment shader must export uint4 words to its packed 10_11_11 unorm attachments" : uintExport ? "fragment shader must export uint4 colors to its unsigned integer attachments" : "fragment shader must export float4 colors to its attachments");
|
||||
Require(signature == (uintExport || packed ? "vertex:u32x4" : "vertex:f32x4"), packed ? "fragment shader must export uint4 words to its packed color attachments" : uintExport ? "fragment shader must export uint4 colors to its unsigned integer attachments" : "fragment shader must export float4 colors to its attachments");
|
||||
locations.insert(location);
|
||||
}
|
||||
Require((!state.dualSourceBlend && state.reversedBlend == 0) || previous.secondSource == "f32x4", "dual-source blending needs a float4 second color at location 0 index 1");
|
||||
|
||||
@@ -743,6 +743,10 @@ struct DecodedColorFormat {
|
||||
ShaderRecompiler::ColorExportPacking packing = ShaderRecompiler::ColorExportPacking::None;
|
||||
};
|
||||
|
||||
const char* PackedColorTargetName(ShaderRecompiler::ColorExportPacking packing) {
|
||||
return packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 ? "11_11_10 float" : "10_11_11 unorm";
|
||||
}
|
||||
|
||||
// CB_COLOR_INFO FORMAT / NUMBER_TYPE / COMP_SWAP to a Vulkan attachment format. AMD formats list
|
||||
// components from the least significant bits, as Vulkan's non-packed formats do.
|
||||
DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number, std::uint32_t swap) {
|
||||
@@ -753,7 +757,7 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
|
||||
};
|
||||
const bool alternate = swap == 1;
|
||||
const auto single = [&](VkFormat vkFormat, std::uint32_t bytes) { return DecodedColorFormat{vkFormat, bytes, static_cast<std::uint8_t>((0xe4u & ~3u) | swap)}; };
|
||||
if (swap > 1 && format != 1 && format != 2 && format != 4 && format != 10 && format != 12 && format != 16) return fail();
|
||||
if (swap > 1 && format != 1 && format != 2 && format != 4 && format != 7 && format != 10 && format != 12 && format != 16) return fail();
|
||||
switch (format) {
|
||||
case 1:
|
||||
if (number == unorm) return single(VK_FORMAT_R8_UNORM, 1);
|
||||
@@ -791,6 +795,9 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
|
||||
if (swap != 0 || (number != floating && number != unorm)) return fail();
|
||||
if (number == unorm) return {VK_FORMAT_R32_UINT, 4, 0xe4u, ShaderRecompiler::ColorExportPacking::Unorm10_11_11};
|
||||
return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
|
||||
case 7:
|
||||
if (swap != 2 || number != floating) return fail();
|
||||
return {VK_FORMAT_R32_UINT, 4, 0xc6u, ShaderRecompiler::ColorExportPacking::Float11_11_10};
|
||||
case 9:
|
||||
// COLOR_2_10_10_10 keeps red in the low bits, the Vulkan A2B10G10R10 packing.
|
||||
if (number != unorm) return fail();
|
||||
@@ -1306,10 +1313,11 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
}
|
||||
}
|
||||
if (color.packing != ShaderRecompiler::ColorExportPacking::None) {
|
||||
const std::string packed = std::string(color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 ? "an " : "a ") + PackedColorTargetName(color.packing);
|
||||
const auto slotExport = (exportFormat >> (4u * color.exportIndex)) & 0xfu;
|
||||
if (slotExport == 5 || slotExport == 6) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " into a 10_11_11 unorm target is unsupported");
|
||||
Require(!state.blendEnable, "blending into a 10_11_11 unorm color target is unsupported");
|
||||
Require((exportedMask & 7u) == 7u, "partial writes of a 10_11_11 unorm color target are unsupported");
|
||||
if (slotExport == 5 || slotExport == 6) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " into " + packed + " target is unsupported");
|
||||
Require(!state.blendEnable, "blending into " + packed + " color target is unsupported");
|
||||
Require((exportedMask & 7u) == 7u, "partial writes of " + packed + " color target are unsupported");
|
||||
}
|
||||
if (state.blendEnable && !reversed && (secondSource(state.srcColorBlendFactor) || secondSource(state.dstColorBlendFactor) || secondSource(state.srcAlphaBlendFactor) || secondSource(state.dstAlphaBlendFactor))) {
|
||||
const auto addition = [](VkBlendOp op) { return op == VK_BLEND_OP_ADD || op == VK_BLEND_OP_SUBTRACT || op == VK_BLEND_OP_REVERSE_SUBTRACT; };
|
||||
@@ -1551,8 +1559,8 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
|
||||
color.format = decoded.format;
|
||||
color.componentMapping = decoded.componentMapping;
|
||||
color.packing = decoded.packing;
|
||||
Require(color.packing == ShaderRecompiler::ColorExportPacking::None || (info & 0x10000000u) == 0, "DCC-compressed 10_11_11 unorm color targets are unsupported");
|
||||
Require(color.packing == ShaderRecompiler::ColorExportPacking::None || (info & 0x00040000u) == 0, "truncating (ROUND_MODE) 10_11_11 unorm color targets are unsupported");
|
||||
if (color.packing != ShaderRecompiler::ColorExportPacking::None && (info & 0x10000000u) != 0) throw std::runtime_error("AGC graphics: DCC-compressed " + std::string(PackedColorTargetName(color.packing)) + " color targets are unsupported");
|
||||
Require(color.packing != ShaderRecompiler::ColorExportPacking::Unorm10_11_11 || (info & 0x00040000u) == 0, "truncating (ROUND_MODE) 10_11_11 unorm color targets are unsupported");
|
||||
for (std::uint32_t word = 0; word < 2; ++word) {
|
||||
const auto clear = find(cx, 0x323 + word + stride);
|
||||
color.clearWords[word] = clear == cx.end() ? 0u : clear->second;
|
||||
|
||||
@@ -6956,13 +6956,78 @@ void unorm10_11_11TargetTests() {
|
||||
return AgcDriver::Graphics::ValidateShaders(shaders, target, subgroup, false);
|
||||
};
|
||||
Require(validate(packed, state) == std::set<std::uint32_t>{0u}, "the packed export did not reach attachment 0");
|
||||
expectFailure([&] { validate(unpacked, state); }, "uint4 words to its packed 10_11_11 unorm attachments");
|
||||
expectFailure([&] { validate(unpacked, state); }, "uint4 words to its packed color attachments");
|
||||
auto plainState = state;
|
||||
plainState.colors[0].packing = ColorExportPacking::None;
|
||||
expectFailure([&] { validate(packed, plainState); }, "float4 colors");
|
||||
auto uintExport = request;
|
||||
uintExport.context.pixel->targetOutputMode[0] = 7;
|
||||
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(uintExport)); }, "fragment output 0 has no 10_11_11 unorm packing path");
|
||||
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(uintExport)); }, "fragment output 0 has no color export packing path");
|
||||
}
|
||||
|
||||
void float11_11_10TargetTests() {
|
||||
using ShaderRecompiler::ColorExportPacking;
|
||||
auto queue = makeState();
|
||||
queue.context[0x31c] = (queue.context.at(0x31c) & ~0x1f7cu) | (7u << 2u) | (7u << 8u) | (2u << 11u) | 0x40000u;
|
||||
queue.context[0x1b3] = 2;
|
||||
queue.context[0x1b4] = 2;
|
||||
const auto state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.color.format == VK_FORMAT_R32_UINT && state.color.elementBytes == 4 && state.color.bytes == colorMemory.size() && state.color.componentMapping == 0xc6u && state.color.packing == ColorExportPacking::Float11_11_10, "an 11_11_10 float target did not decode as a packed R32_UINT attachment with blue in the low bits");
|
||||
const auto packings = AgcDriver::Graphics::ExportPackings(state);
|
||||
Require(packings[0] == ColorExportPacking::Float11_11_10 && std::all_of(packings.begin() + 1, packings.end(), [](ColorExportPacking packing) { return packing == ColorExportPacking::None; }), "export 0 did not carry the 11_11_10 float packing alone");
|
||||
Require(AgcDriver::Graphics::ExportMappings(state)[0] == 0xc6u, "export 0 did not reverse its components into the 11_11_10 float target");
|
||||
auto changed = queue;
|
||||
changed.context[0x31c] &= ~0x40000u;
|
||||
Require(AgcDriver::Graphics::DecodeState(changed).color.packing == ColorExportPacking::Float11_11_10, "an 11_11_10 float target without ROUND_MODE was refused");
|
||||
for (const auto swap : {0u, 1u, 3u}) {
|
||||
changed = queue;
|
||||
changed.context[0x31c] = (changed.context[0x31c] & ~0x1800u) | (swap << 11u);
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type 7 component swap " + std::to_string(swap));
|
||||
}
|
||||
for (const auto number : {0u, 4u}) {
|
||||
changed = queue;
|
||||
changed.context[0x31c] = (changed.context[0x31c] & ~0x700u) | (number << 8u) | 0x8000u;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type " + std::to_string(number) + " component swap 2");
|
||||
}
|
||||
changed = queue;
|
||||
changed.context[0x1e0] = 0x60010001u;
|
||||
for (std::uint32_t i = 0; i < 4; ++i) changed.context[0x105 + i] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "blending into an 11_11_10 float color target");
|
||||
changed = queue;
|
||||
changed.context[0x8e] = 6;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "partial writes of an 11_11_10 float color target");
|
||||
changed.context[0x8e] = 7;
|
||||
Require(AgcDriver::Graphics::DecodeState(changed).color.packing == ColorExportPacking::Float11_11_10, "an RGB write of an 11_11_10 float target was refused");
|
||||
for (const auto format : {5u, 6u}) {
|
||||
changed = queue;
|
||||
changed.context[0x1c5] = format;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "color export format " + std::to_string(format) + " into an 11_11_10 float target");
|
||||
}
|
||||
changed = queue;
|
||||
changed.context[0x1c5] = 4;
|
||||
Require(AgcDriver::Graphics::DecodeState(changed).color.packing == ColorExportPacking::Float11_11_10, "an FP16_ABGR export into an 11_11_10 float target was refused");
|
||||
changed = queue;
|
||||
changed.context[0x31c] |= 0x10000000u;
|
||||
changed.context[0x325] = 0x1234;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "DCC-compressed 11_11_10 float color targets");
|
||||
|
||||
constexpr std::array<std::uint32_t, 3> code{0xf800180fu, 0x07060504u, 0xbf810000u};
|
||||
const auto request = packedExportRequest(queue, state, code);
|
||||
auto unorm = request;
|
||||
unorm.context.pixel->targetExportPacking[0] = ColorExportPacking::Unorm10_11_11;
|
||||
const ShaderRecompiler::RequestSerializer serializer;
|
||||
Require(serializer.Deserialize(serializer.Serialize(request)).request.context.pixel->targetExportPacking == request.context.pixel->targetExportPacking, "the 11_11_10 float packing was lost in serialization");
|
||||
const auto packed = ShaderRecompiler::Recompile(request);
|
||||
const auto unormPacked = ShaderRecompiler::Recompile(unorm);
|
||||
Require(packed.variantId == unormPacked.variantId && packed.PipelineVariantId() != unormPacked.PipelineVariantId(), "the 11_11_10 float packing did not key its own specialization of the shared artifact");
|
||||
ShaderRecompiler::RecompileResult vertex;
|
||||
vertex.spirv = makeModule({});
|
||||
const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &packed, 0}}};
|
||||
Require(AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false) == std::set<std::uint32_t>{0u}, "the packed float export did not reach attachment 0");
|
||||
auto invalid = request;
|
||||
invalid.context.pixel->targetExportPacking[0] = static_cast<ColorExportPacking>(3);
|
||||
expectFailure([&] { static_cast<void>(ShaderRecompiler::Recompile(invalid)); }, "invalid prepared fragment export packing");
|
||||
}
|
||||
|
||||
AgcDriver::QueueState dualSourceQueue() {
|
||||
@@ -8299,6 +8364,7 @@ int main(int argc, char** argv) {
|
||||
memoryValueFillTests();
|
||||
ZExportTests();
|
||||
unorm10_11_11TargetTests();
|
||||
float11_11_10TargetTests();
|
||||
DepthBoundsBiasTests();
|
||||
conservativeZExportTests();
|
||||
earlyFragmentTestsTests();
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::ColorTargetLayout;
|
||||
using AgcDriver::Graphics::ColorTileMode;
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::uint32_t Width = 256;
|
||||
constexpr std::uint32_t Height = 128;
|
||||
constexpr std::uint32_t ScissorWidth = 128;
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
constexpr std::uint32_t Float11_11_10Info = 0x4171cu;
|
||||
constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u;
|
||||
constexpr std::uint32_t LinearAttrib3 = 0x09000000u;
|
||||
constexpr std::uint32_t Float32Export = 9;
|
||||
constexpr std::uint32_t Float16Export = 4;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
|
||||
0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<float, 4>, 3> Triangle{{
|
||||
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
|
||||
}};
|
||||
|
||||
alignas(256) std::array<std::array<std::uint32_t, 64>, 32> Programs{};
|
||||
std::size_t programCount = 0;
|
||||
|
||||
std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) {
|
||||
const std::uint32_t infinity = 0x1fu << mantissaBits;
|
||||
if (std::isnan(value)) {
|
||||
const auto top = (std::bit_cast<std::uint32_t>(value) & 0x7fffffu) >> (23u - mantissaBits);
|
||||
return infinity | (top == 0u ? 1u : top);
|
||||
}
|
||||
if (!(value > 0.0f)) return 0;
|
||||
if (std::isinf(value)) return infinity;
|
||||
const double magnitude = value;
|
||||
const double scale = std::ldexp(1.0, static_cast<int>(mantissaBits));
|
||||
if (magnitude < std::ldexp(1.0, -14)) return static_cast<std::uint32_t>(std::floor(magnitude * std::ldexp(1.0, 14) * scale));
|
||||
int exponent = 0;
|
||||
const double fraction = std::frexp(magnitude, &exponent);
|
||||
if (exponent - 1 > 15) return infinity - 1u;
|
||||
return (static_cast<std::uint32_t>(exponent - 1 + 15) << mantissaBits) | static_cast<std::uint32_t>(std::floor((fraction * 2.0 - 1.0) * scale));
|
||||
}
|
||||
|
||||
std::uint32_t Packed(std::array<float, 3> rgb) {
|
||||
return SmallFloat(rgb[2], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[0], 6) << 21u);
|
||||
}
|
||||
|
||||
std::uint32_t Half(float value) {
|
||||
Require(value == 0.0f || (value >= 0.0625f && value <= 1.0f), "the test's half values are normal and at most 1");
|
||||
if (value == 0.0f) return 0;
|
||||
const auto bits = std::bit_cast<std::uint32_t>(value);
|
||||
Require((bits & 0x1fffu) == 0, "the test's half values are exact");
|
||||
return ((((bits >> 23u) & 0xffu) - 112u) << 10u) | ((bits >> 13u) & 0x3ffu);
|
||||
}
|
||||
|
||||
std::span<const std::uint32_t> Program(const std::array<std::uint32_t, 4>& vgprs, bool compressed) {
|
||||
Require(programCount < Programs.size(), "too many test programs");
|
||||
auto& code = Programs[programCount++];
|
||||
std::size_t at = 0;
|
||||
for (std::uint32_t vgpr = 0; vgpr < (compressed ? 2u : 4u); ++vgpr) {
|
||||
code[at++] = 0x7e0002ffu | ((4u + vgpr) << 17u);
|
||||
code[at++] = vgprs[vgpr];
|
||||
}
|
||||
code[at++] = compressed ? 0xf8001c0fu : 0xf800180fu;
|
||||
code[at++] = compressed ? 0x00000504u : 0x07060504u;
|
||||
code[at++] = 0xbf810000u;
|
||||
return std::span<const std::uint32_t>(code.data(), at);
|
||||
}
|
||||
|
||||
std::span<const std::uint32_t> Float32Program(std::array<float, 3> rgb) {
|
||||
return Program({std::bit_cast<std::uint32_t>(rgb[0]), std::bit_cast<std::uint32_t>(rgb[1]), std::bit_cast<std::uint32_t>(rgb[2]), std::bit_cast<std::uint32_t>(1.0f)}, false);
|
||||
}
|
||||
|
||||
std::span<const std::uint32_t> Float16Program(std::array<float, 3> rgb) {
|
||||
return Program({Half(rgb[0]) | (Half(rgb[1]) << 16u), Half(rgb[2]) | (Half(1.0f) << 16u), 0, 0}, true);
|
||||
}
|
||||
|
||||
struct Block {
|
||||
explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
|
||||
#ifdef _WIN32
|
||||
if (watched) {
|
||||
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
|
||||
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
|
||||
} else {
|
||||
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
}
|
||||
#else
|
||||
if (watched) {
|
||||
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Require(raw != MAP_FAILED, "cannot map the color target");
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
||||
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
|
||||
if (aligned != begin) munmap(raw, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
|
||||
data = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
|
||||
} else {
|
||||
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
|
||||
}
|
||||
#endif
|
||||
Require(data != nullptr, "cannot allocate the color target");
|
||||
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched");
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(data, bytes, true, true, true);
|
||||
}
|
||||
~Block() {
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(data);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (watched) {
|
||||
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
|
||||
} else {
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
}
|
||||
#else
|
||||
if (watched) {
|
||||
munmap(data, bytes);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
|
||||
} else {
|
||||
std::free(data);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Block(const Block&) = delete;
|
||||
Block& operator=(const Block&) = delete;
|
||||
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
|
||||
std::size_t bytes;
|
||||
bool watched;
|
||||
std::uint8_t* data = nullptr;
|
||||
};
|
||||
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3) {
|
||||
AgcDriver::Registers cx;
|
||||
cx[0x318] = static_cast<std::uint32_t>(block.Address() >> 8u);
|
||||
cx[0x31b] = 0;
|
||||
cx[0x31c] = Float11_11_10Info;
|
||||
cx[0x31d] = 0;
|
||||
cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u);
|
||||
cx[0x3b8] = attrib3;
|
||||
cx[0x390] = static_cast<std::uint32_t>(block.Address() >> 40u);
|
||||
auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
|
||||
Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == 0xc6u && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface");
|
||||
return color;
|
||||
}
|
||||
|
||||
void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, std::uint32_t exportFormat, std::span<const std::uint32_t> pixelCode) {
|
||||
const auto target = device.Target();
|
||||
constexpr std::uint32_t waveSize = 64;
|
||||
std::vector<std::uint32_t> vertexUserData(4, 0u);
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(Triangle.data());
|
||||
const std::array<std::uint32_t, 4> vertexBuffer{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (16u << 16u), static_cast<std::uint32_t>(Triangle.size()), 0x01016facu};
|
||||
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
|
||||
ShaderRecompiler::RecompileRequest vertex{
|
||||
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
|
||||
{waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
|
||||
target,
|
||||
{0, 0, 0, 64}
|
||||
};
|
||||
vertex.useCache = false;
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
AgcDriver::Graphics::State state{};
|
||||
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
|
||||
state.colors = {color};
|
||||
state.color = color;
|
||||
state.hasColorTarget = true;
|
||||
state.renderExtent = {Width, Height};
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {ScissorWidth, Height}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
VkPipelineColorBlendAttachmentState blend{};
|
||||
blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
|
||||
state.blends = {blend};
|
||||
state.blend = blend;
|
||||
state.blendConstants = {};
|
||||
|
||||
const auto codeAddress = reinterpret_cast<std::uintptr_t>(pixelCode.data());
|
||||
const AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, exportFormat}};
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{codeAddress, std::as_bytes(pixelCode)}}};
|
||||
ShaderRecompiler::RecompileRequest fragment{
|
||||
{ShaderStage::Fragment, codeAddress, pixelCode, 0, {}},
|
||||
{waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory},
|
||||
target,
|
||||
PixelPushLayout(vertexPush, target)
|
||||
};
|
||||
const auto handle = ShaderRecompiler::PrepareShader(fragment);
|
||||
AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}};
|
||||
snapshot.prepared->entries.push_back({0, handle});
|
||||
fragment.shader.code = snapshot.code;
|
||||
fragment.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(context, AgcDriver::Graphics::ExportMappings(state));
|
||||
fragment.context.pixel->targetExportPacking = AgcDriver::Graphics::ExportPackings(state);
|
||||
const ShaderRecompiler::SrtRuntime runtime{};
|
||||
const auto materialize = [&](const ShaderRecompiler::RecompileRequest& request) {
|
||||
const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request);
|
||||
return invocation.Materialize(*invocation.Capture(runtime));
|
||||
};
|
||||
const auto pixelResult = materialize(fragment);
|
||||
auto unpacked = fragment;
|
||||
unpacked.context.pixel->targetExportPacking.fill(ShaderRecompiler::ColorExportPacking::None);
|
||||
const auto plain = materialize(unpacked);
|
||||
Require(pixelResult->variantId == ShaderRecompiler::GetPreparedArtifact(*handle).variantId && plain->variantId == pixelResult->variantId, "the draw did not use the registered pixel shader");
|
||||
Require(plain->PipelineVariantId() != pixelResult->PipelineVariantId() && materialize(fragment)->PipelineVariantId() == pixelResult->PipelineVariantId(), "the 11_11_10 float packing did not key its own specialization");
|
||||
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
|
||||
{ShaderStage::Vertex, &vertexResult, 0},
|
||||
{ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)}
|
||||
}};
|
||||
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
|
||||
device.Draw(state, draw, shaders);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> ReadBack(AgcDriver::VulkanDevice& device, const Block& block) {
|
||||
AgcDriver::Graphics::StorageTexture::FlushPending(block.Address(), block.bytes, nullptr, "test read-back");
|
||||
device.WaitIdle();
|
||||
std::vector<std::uint32_t> stored(block.bytes / 4u);
|
||||
AgcDriver::GuestMemory::Read(block.Address(), std::as_writable_bytes(std::span(stored)), 1);
|
||||
return stored;
|
||||
}
|
||||
|
||||
void ExpectSurface(AgcDriver::VulkanDevice& device, const Block& block, const ColorTargetLayout& layout, const std::vector<std::uint32_t>& before, std::uint32_t word, const std::string& what) {
|
||||
const auto stored = ReadBack(device, block);
|
||||
for (std::uint32_t y = 0; y < Height; ++y) {
|
||||
for (std::uint32_t x = 0; x < Width; ++x) {
|
||||
const auto index = layout.Offset(x, y) / 4u;
|
||||
const bool inside = x < ScissorWidth;
|
||||
const auto expected = inside ? word : before[index];
|
||||
if (stored[index] == expected) continue;
|
||||
char detail[160];
|
||||
std::snprintf(detail, sizeof(detail), ": pixel (%u, %u) %s the scissor holds 0x%08x, expected 0x%08x", x, y, inside ? "inside" : "outside", stored[index], expected);
|
||||
throw std::runtime_error(what + detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Fill(Block& block) {
|
||||
std::vector<std::uint32_t> words(block.bytes / 4u);
|
||||
for (std::size_t index = 0; index < words.size(); ++index) words[index] = static_cast<std::uint32_t>(index * 0x9e3779b9u);
|
||||
std::memcpy(block.data, words.data(), block.bytes);
|
||||
return words;
|
||||
}
|
||||
|
||||
std::string Describe(std::array<float, 3> rgb) {
|
||||
char text[96];
|
||||
std::snprintf(text, sizeof(text), "(%.9g, %.9g, %.9g)", rgb[0], rgb[1], rgb[2]);
|
||||
return text;
|
||||
}
|
||||
|
||||
void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind) {
|
||||
const ColorTargetLayout layout(Width, Height, mode, 4);
|
||||
Require(layout.Bytes() == block.bytes, "the test surface size disagrees with its layout");
|
||||
const auto color = DecodeTarget(block, attrib3);
|
||||
auto before = Fill(block);
|
||||
const float nan = std::numeric_limits<float>::quiet_NaN();
|
||||
const float infinity = std::numeric_limits<float>::infinity();
|
||||
const std::array<std::array<float, 3>, 10> cases{{
|
||||
{1.0f, 0.5f, 0.25f},
|
||||
{2.0f, 0.75f, 65024.0f},
|
||||
{1.0f + 1.9f / 64.0f, 1.0f + 0.9f / 64.0f, 1.0f + 1.9f / 32.0f},
|
||||
{65000.0f, 70000.0f, 1.0e30f},
|
||||
{nan, -1.0f, infinity},
|
||||
{-infinity, -0.0f, std::numeric_limits<float>::denorm_min()},
|
||||
{std::ldexp(1.0f, -15), std::ldexp(3.0f, -20), std::ldexp(1.0f, -19)},
|
||||
{std::ldexp(1.0f, -21), std::ldexp(1.9f, -20), std::ldexp(1.9f, -19)},
|
||||
{std::numeric_limits<float>::min(), 0.1f, 1.0f / 3.0f},
|
||||
{3.0f, 5.0f, 7.0f},
|
||||
}};
|
||||
for (const auto& rgb : cases) {
|
||||
Draw(device, color, Float32Export, Float32Program(rgb));
|
||||
ExpectSurface(device, block, layout, before, Packed(rgb), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target");
|
||||
before = ReadBack(device, block);
|
||||
}
|
||||
const std::array<float, 3> halves{0.5f, 0.75f, 0.125f};
|
||||
Draw(device, color, Float16Export, Float16Program(halves));
|
||||
ExpectSurface(device, block, layout, before, Packed(halves), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Block tiled(ColorTargetLayout(Width, Height, ColorTileMode::RenderTarget, 4).Bytes());
|
||||
Block linear(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 4).Bytes());
|
||||
TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, "tiled");
|
||||
TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, "linear");
|
||||
std::puts("11_11_10 float color target tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ ConservativeZExport readConservativeZExport(Reader& reader) {
|
||||
|
||||
ColorExportPacking readColorExportPacking(Reader& reader) {
|
||||
const auto value = reader.ReadU8();
|
||||
if (value > static_cast<std::uint8_t>(ColorExportPacking::Unorm10_11_11)) {
|
||||
if (value > static_cast<std::uint8_t>(ColorExportPacking::Float11_11_10)) {
|
||||
throw std::runtime_error("RequestSerializer: invalid ColorExportPacking value");
|
||||
}
|
||||
return static_cast<ColorExportPacking>(value);
|
||||
|
||||
@@ -32,7 +32,7 @@ inline std::vector<std::uint32_t> SpecializeFragmentOutputs(std::vector<std::uin
|
||||
reversed = constant.value;
|
||||
}
|
||||
if (constant.id < PipelineSpecialization::ExportPackingBase || constant.id >= PipelineSpecialization::ExportPackingBase + 8u) continue;
|
||||
if (constant.value > static_cast<std::uint32_t>(ColorExportPacking::Unorm10_11_11)) throw std::runtime_error("invalid prepared fragment export packing");
|
||||
if (constant.value > static_cast<std::uint32_t>(ColorExportPacking::Float11_11_10)) throw std::runtime_error("invalid prepared fragment export packing");
|
||||
if (constant.value != static_cast<std::uint32_t>(ColorExportPacking::None)) packed.insert(constant.id - PipelineSpecialization::ExportPackingBase);
|
||||
}
|
||||
if (packed.empty() && !dualSource && reversed == 0u) return words;
|
||||
@@ -81,7 +81,7 @@ inline std::vector<std::uint32_t> SpecializeFragmentOutputs(std::vector<std::uin
|
||||
for (const auto location : packed) {
|
||||
const auto variable = output(location);
|
||||
if (variable == 0u) continue;
|
||||
if (!specializedIds.contains(PipelineSpecialization::ExportPackingBase + location)) throw std::runtime_error("fragment output " + std::to_string(location) + " has no 10_11_11 unorm packing path");
|
||||
if (!specializedIds.contains(PipelineSpecialization::ExportPackingBase + location)) throw std::runtime_error("fragment output " + std::to_string(location) + " has no color export packing path");
|
||||
if (!floatVectorPointer(outputs.at(variable))) throw std::runtime_error("packed fragment output " + std::to_string(location) + " is not a float4");
|
||||
retyped.insert(variable);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@ enum class ConservativeZExport : std::uint8_t {
|
||||
|
||||
enum class ColorExportPacking : std::uint8_t {
|
||||
None,
|
||||
Unorm10_11_11
|
||||
Unorm10_11_11,
|
||||
Float11_11_10
|
||||
};
|
||||
|
||||
enum class RasterPrimitive : std::uint8_t {
|
||||
|
||||
@@ -254,8 +254,8 @@ std::uint32_t MrtOutputMode(const SpirvEmitterState& state, const ExportInfo& ex
|
||||
return state.inputInfo.pixel->targetOutputMode[exp.index];
|
||||
}
|
||||
|
||||
std::uint32_t PackUnorm10_11_11(SpirvEmitterState& state, std::uint32_t color) {
|
||||
const auto info = GetFormatInfo(IrBufferFormat::Format11_11_10UNorm);
|
||||
std::uint32_t PackExport(SpirvEmitterState& state, IrBufferFormat format, std::uint32_t color) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
auto packed = ConstantU32(state, 0u);
|
||||
for (std::uint32_t component = 0; component < info.componentCount; component++) {
|
||||
const auto value = state.module.AllocateId();
|
||||
@@ -270,8 +270,9 @@ std::uint32_t PackUnorm10_11_11(SpirvEmitterState& state, std::uint32_t color) {
|
||||
|
||||
std::uint32_t SelectExportPacking(SpirvEmitterState& state, std::uint32_t target, std::uint32_t color) {
|
||||
const auto packing = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportPackingBase + target, static_cast<std::uint32_t>(ColorExportPacking::None));
|
||||
const auto packed = EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast<std::uint32_t>(ColorExportPacking::Unorm10_11_11));
|
||||
const auto word = PackUnorm10_11_11(state, color);
|
||||
const auto packed = EmitCompareU32Constant(state, spv::OpINotEqual, packing, static_cast<std::uint32_t>(ColorExportPacking::None));
|
||||
const auto floating = EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast<std::uint32_t>(ColorExportPacking::Float11_11_10));
|
||||
const auto word = Select(state, TypeU32(state), floating, PackExport(state, IrBufferFormat::Format10_11_11Float, color), PackExport(state, IrBufferFormat::Format11_11_10UNorm, color));
|
||||
std::array<std::uint32_t, 4> components{};
|
||||
for (std::uint32_t component = 0; component < components.size(); ++component) {
|
||||
const auto unpacked = state.module.AllocateId();
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
- The [dispatches that follow a compute queue's wait](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp) (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (`APS5_GROUP_CAPTURE_ALL_QUEUES=1` includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory.
|
||||
- [PA_CL_CLIP_CNTL](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented.
|
||||
- [10_11_11 unorm color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion.
|
||||
- [11_11_10 float color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export toward zero like format-43 image stores (overflow to the largest finite value, negatives to zero, NaN kept), which is assumed to match the CB's conversion; only the reversed component order (COMP_SWAP 2) is accepted.
|
||||
- [PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver) uses Vulkan's last-vertex mode for native primitives. Bit 19 follows [opengnm's `flatshade_first` mapping](https://github.com/blackbearreloaded/ps5-opengl/blob/db752da4c89380af228497b5b6852163a4e61069/src/gallium/ps5/ps5_screen.c#L2244); vertex selection follows [Vulkan](https://docs.vulkan.org/spec/latest/chapters/drawing.html#drawing-primitive-topologies), without console measurements. A [rect-list](../../../core/shader/recompiler/SpirvBackend/src/RectListShaders.cpp) draw in last mode gives all four corners of each rectangle the flat inputs of its third vertex through the generated control stage, as first mode gives them those of its first, so Vulkan's choice of provoking vertex for the tessellated triangles does not matter; which vertex the console takes for a rectangle is not measured either. Last mode throws without `VK_EXT_provoking_vertex`, for generated mesh or tessellation primitives, explicit per-vertex flat inputs, and triangle strips whose pixel shader reads a vertex slot itself (`v_interp_mov_f32`, or 16-bit interpolation, which is not checked yet), since the vertex order of odd strip triangles [depends on the device](https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceFragmentShaderBarycentricPropertiesKHR.html). `v_interp_p1_f32` and `v_interp_p2_f32` read P10 and P20 against the guest's I and J, which come from `gl_BaryCoordKHR` in the same vertex order as the per-vertex values, so the order cancels out and strips with only those reads draw. A FLAT_SHADE input without pass-through that is read through `v_interp_mov_f32` P10 and not P20 [returns the provoking vertex's value](../../../core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp), like P0. ps5-opengl reads every flat input that way, and its driver passes the GL 4.6 CTS on a PS5 (#1413). The lane itself was not measured on the console, so with the first provoking vertex such a shader throws.
|
||||
- [Dual-source blending](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) takes the MRT1 export as the second source of the MRT0 blend, as RADV and radeonsi program it; what the CB does with other export or target layouts is unknown, so they throw.
|
||||
- [Blending into reversed-order color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - 8_8_8_8 and 16_16_16_16 targets with `COMP_SWAP` 2 or 3 keep the exported alpha in their first channel and red or blue in their fourth, which Vulkan would blend with the color and the alpha equation respectively. The color block is taken to blend in shader export order and apply `COMP_SWAP` afterwards: the alpha equation and every source-alpha factor use the exported alpha, and the destination alpha is the first channel. This follows the `CB_TARGET_MASK` text of the AMD [SI](https://xorg.freedesktop.org/docs/AMD/old/SI_3D_registers.pdf) (p. 229) and [CIK](https://xorg.freedesktop.org/docs/AMD/old/CIK_3D_registers_v2.pdf) (p. 238) register references ("the order of components after blending and before COMP_SWAP is applied"). RADV builds `CB_BLENDn_CONTROL` without regard to the swap ([radv_pipeline_graphics.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/vulkan/radv_pipeline_graphics.c#L650-L709)) and renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 with `SWAP_STD_REV` and `SWAP_ALT_REV` ([vk_format.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/vulkan/util/vk_format.c#L43-L48), [ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c#L609-L624)). Mesa CI's Navi10 (gfx1010) CTS run ([job 112381553](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/112381553), [112381554](https://gitlab.freedesktop.org/mesa/mesa/-/jobs/112381554)) passes all 400 `blend.format.<format>.states` cases of each of those formats, including separate alpha and destination-alpha factors. It is not measured on gfx1013 or on 8_8_8_8 and 16_16_16_16 targets. Factor pairs Vulkan applies per channel are used directly. Otherwise the pixel shader only copies values: it writes the exported alpha, each channel's own export or the constants 0 and 1 into a second color at location 0 index 1 (and 1 into a channel of the first color whose source factor is ONE, moving its export into the factor), and the blend forms every factor, `1 -` and product from those copies through `SRC1_COLOR`/`SRC1_ALPHA` factors. The blend unit therefore rounds them as it rounds a plain Vulkan blend of the same export; Vulkan does not require a second-source color to be converted like the first, but on an RTX 5080 and lavapipe every reversed-order case of the execution test is bit-identical to the same draw into a `SWAP_STD` target, which the test checks. Factor combinations that copies cannot express (for example `ONE_MINUS_SRC_ALPHA` in the alpha equation with `SRC_COLOR` in the color equation), sRGB, SNORM and integer targets, constant and second-source factors, destination-alpha and `SRC_ALPHA_SATURATE` factors in the color equation, different color and alpha operations, MIN or MAX with factors other than ONE, other color exports when the blend needs the second color, and devices without `dualSrcBlend` throw. Gran Turismo 7 (PPSA01317) blends `CB_BLEND0_CONTROL` 0x65010504 into such a target (#4782).
|
||||
|
||||
Reference in new issue
Block a user