mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
graphics: materialize fast clears before sampling reused targets
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
This commit is contained in:
@@ -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]];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user