gpu: preserve low-alpha 2/10/10/10 render-target storage

This commit is contained in:
nmzik
2026-10-28 22:29:50 +01:00
parent 77458260f1
commit 963aaeb3b6
6 changed files with 178 additions and 129 deletions
+1
View File
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true}, {BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false}, {BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false}, {BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
{BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false}, {BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true}, {BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false}, {BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
@@ -17,7 +17,7 @@
namespace Libs::Graphics { namespace Libs::Graphics {
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 }; enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 };
enum class VideoOutCompression : uint8_t { enum class VideoOutCompression : uint8_t {
Uncompressed, Uncompressed,
@@ -105,6 +105,8 @@ struct ImageInfo {
switch (guest_format) { switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float: case Prospero::BufferFormat::k10_11_11Float:
return ColorTransform::Reverse10_11_11; return ColorTransform::Reverse10_11_11;
case Prospero::BufferFormat::k2_10_10_10UNorm:
return ColorTransform::Rotate2_10_10_10;
default: return ColorTransform::None; default: return ColorTransform::None;
} }
} }
@@ -32,8 +32,12 @@ struct HostFormatInfo {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format, HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) { Prospero::ChannelOrder order) {
if (guest_format == Prospero::BufferFormat::k10_11_11Float) { switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float:
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra}; return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k2_10_10_10UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr};
default: break;
} }
if (order == Prospero::ChannelOrder::kAlt) { if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) { switch (guest_format) {
@@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 || block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0); info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) || EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
(transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u)); ((transform == ColorTransform::Reverse10_11_11 ||
transform == ColorTransform::Rotate2_10_10_10) && block.bytes_per_element != 4u));
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) { const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true); return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
}; };
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
uint32_t element_bytes = 0; uint32_t element_bytes = 0;
switch (transform) { switch (transform) {
case ColorTransform::SwapBgra16: element_bytes = 8; break; case ColorTransform::SwapBgra16: element_bytes = 8; break;
case ColorTransform::Reverse10_11_11: element_bytes = 4; break; case ColorTransform::Reverse10_11_11:
case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break;
default: EXIT("invalid color transform\n"); default: EXIT("invalid color transform\n");
} }
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 || EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
@@ -1,4 +1,4 @@
// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly. // ColorTransform values match imageInfo.h. Preserve the component bit fields exactly.
uvec2 transform_color(uvec2 value, uint transform, bool to_host) { uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
switch (transform) { switch (transform) {
case 1u: case 1u:
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
} }
break; break;
} }
case 3u:
if (to_host) {
value.x = (value.x >> 2u) | (value.x << 30u);
} else {
value.x = (value.x << 2u) | (value.x >> 30u);
}
break;
} }
return value; return value;
} }
+74 -41
View File
@@ -13808,38 +13808,64 @@ public:
resources.UnmapMemory(base, allocation_size); resources.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
} }
struct TransferCase {
Prospero::ChannelLayout layout;
Prospero::ChannelType type;
Prospero::BufferFormat format;
Prospero::BufferFormat alias_format;
vk::Format native_format;
u32 swizzle;
std::array<u32, 4> words;
std::array<std::array<float, 4>, 4> channels;
std::array<float, 4> clear;
u32 clear_word;
std::array<float, 4> alias_clear;
u32 alias_word;
};
constexpr std::array transfers{
TransferCase{Prospero::ChannelLayout::k10_11_11, Prospero::ChannelType::kFloat,
Prospero::BufferFormat::k10_11_11Float, Prospero::BufferFormat::k11_11_10Float,
vk::Format::eB10G11R11UfloatPack32, DstSel(4, 5, 6, 1),
{0x883005e1u, 0x7c0e81c0u, 0x800f0000u, 0x98120280u},
{{{1.03125f, 2.03125f, 4.0625f, 1.f}, {0.5f, 0.75f, 1.5f, 1.f},
{0.f, 1.f, 2.f, 1.f}, {32.f, 8.f, 16.f, 1.f}}},
{8.f, 4.f, 2.f, 1.f}, 0x90110200u,
{16.f, 8.f, 4.f, 1.f}, 0x98120220u},
TransferCase{Prospero::ChannelLayout::k2_10_10_10, Prospero::ChannelType::kUNorm,
Prospero::BufferFormat::k2_10_10_10UNorm, Prospero::BufferFormat::k10_10_10_2UNorm,
vk::Format::eA2R10G10B10UnormPack32, DstSel(4, 5, 6, 7),
{0x0540d015u, 0x00000fffu, 0x003ff000u, 0xffe01406u},
{{{1.f / 3, 5.f / 1023, 13.f / 1023, 21.f / 1023}, {1.f, 1.f, 0.f, 0.f},
{0.f, 0.f, 1.f, 0.f}, {2.f / 3, 257.f / 1023, 513.f / 1023, 1.f}}},
{21.f / 1023, 13.f / 1023, 5.f / 1023, 1.f / 3}, 0x0540d015u,
{33.f / 1023, 65.f / 1023, 129.f / 1023, 2.f / 3}, 0x08441206u},
};
for (const auto &format : transfers) {
for (const auto tile : {Prospero::TileMode::kLinear, Prospero::TileMode::kRenderTarget}) { for (const auto tile : {Prospero::TileMode::kLinear, Prospero::TileMode::kRenderTarget}) {
constexpr std::array<u32, 4> words{0x883005e1u, 0x7c0e81c0u,
0x800f0000u, 0x98120280u};
constexpr std::array<std::array<float, 4>, 4> channels{{
{1.03125f, 2.03125f, 4.0625f, 1.f}, {0.5f, 0.75f, 1.5f, 1.f},
{0.f, 1.f, 2.f, 1.f}, {32.f, 8.f, 16.f, 1.f}}};
const auto target = TextureGetRenderTargetFormat( const auto target = TextureGetRenderTargetFormat(
Prospero::ChannelLayout::k10_11_11, Prospero::ChannelType::kFloat, format.layout, format.type, Prospero::ChannelOrder::kReversed);
Prospero::ChannelOrder::kReversed); Require(name, "reversed packed render target",
Require(name, "10_11_11 reversed render target", target.format == format.native_format && target.guest_format == format.format &&
target.format == vk::Format::eB10G11R11UfloatPack32 &&
target.guest_format == Prospero::BufferFormat::k10_11_11Float &&
target.export_mapping == Prospero::ColorMappingRgba, target.export_mapping == Prospero::ColorMappingRgba,
"PPSA24156 reversed packed-float target lost its physical encoding"); "PPSA24156 reversed packed target lost its physical encoding");
std::memset(mapped, 0, allocation_size); std::memset(mapped, 0, allocation_size);
std::array<u32, 4> offsets{0, 4, 8, 12}; std::array<u32, 4> offsets{0, 4, 8, 12};
if (tile == Prospero::TileMode::kRenderTarget) { if (tile == Prospero::TileMode::kRenderTarget) {
TileBlockLayout block{}; TileBlockLayout block{};
u32 block_xor = 0; u32 block_xor = 0;
Require(name, "10_11_11 tile layout", Require(name, "packed tile layout",
TileGetBlockLayout(TileBlockFamily::RenderTarget64KB, 4, block) && TileGetBlockLayout(TileBlockFamily::RenderTarget64KB, 4, block) &&
TileGetBlockXor(block, 0, 0, 0, block_xor), TileGetBlockXor(block, 0, 0, 0, block_xor),
"packed-float render-target layout is unavailable"); "packed render-target layout is unavailable");
for (u32 pixel = 0; pixel < words.size(); ++pixel) { for (u32 pixel = 0; pixel < format.words.size(); ++pixel) {
Require(name, "10_11_11 tile offset", Require(name, "packed tile offset",
TileGetBlockOffset(block, pixel, 0, 0, offsets[pixel]), TileGetBlockOffset(block, pixel, 0, 0, offsets[pixel]),
"packed-float texel offset is unavailable"); "packed texel offset is unavailable");
offsets[pixel] ^= block_xor; offsets[pixel] ^= block_xor;
} }
} }
for (u32 pixel = 0; pixel < words.size(); ++pixel) { for (u32 pixel = 0; pixel < format.words.size(); ++pixel) {
std::memcpy(static_cast<uint8_t *>(mapped) + offsets[pixel], &words[pixel], sizeof(u32)); std::memcpy(static_cast<uint8_t *>(mapped) + offsets[pixel], &format.words[pixel], sizeof(u32));
} }
RenderContext context(m_runtime_context); RenderContext context(m_runtime_context);
auto &scheduler = context.GetCommandScheduler(); auto &scheduler = context.GetCommandScheduler();
@@ -13850,27 +13876,25 @@ public:
context.MapMemory(base, allocation_size); context.MapMemory(base, allocation_size);
auto &cache = context.GetTextureCache(); auto &cache = context.GetTextureCache();
auto &executor = context.GetRenderExecutor(); auto &executor = context.GetRenderExecutor();
constexpr u32 swizzle = DstSel(4, 5, 6, 1);
ShaderTextureResource descriptor{{static_cast<u32>(base >> 8u), ShaderTextureResource descriptor{{static_cast<u32>(base >> 8u),
(static_cast<u32>(Prospero::BufferFormat::k10_11_11Float) << 20u) | (static_cast<u32>(format.format) << 20u) | (3u << 30u), 0,
(3u << 30u), 0, format.swizzle | (static_cast<u32>(tile) << 20u) |
swizzle | (static_cast<u32>(tile) << 20u) |
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28u), (static_cast<u32>(Prospero::ImageType::kColor2D) << 28u),
0, 0x00700000u, 0, 0}}; 0, 0x00700000u, 0, 0}};
TestCase test; TestCase test;
test.name = name; test.name = name;
test.has_user_data = true; test.has_user_data = true;
test.image_descriptor_swizzle = swizzle; test.image_descriptor_swizzle = format.swizzle;
std::copy_n(descriptor.fields, 8, test.user_data.begin()); std::copy_n(descriptor.fields, 8, test.user_data.begin());
test.user_data[50] = 16 * sizeof(u32); test.user_data[50] = 16 * sizeof(u32);
for (u32 pixel = 0; pixel < words.size(); ++pixel) { for (u32 pixel = 0; pixel < format.words.size(); ++pixel) {
AppendVMovU32(&test.code, 20, pixel); AppendVMovU32(&test.code, 20, pixel);
AppendVMovU32(&test.code, 21, 0); AppendVMovU32(&test.code, 21, 0);
test.code.push_back(EncodeMimg0(0x00, 0xf)); test.code.push_back(EncodeMimg0(0x00, 0xf));
test.code.push_back(EncodeMimg1(0, 20)); test.code.push_back(EncodeMimg1(0, 20));
for (u32 channel = 0; channel < 4; ++channel) { for (u32 channel = 0; channel < 4; ++channel) {
AppendStoreVgpr(&test.code, channel, pixel * 4 + channel); AppendStoreVgpr(&test.code, channel, pixel * 4 + channel);
test.expected.push_back(std::bit_cast<u32>(channels[pixel][channel])); test.expected.push_back(std::bit_cast<u32>(format.channels[pixel][channel]));
} }
} }
AppendEnd(&test.code); AppendEnd(&test.code);
@@ -13886,9 +13910,17 @@ public:
scheduler.Finish(); scheduler.Finish();
auto output = CreateStorageBuffer(name, {}, 16); auto output = CreateStorageBuffer(name, {}, 16);
Dispatch(test, compiled, output, nullptr, &sampled); Dispatch(test, compiled, output, nullptr, &sampled);
Require(name, "10_11_11 uploaded components", const auto actual = ReadBuffer(name, output, 16);
ReadBuffer(name, output, 16) == test.expected, for (u32 component = 0; component < actual.size(); ++component) {
"packed-float upload or sample changed a component's width or order"); bool matches = actual[component] == test.expected[component];
if (format.type == Prospero::ChannelType::kUNorm) {
const float observed = std::bit_cast<float>(actual[component]);
const float expected = std::bit_cast<float>(test.expected[component]);
matches = std::abs(observed - expected) < 0.000001f;
}
Require(name, "packed uploaded components", matches,
"packed upload or sample changed a component's width or order");
}
DestroyBuffer(&output); DestroyBuffer(&output);
const auto clear = [&](std::array<float, 4> channels) { const auto clear = [&](std::array<float, 4> channels) {
@@ -13901,26 +13933,26 @@ public:
vk::ImageLayout::eTransferDstOptimal, vk::ClearColorValue{channels}, range); vk::ImageLayout::eTransferDstOptimal, vk::ClearColorValue{channels}, range);
cache.MarkGpuWritten(binding.image_id); cache.MarkGpuWritten(binding.image_id);
}; };
clear({8.f, 4.f, 2.f, 1.f}); clear(format.clear);
Require(name, "10_11_11 guest download", Require(name, "packed guest download",
TextureCacheTestAccess::TryDownload(cache, binding.image_id), TextureCacheTestAccess::TryDownload(cache, binding.image_id),
"packed-float native contents could not be downloaded"); "packed native contents could not be downloaded");
scheduler.Finish(); scheduler.Finish();
scheduler.DrainPriorityOperations(); scheduler.DrainPriorityOperations();
std::array<u32, 4> guest{}; std::array<u32, 4> guest{};
for (u32 pixel = 0; pixel < words.size(); ++pixel) { for (u32 pixel = 0; pixel < format.words.size(); ++pixel) {
Require(name, "10_11_11 guest texel", Require(name, "packed guest texel",
Libs::LibKernel::Memory::TryReadBacking(base + offsets[pixel], Libs::LibKernel::Memory::TryReadBacking(base + offsets[pixel],
&guest[pixel], sizeof(u32)), &guest[pixel], sizeof(u32)),
"packed-float guest texel is inaccessible"); "packed guest texel is inaccessible");
} }
Require(name, "10_11_11 exact guest words", Require(name, "exact packed guest words",
guest == std::array<u32, 4>{0x90110200u, 0x90110200u, 0x90110200u, 0x90110200u}, std::all_of(guest.begin(), guest.end(), [&](u32 word) { return word == format.clear_word; }),
"native packed-float write lost the guest 10/11/11 bit layout"); "native packed write lost the guest bit layout");
clear({16.f, 8.f, 4.f, 1.f}); clear(format.alias_clear);
auto alias_desc = binding.desc; auto alias_desc = binding.desc;
alias_desc.info.guest_format = Prospero::BufferFormat::k11_11_10Float; alias_desc.info.guest_format = format.alias_format;
alias_desc.view_info.mapping = {}; alias_desc.view_info.mapping = {};
Require(name, "same-format transform compatibility", Require(name, "same-format transform compatibility",
!alias_desc.info.IsCompatible(binding.desc.info), !alias_desc.info.IsCompatible(binding.desc.info),
@@ -13930,12 +13962,13 @@ public:
Require(name, "same-format transformed alias", Require(name, "same-format transformed alias",
alias != binding.image_id && alias != binding.image_id &&
ReadCachedTexel(name, context, alias, {}, {4, 1, 1}) == ReadCachedTexel(name, context, alias, {}, {4, 1, 1}) ==
std::vector<u32>(4, 0x98120220u), std::vector<u32>(4, format.alias_word),
"packed-float alias reused a transformed image or bypassed guest bytes"); "packed alias reused a transformed image or bypassed guest bytes");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
context.UnmapMemory(base, allocation_size); context.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
} }
}
Require(name, "release", Require(name, "release",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 && Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 &&
Libs::LibKernel::Memory::KernelReleaseDirectMemory( Libs::LibKernel::Memory::KernelReleaseDirectMemory(