graphics: materialize fast clears before sampling reused targets

Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
This commit is contained in:
nmzik
2026-10-13 03:21:40 +02:00
parent 40692dfb64
commit 31b7be9ff2
5 changed files with 113 additions and 41 deletions
@@ -236,7 +236,8 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
desc.info.metadata.clear_word = rt.clear_word0.word0;
desc.info.metadata.clear_word =
rt.clear_word0.word0 | (uint64_t {rt.clear_word1.word1} << 32u);
desc.info.metadata.clear_register_valid = true;
}
desc.view_info.format = target_format.format;
@@ -27,7 +27,7 @@ struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
uint32_t clear_word = 0;
uint64_t clear_word = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
bool clear_register_valid = false;
@@ -414,9 +414,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
return false;
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint64_t clear_words,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
const auto packed = static_cast<uint32_t>(clear_words);
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
const auto srgb8 = [](uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
@@ -434,6 +435,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
(mantissa << 13u));
};
switch (format) {
case vk::Format::eR16G16B16A16Sfloat:
next.float32[2] = float16(static_cast<uint32_t>(clear_words >> 32u));
next.float32[3] = float16(static_cast<uint32_t>(clear_words >> 48u));
[[fallthrough]];
case vk::Format::eR16G16Sfloat:
next.float32[1] = float16(packed >> 16u);
[[fallthrough]];
+2
View File
@@ -196,6 +196,8 @@ private:
std::span<PreparedBindings* const> stages = {});
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
[[nodiscard]] bool ConsumeMetadataColorOperation(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void ResetBindings();
+15 -10
View File
@@ -608,16 +608,19 @@ enum class CbColorMode : uint8_t {
DccDecompress = 6,
};
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& ctx = buffer.GetRegisters();
const auto mode = ctx.GetColorControl().mode;
// These special modes run color-buffer metadata or decompression operations. The shader is a
// vehicle for that operation, and its exported color must not be applied as a normal draw.
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
// future CMask/FMask support can consume its state through the same TextureCache path.
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
if (mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear)) {
// FCE can precede any ordinary draw. Discover the target to materialize its native
// clear metadata before the expanded image is sampled.
RenderColorInfo target {};
ResolveRenderColorTarget(buffer, target, render_target_slice_offset, 0, true);
return true;
}
// Metadata/decompression shaders must not export colors into the expanded image.
return mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
}
@@ -1257,7 +1260,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
@@ -1370,7 +1374,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
+87 -28
View File
@@ -10865,12 +10865,14 @@ public:
constexpr const char *name = "RenderExecutorColorMetadataClear";
struct PackedCase {
vk::Format format;
uint32_t word;
uint64_t word;
std::array<float, 4> channels;
};
const std::array packed_cases{
PackedCase{vk::Format::eR16Sfloat, 0x7bff7bffu, {65504.0f}},
PackedCase{vk::Format::eR16G16Sfloat, 0xbc003c00u, {1.0f, -1.0f}},
PackedCase{vk::Format::eR16G16B16A16Sfloat, 0x40003800bc003c00ull,
{1.0f, -1.0f, 0.5f, 2.0f}},
PackedCase{vk::Format::eR16Sfloat, 0xffff0001u, {0x1p-24f}},
PackedCase{vk::Format::eR16Sfloat, 0x00008001u, {-0x1p-24f}},
PackedCase{vk::Format::eR16Sfloat, 0x000003ffu, {1023.0f * 0x1p-24f}},
@@ -10904,7 +10906,7 @@ public:
"alternating R16 pixels were treated as a uniform clear");
}
constexpr uintptr_t base = 0x0000000204100000ull;
constexpr uint64_t allocation_size = 0x200000;
constexpr uint64_t allocation_size = 0x400000;
constexpr uint64_t allocation_alignment = 0x10000;
struct FillCase {
uint32_t fill;
@@ -10915,12 +10917,19 @@ public:
Prospero::ChannelLayout layout = Prospero::ChannelLayout::k16_16_16_16;
Prospero::ChannelType type = Prospero::ChannelType::kFloat;
uint32_t clear_word = 0;
bool eliminate_fast_clear = false;
};
constexpr std::array cases{
FillCase{0x40404040u, {0, 0x3c000000u}},
FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}},
FillCase{0x40404040u, {0, 0x3c000000u}, true},
FillCase{0, {0x804020ffu, 0}, false, true},
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
.layout = Prospero::ChannelLayout::k8_8_8_8,
.type = Prospero::ChannelType::kUNorm},
FillCase{.fill = 0, .texel = {0, 0}, .cmask = true,
.eliminate_fast_clear = true},
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
.eliminate_fast_clear = true},
FillCase{0x40404040u, {0, 0x3c000000u}, false, false, true},
FillCase{.fill = 0x20202020u, .texel = {0x7bff7bffu},
.layout = Prospero::ChannelLayout::k16, .clear_word = 0x7bff7bffu},
@@ -10985,15 +10994,16 @@ public:
std::memset(mapped, 0, allocation_size);
for (const auto &fill_case : cases) {
const auto layout = fill_case.cmask ? Prospero::ChannelLayout::k8_8_8_8 : fill_case.layout;
const auto type = fill_case.cmask ? Prospero::ChannelType::kUNorm : fill_case.type;
const auto layout = fill_case.layout;
const auto type = fill_case.type;
const auto bytes_per_pixel = TextureGetRenderTargetFormat(
layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element;
const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1;
TileSizeAlign dcc_size{};
(void)TileGetDccSize(512, 256, 1, bytes_per_pixel, 1,
Prospero::TileMode::kRenderTarget, dcc_size, 0);
const uint64_t dcc_address = base + (fill_case.reuse_mips ? 0x160000 : 0x100000);
const uint64_t dcc_address = base + (fill_case.eliminate_fast_clear ? 0x200000 :
fill_case.reuse_mips ? 0x160000 : 0x100000);
const uint32_t selected_layer = fill_case.cmask ? 1 : 0;
const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size;
const std::vector<u32> expected(
@@ -11023,7 +11033,7 @@ public:
registers.SetColorDccAddr(0, {.addr = dcc_address});
registers.SetColorCmask(0, {.addr = dcc_address});
registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : fill_case.clear_word});
registers.SetColorClearWord1(0, {.word1 = 0});
registers.SetColorClearWord1(0, {.word1 = fill_case.eliminate_fast_clear ? expected[1] : 0});
registers.SetRenderTargetMask(0x0f);
scheduler.Begin(registers, user_config, shaders);
@@ -11032,7 +11042,8 @@ public:
auto &executor = context.GetRenderExecutor();
resources.MapMemory(base, allocation_size);
ImageId preexisting_id{};
if (fill_case.reuse_unorm || fill_case.reuse_mips) {
RenderColorInfo color{};
if (fill_case.reuse_unorm || fill_case.reuse_mips || fill_case.eliminate_fast_clear) {
const auto float_info = registers.GetRenderTarget(0).info;
const auto single_mip = registers.GetRenderTarget(0).attrib2;
auto initial_info = float_info;
@@ -11049,6 +11060,7 @@ public:
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), preexisting, 0);
preexisting_id = preexisting.image_id;
color = preexisting;
(void)texture_cache.FindRenderTarget(preexisting_id, preexisting.desc);
if (fill_case.reuse_mips) {
Require(name, "mip chain excludes metadata",
@@ -11061,6 +11073,13 @@ public:
texture_cache, scheduler.Current(), preexisting_id,
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear);
}
if (fill_case.eliminate_fast_clear) {
vk::ClearValue stale{};
stale.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), preexisting_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 2}, stale);
}
RenderExecutorTestAccess::ResetBindings(executor);
registers.SetColorInfo(0, float_info);
registers.SetColorAttrib2(0, single_mip);
@@ -11088,7 +11107,66 @@ public:
!texture_cache.IsMeta(dcc_address),
"the native fill guessed an unbound metadata allocation's type");
RenderColorInfo color{};
const auto check_expanded_metadata = [&] {
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
std::vector<uint8_t> bytes(metadata_size);
Require(name, "expanded metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
"native color metadata could not be read back");
const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0);
Require(name, "selected metadata slice expansion",
std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) &&
std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }),
"materialization retained a clear key or changed an unselected slice");
};
const auto paint = [&] {
vk::ClearValue clear{};
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), color.image_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, selected_layer, 1}, clear);
};
if (fill_case.eliminate_fast_clear) {
registers.SetRenderTargetMask(0);
registers.SetColorControl({.mode = 2});
RenderExecutorTestAccess::DrawAuto(executor, scheduler.Current(),
{.vertex_count = 3, .instance_count = 1});
auto sampled = color.desc;
sampled.type = BindingType::Texture;
sampled.view_info.usage = vk::ImageUsageFlagBits::eSampled;
color.image_id = texture_cache.FindImage(sampled);
Require(name, "FCE sampled image reuses stale target",
color.image_id == preexisting_id &&
texture_cache.FindTexture(color.image_id, sampled) != nullptr,
"FCE failed to retain the sampled allocation");
const std::vector<u32> stale{0x00003c00u, 0x3c003c00u};
Require(name, "FCE before graphics shader or target binding",
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == expected &&
ReadCachedTexel(name, context, color.image_id, {511, 255, 0},
{1, 1, 1}, 1) == expected &&
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 0) == stale,
"FCE lost a clear register or changed the unselected layer");
check_expanded_metadata();
paint();
RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(),
{.index_count = 3, .instance_count = 1});
(void)texture_cache.FindTexture(color.image_id, sampled);
Require(name, "repeated indexed FCE preserves expanded color",
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == stale,
"FCE reapplied the consumed clear after rendering");
WriteMetadata(context, dcc_address, metadata_size, 0);
RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(),
{.index_count = 3, .instance_count = 1});
(void)texture_cache.FindTexture(color.image_id, sampled);
Require(name, "indexed FCE consumes a new clear",
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == expected,
"indexed FCE skipped a new metadata clear");
resources.UnmapMemory(base, allocation_size);
scheduler.Finish();
return;
}
RenderExecutorTestAccess::ResolveRenderColorTarget(
executor, scheduler.Current(), color, 0);
RenderDepthInfo no_depth{};
@@ -11132,18 +11210,6 @@ public:
std::vector<u32>{0x00003c00u, 0x00003c00u},
"clearing mip zero changed its higher mip");
}
const auto check_expanded_metadata = [&] {
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
std::vector<uint8_t> bytes(metadata_size);
Require(name, "expanded metadata readback",
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
"native color metadata could not be read back");
const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0);
Require(name, "selected metadata slice expansion",
std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) &&
std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }),
"materialization retained a clear key or changed an unselected slice");
};
check_expanded_metadata();
const auto bind = [&] {
@@ -11151,13 +11217,6 @@ public:
color.image_id = texture_cache.FindImage(color.desc);
(void)texture_cache.FindRenderTarget(color.image_id, color.desc);
};
const auto paint = [&] {
vk::ClearValue clear{};
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), color.image_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, selected_layer, 1}, clear);
};
std::vector<u32> painted{0x00003c00u, 0x3c003c00u};
if (fill_case.reuse_unorm) {
painted = {0x0000ffffu, 0xffffffffu};