mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
renderer: track and materialize deferred DCC clears
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
+100
-13
@@ -252,7 +252,7 @@ void TextureCache::DeleteImage(ImageId id) {
|
|||||||
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
||||||
}
|
}
|
||||||
m_download_images.erase(id);
|
m_download_images.erase(id);
|
||||||
if (owner->info.metadata.kind == ImageMetadataKind::Htile) {
|
if (owner->info.HasMetadata()) {
|
||||||
m_surface_metas.erase(owner->info.metadata.range.address);
|
m_surface_metas.erase(owner->info.metadata.range.address);
|
||||||
}
|
}
|
||||||
UnregisterImage(id);
|
UnregisterImage(id);
|
||||||
@@ -1327,6 +1327,19 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
|
|||||||
image.MarkGpuModified();
|
image.MarkGpuModified();
|
||||||
image.usage.render_target = true;
|
image.usage.render_target = true;
|
||||||
RefreshImage(id, desc);
|
RefreshImage(id, desc);
|
||||||
|
// DCC uses a separate metadata allocation. Register it when the color target is bound,
|
||||||
|
// matching the point where CMask/FMask will be registered. Preserve a PendingDcc entry
|
||||||
|
// because the metadata fill may have run before this bind.
|
||||||
|
if (desc.info.metadata.kind == ImageMetadataKind::Dcc) {
|
||||||
|
image.info.metadata = desc.info.metadata;
|
||||||
|
auto [metadata, inserted] = m_surface_metas.try_emplace(
|
||||||
|
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
|
||||||
|
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||||
|
metadata->second.type = MetaDataInfo::Type::Dcc;
|
||||||
|
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::Dcc) {
|
||||||
|
EXIT("TextureCache: color target reuses non-DCC metadata\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
CommitGpuWrite(image);
|
CommitGpuWrite(image);
|
||||||
TrackImageDownloadLocked(id, image);
|
TrackImageDownloadLocked(id, image);
|
||||||
const auto view = image.FindView(desc.view_info);
|
const auto view = image.FindView(desc.view_info);
|
||||||
@@ -1349,8 +1362,20 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
|||||||
RefreshImage(id, desc);
|
RefreshImage(id, desc);
|
||||||
if (desc.info.HasMetadata()) {
|
if (desc.info.HasMetadata()) {
|
||||||
image.info.metadata = desc.info.metadata;
|
image.info.metadata = desc.info.metadata;
|
||||||
m_surface_metas.try_emplace(desc.info.metadata.range.address,
|
auto [metadata, inserted] =
|
||||||
MetaDataInfo {.clear_mask = image.info.htile_clear_mask});
|
m_surface_metas.try_emplace(desc.info.metadata.range.address,
|
||||||
|
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||||
|
.clear_mask = image.info.htile_clear_mask});
|
||||||
|
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||||
|
// PendingDcc fills use DCC-specific encoding. Do not reinterpret a speculative
|
||||||
|
// DCC/buffer fill as HTile state if the address is later classified as depth metadata.
|
||||||
|
metadata->second.type = MetaDataInfo::Type::HTile;
|
||||||
|
metadata->second.clear_mask = image.info.htile_clear_mask;
|
||||||
|
metadata->second.fill_value = 0xffffffffu;
|
||||||
|
metadata->second.fill_size = 0;
|
||||||
|
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
|
||||||
|
EXIT("TextureCache: depth target reuses non-HTile metadata\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CommitGpuWrite(image);
|
CommitGpuWrite(image);
|
||||||
if (desc.info.HasStencil()) {
|
if (desc.info.HasStencil()) {
|
||||||
@@ -1826,33 +1851,90 @@ void TextureCache::ClearGpuModified(ImageId id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TextureCache::IsMeta(uint64_t address) {
|
bool TextureCache::IsMeta(uint64_t address) {
|
||||||
CacheLock lock(*this, m_lock);
|
|
||||||
return m_surface_metas.contains(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
|
|
||||||
CacheLock lock(*this, m_lock);
|
CacheLock lock(*this, m_lock);
|
||||||
const auto found = m_surface_metas.find(address);
|
const auto found = m_surface_metas.find(address);
|
||||||
if (found == m_surface_metas.end()) {
|
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
|
||||||
|
CacheLock lock(*this, m_lock);
|
||||||
|
const auto found = m_surface_metas.find(address);
|
||||||
|
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||||
|
slice >= 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (fill_value != nullptr) {
|
||||||
|
*fill_value = found->second.fill_value;
|
||||||
|
}
|
||||||
return (found->second.clear_mask & (1u << slice)) != 0;
|
return (found->second.clear_mask & (1u << slice)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureCache::ClearMeta(uint64_t address) {
|
bool TextureCache::ClearMeta(uint64_t address) {
|
||||||
CacheLock lock(*this, m_lock);
|
CacheLock lock(*this, m_lock);
|
||||||
const auto found = m_surface_metas.find(address);
|
const auto found = m_surface_metas.find(address);
|
||||||
if (found == m_surface_metas.end()) {
|
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||||
|
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||||
|
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
|
||||||
|
// validated fill value, so an arbitrary compute write must not clear it.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
found->second.clear_mask = UINT32_MAX;
|
found->second.clear_mask = UINT32_MAX;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
|
||||||
|
if (!GuestRange {address, size}.Valid()) {
|
||||||
|
EXIT("TextureCache: invalid DCC fill range\n");
|
||||||
|
}
|
||||||
|
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
|
||||||
|
// and mark only recognized deferred-clear encodings as logically clear.
|
||||||
|
const auto dcc_clear_mask = [fill_value] {
|
||||||
|
const auto code = static_cast<uint8_t>(fill_value);
|
||||||
|
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
switch (code) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x20:
|
||||||
|
case 0x40:
|
||||||
|
case 0x80:
|
||||||
|
case 0xc0: return UINT32_MAX;
|
||||||
|
default: return 0u;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
CacheLock lock(*this, m_lock);
|
||||||
|
auto found = m_surface_metas.find(address);
|
||||||
|
if (found == m_surface_metas.end()) {
|
||||||
|
// This dispatch may precede color-target discovery. Retain it in the shared
|
||||||
|
// metadata map, but PendingDcc remains invisible to IsMeta until registration. Returning
|
||||||
|
// false lets the guest dispatch execute and initialize memory while the type is uncertain.
|
||||||
|
m_surface_metas.emplace(address, MetaDataInfo {.type = MetaDataInfo::Type::PendingDcc,
|
||||||
|
.clear_mask = dcc_clear_mask,
|
||||||
|
.fill_value = fill_value,
|
||||||
|
.fill_size = size});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (found->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||||
|
found->second.clear_mask = dcc_clear_mask;
|
||||||
|
found->second.fill_value = fill_value;
|
||||||
|
found->second.fill_size = size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (found->second.type == MetaDataInfo::Type::Dcc) {
|
||||||
|
found->second.clear_mask = dcc_clear_mask;
|
||||||
|
found->second.fill_value = fill_value;
|
||||||
|
found->second.fill_size = size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The strict DCC path must not reinterpret a CMask/FMask/HTile allocation as DCC.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||||
CacheLock lock(*this, m_lock);
|
CacheLock lock(*this, m_lock);
|
||||||
const auto found = m_surface_metas.find(address);
|
const auto found = m_surface_metas.find(address);
|
||||||
if (found == m_surface_metas.end()) {
|
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||||
|
slice >= 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (is_clear) {
|
if (is_clear) {
|
||||||
@@ -1867,8 +1949,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
|||||||
if (!GuestRange {address, size}.Valid()) {
|
if (!GuestRange {address, size}.Valid()) {
|
||||||
EXIT("TextureCache: invalid unmap range\n");
|
EXIT("TextureCache: invalid unmap range\n");
|
||||||
}
|
}
|
||||||
CacheLock lock(*this, m_lock);
|
CacheLock lock(*this, m_lock);
|
||||||
auto images = FindImagesInRegion(address, size, false);
|
const auto end = address + size;
|
||||||
|
for (auto metadata = m_surface_metas.lower_bound(address);
|
||||||
|
metadata != m_surface_metas.end() && metadata->first < end;) {
|
||||||
|
metadata = m_surface_metas.erase(metadata);
|
||||||
|
}
|
||||||
|
auto images = FindImagesInRegion(address, size, false);
|
||||||
if (!images.empty()) {
|
if (!images.empty()) {
|
||||||
m_scheduler.Finish();
|
m_scheduler.Finish();
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -69,8 +69,12 @@ public:
|
|||||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||||
|
|
||||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
|
||||||
|
uint32_t* fill_value = nullptr);
|
||||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||||
|
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
|
||||||
|
// False may still record PendingDcc state, but the guest dispatch must execute.
|
||||||
|
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||||
|
|
||||||
void UnmapMemory(uint64_t address, uint64_t size);
|
void UnmapMemory(uint64_t address, uint64_t size);
|
||||||
@@ -88,7 +92,17 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct MetaDataInfo {
|
struct MetaDataInfo {
|
||||||
|
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
|
||||||
|
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
|
||||||
|
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||||
|
// Keep all surface metadata in this one shadPS4-style entry so CMask/FMask can be
|
||||||
|
// registered beside HTile and DCC without introducing parallel tracking paths.
|
||||||
|
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
|
||||||
|
|
||||||
|
Type type = Type::PendingDcc;
|
||||||
uint32_t clear_mask = 0;
|
uint32_t clear_mask = 0;
|
||||||
|
uint32_t fill_value = 0xffffffffu;
|
||||||
|
uint64_t fill_size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OverlapResult {
|
struct OverlapResult {
|
||||||
|
|||||||
@@ -21,6 +21,28 @@ namespace Libs::Graphics {
|
|||||||
|
|
||||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||||
|
|
||||||
|
static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc,
|
||||||
|
uint32_t packed_clear) {
|
||||||
|
// Register-backed DCC clears use the target's packed clear value. Decode one-word guest
|
||||||
|
// formats here; unsupported encodings remain tracked without unsafe materialization.
|
||||||
|
info.metadata_clear_supported =
|
||||||
|
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
|
||||||
|
switch (format) {
|
||||||
|
case vk::Format::eR8G8B8A8Unorm:
|
||||||
|
case vk::Format::eR8G8B8A8Srgb:
|
||||||
|
case vk::Format::eB8G8R8A8Unorm:
|
||||||
|
case vk::Format::eB8G8R8A8Srgb:
|
||||||
|
case vk::Format::eA2B10G10R10UnormPack32:
|
||||||
|
case vk::Format::eA2R10G10B10UnormPack32:
|
||||||
|
info.metadata_fixed_clear_supported = has_dcc;
|
||||||
|
break;
|
||||||
|
default: info.metadata_fixed_clear_supported = false; break;
|
||||||
|
}
|
||||||
|
if (!info.metadata_clear_supported) {
|
||||||
|
info.color_clear_value = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||||
RenderColorInfo& r,
|
RenderColorInfo& r,
|
||||||
@@ -55,20 +77,22 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No color output
|
// No color output
|
||||||
r.type = RenderColorType::NoColorOutput;
|
r.type = RenderColorType::NoColorOutput;
|
||||||
r.desc = {};
|
r.desc = {};
|
||||||
r.base_addr = 0;
|
r.base_addr = 0;
|
||||||
r.image_id = {};
|
r.image_id = {};
|
||||||
r.image_view = nullptr;
|
r.image_view = nullptr;
|
||||||
r.format = vk::Format::eUndefined;
|
r.format = vk::Format::eUndefined;
|
||||||
r.extent = {};
|
r.extent = {};
|
||||||
r.base_mip_level = 0;
|
r.base_mip_level = 0;
|
||||||
r.base_array_layer = 0;
|
r.base_array_layer = 0;
|
||||||
r.buffer_size = 0;
|
r.buffer_size = 0;
|
||||||
r.samples = 1;
|
r.samples = 1;
|
||||||
r.export_mapping = {};
|
r.export_mapping = {};
|
||||||
r.color_clear_enable = false;
|
r.color_clear_enable = false;
|
||||||
r.color_clear_value = {};
|
r.metadata_clear_supported = false;
|
||||||
|
r.metadata_fixed_clear_supported = false;
|
||||||
|
r.color_clear_value = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto samples = render_sample_count(rt.attrib.num_fragments);
|
const auto samples = render_sample_count(rt.attrib.num_fragments);
|
||||||
@@ -107,9 +131,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
|
// Color-control state selects the color-buffer operation and logical blend operation.
|
||||||
// ROP3 Copy is the normal color write path, not a render-target clear.
|
// The normal copy operation is a regular color write, not an attachment clear.
|
||||||
// SRGB clear words are still encoded as normalized component values.
|
// Nonlinear clear values are still stored as normalized components.
|
||||||
// Fast color clears are metadata driven and must be handled explicitly when
|
// Fast color clears are metadata driven and must be handled explicitly when
|
||||||
// that metadata path is implemented; render-pass load must preserve contents.
|
// that metadata path is implemented; render-pass load must preserve contents.
|
||||||
r.color_clear_enable = false;
|
r.color_clear_enable = false;
|
||||||
@@ -309,6 +333,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
|||||||
desc.info.bytes_per_block = bytes_per_element;
|
desc.info.bytes_per_block = bytes_per_element;
|
||||||
desc.info.samples = samples;
|
desc.info.samples = samples;
|
||||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||||
|
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||||
|
if (has_dcc) {
|
||||||
|
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
|
||||||
|
// TextureCache can associate metadata fills observed before target registration.
|
||||||
|
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||||
|
desc.info.metadata.range.address = rt.dcc_addr.addr;
|
||||||
|
}
|
||||||
for (uint32_t level = 0; level < levels; level++) {
|
for (uint32_t level = 0; level < levels; level++) {
|
||||||
if (volume) {
|
if (volume) {
|
||||||
const auto& mip = volume_layout.mips[level];
|
const auto& mip = volume_layout.mips[level];
|
||||||
@@ -355,7 +386,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
|||||||
r.samples = samples;
|
r.samples = samples;
|
||||||
r.export_mapping = target_format.export_mapping;
|
r.export_mapping = target_format.export_mapping;
|
||||||
r.color_clear_enable = false;
|
r.color_clear_enable = false;
|
||||||
r.color_clear_value = {};
|
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
|
||||||
BindRenderTarget(r.image_id);
|
BindRenderTarget(r.image_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ struct RenderColorInfo {
|
|||||||
uint32_t target_slot = 0;
|
uint32_t target_slot = 0;
|
||||||
uint32_t samples = 1;
|
uint32_t samples = 1;
|
||||||
Prospero::ColorComponentMapping export_mapping;
|
Prospero::ColorComponentMapping export_mapping;
|
||||||
bool color_clear_enable = false;
|
bool color_clear_enable = false;
|
||||||
|
bool metadata_clear_supported = false;
|
||||||
|
bool metadata_fixed_clear_supported = false;
|
||||||
vk::ClearColorValue color_clear_value {};
|
vk::ClearColorValue color_clear_value {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,21 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
|||||||
}
|
}
|
||||||
auto& cache = command.GetContext().GetTextureCache();
|
auto& cache = command.GetContext().GetTextureCache();
|
||||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||||
|
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
|
||||||
|
// before the render target is bound. TryConsumeDccFill either consumes registered state
|
||||||
|
// or retains a PendingDcc fill while allowing the dispatch to run.
|
||||||
|
const bool registered_metadata =
|
||||||
|
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
|
||||||
|
static std::atomic<uint32_t> logged_metadata_clears {0};
|
||||||
|
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||||
|
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
|
||||||
|
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||||
|
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
|
||||||
|
descriptor.Base48(), size, packed_clear);
|
||||||
|
}
|
||||||
|
if (registered_metadata) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
static std::atomic<uint32_t> logged_clears {0};
|
static std::atomic<uint32_t> logged_clears {0};
|
||||||
|
|||||||
@@ -377,8 +377,8 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
|
|||||||
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component
|
// Color-control operation selects special color-buffer paths, not the normal component write
|
||||||
// write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black.
|
// mask. Attachment availability therefore follows the target write mask.
|
||||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||||
ret.color_write_enable[i] =
|
ret.color_write_enable[i] =
|
||||||
(i < color_count &&
|
(i < color_count &&
|
||||||
@@ -552,6 +552,71 @@ struct DrawCallInfo {
|
|||||||
uint32_t first_instance = 0;
|
uint32_t first_instance = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool ResolveDccAttachmentClear(TextureCache& cache, const RenderColorInfo& target,
|
||||||
|
const ImageViewInfo& view,
|
||||||
|
vk::ClearColorValue& clear_value) {
|
||||||
|
if (target.desc.info.metadata.kind != ImageMetadataKind::Dcc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DCC fast clear may update metadata only and leave the color allocation stale. Vulkan has
|
||||||
|
// no guest DCC state, so materialize the deferred value when the surface is next bound.
|
||||||
|
const auto metadata_address = target.desc.info.metadata.range.address;
|
||||||
|
uint32_t metadata_value = 0xffffffffu;
|
||||||
|
if (!cache.IsMetaCleared(metadata_address, view.base_layer, &metadata_value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate recognized constant and register-backed DCC states into a host clear value.
|
||||||
|
clear_value = {};
|
||||||
|
switch (static_cast<uint8_t>(metadata_value)) {
|
||||||
|
case 0x00: break;
|
||||||
|
case 0x20:
|
||||||
|
if (!target.metadata_clear_supported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clear_value = target.color_clear_value;
|
||||||
|
break;
|
||||||
|
case 0x40:
|
||||||
|
if (!target.metadata_fixed_clear_supported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clear_value.float32[3] = 1.0f;
|
||||||
|
break;
|
||||||
|
case 0x80:
|
||||||
|
if (!target.metadata_fixed_clear_supported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clear_value.float32[0] = 1.0f;
|
||||||
|
clear_value.float32[1] = 1.0f;
|
||||||
|
clear_value.float32[2] = 1.0f;
|
||||||
|
break;
|
||||||
|
case 0xc0:
|
||||||
|
if (!target.metadata_fixed_clear_supported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clear_value.float32[0] = 1.0f;
|
||||||
|
clear_value.float32[1] = 1.0f;
|
||||||
|
clear_value.float32[2] = 1.0f;
|
||||||
|
clear_value.float32[3] = 1.0f;
|
||||||
|
break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t layer = 1; layer < view.layer_count; layer++) {
|
||||||
|
if (!cache.IsMetaCleared(metadata_address, view.base_layer + layer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Consume only after every layer in this Vulkan view can be materialized together.
|
||||||
|
for (uint32_t layer = 0; layer < view.layer_count; layer++) {
|
||||||
|
if (!cache.TouchMeta(metadata_address, view.base_layer + layer, false)) {
|
||||||
|
EXIT("failed to consume DCC clear state\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||||
uint32_t color_count, RenderDepthInfo& depth) {
|
uint32_t color_count, RenderDepthInfo& depth) {
|
||||||
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||||
@@ -592,14 +657,20 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
|||||||
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
||||||
view.layer_count},
|
view.layer_count},
|
||||||
buffer.Handle());
|
buffer.Handle());
|
||||||
state.width = std::min(state.width, target.extent.width);
|
state.width = std::min(state.width, target.extent.width);
|
||||||
state.height = std::min(state.height, target.extent.height);
|
state.height = std::min(state.height, target.extent.height);
|
||||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||||
auto& attachment = state.color_attachments[i];
|
auto& attachment = state.color_attachments[i];
|
||||||
attachment.image_view = target.image_view;
|
attachment.image_view = target.image_view;
|
||||||
attachment.image_layout = layout;
|
attachment.image_layout = layout;
|
||||||
attachment.clear_value = target.color_clear_value.uint32;
|
attachment.clear_value = target.color_clear_value.uint32;
|
||||||
attachment.is_clear = target.color_clear_enable;
|
vk::ClearColorValue metadata_clear_value {};
|
||||||
|
const bool metadata_clear =
|
||||||
|
ResolveDccAttachmentClear(cache, target, view, metadata_clear_value);
|
||||||
|
if (metadata_clear) {
|
||||||
|
attachment.clear_value = metadata_clear_value.uint32;
|
||||||
|
}
|
||||||
|
attachment.is_clear = target.color_clear_enable || metadata_clear;
|
||||||
}
|
}
|
||||||
if (depth.image_id) {
|
if (depth.image_id) {
|
||||||
const auto owner = cache.ResolveOwner(depth.image_id);
|
const auto owner = cache.ResolveOwner(depth.image_id);
|
||||||
@@ -687,10 +758,11 @@ enum class CbColorMode : uint8_t {
|
|||||||
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
|
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
|
||||||
const auto& ctx = buffer.GetRegisters();
|
const auto& ctx = buffer.GetRegisters();
|
||||||
const auto mode = ctx.GetColorControl().mode;
|
const auto mode = ctx.GetColorControl().mode;
|
||||||
// These AGC CB modes run color-buffer metadata/decompression operations. The shader is a
|
// These special modes run color-buffer metadata or decompression operations. The shader is a
|
||||||
// dummy vehicle for the CB, and its exported color must not be applied as a normal draw.
|
// vehicle for that operation, and its exported color must not be applied as a normal draw.
|
||||||
// Kyty currently stores host images as expanded Vulkan images and does not track CMASK/DCC
|
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
|
||||||
// metadata state, so the matching host operation is a no-op.
|
// 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) ||
|
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
|
||||||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
|
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
|
||||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||||
@@ -948,7 +1020,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
|
|||||||
}
|
}
|
||||||
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
|
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
|
||||||
|
|
||||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||||
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
|
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
|
||||||
static_cast<bool>(state.depth_info.image_id));
|
static_cast<bool>(state.depth_info.image_id));
|
||||||
if (state.color_count == 0 && !with_depth && !state.ps_active) {
|
if (state.color_count == 0 && !with_depth && !state.ps_active) {
|
||||||
|
|||||||
@@ -4542,10 +4542,15 @@ public:
|
|||||||
};
|
};
|
||||||
auto metadata_depth_a = MakeMetadataDepth(metadata_data_a, metadata_a);
|
auto metadata_depth_a = MakeMetadataDepth(metadata_data_a, metadata_a);
|
||||||
auto metadata_depth_b = MakeMetadataDepth(metadata_data_b, metadata_b);
|
auto metadata_depth_b = MakeMetadataDepth(metadata_data_b, metadata_b);
|
||||||
|
metadata_depth_a.info.htile_clear_mask = 0;
|
||||||
const auto metadata_depth_a_id =
|
const auto metadata_depth_a_id =
|
||||||
texture_cache.FindImage(metadata_depth_a);
|
texture_cache.FindImage(metadata_depth_a);
|
||||||
const auto metadata_depth_b_id =
|
const auto metadata_depth_b_id =
|
||||||
texture_cache.FindImage(metadata_depth_b);
|
texture_cache.FindImage(metadata_depth_b);
|
||||||
|
Require(name, "pending DCC/HTile separation",
|
||||||
|
!texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) &&
|
||||||
|
!texture_cache.IsMeta(base + metadata_a),
|
||||||
|
"an unclassified PS5 DCC fill was exposed as registered metadata");
|
||||||
const auto metadata_depth_a_view =
|
const auto metadata_depth_a_view =
|
||||||
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
|
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
|
||||||
const auto metadata_depth_b_view =
|
const auto metadata_depth_b_view =
|
||||||
@@ -4553,6 +4558,7 @@ public:
|
|||||||
Require(name, "shared-page metadata state",
|
Require(name, "shared-page metadata state",
|
||||||
metadata_depth_a_view != nullptr &&
|
metadata_depth_a_view != nullptr &&
|
||||||
metadata_depth_b_view != nullptr &&
|
metadata_depth_b_view != nullptr &&
|
||||||
|
!texture_cache.IsMetaCleared(base + metadata_a, 0) &&
|
||||||
texture_cache.ClearMeta(base + metadata_a) &&
|
texture_cache.ClearMeta(base + metadata_a) &&
|
||||||
texture_cache.ClearMeta(base + metadata_b),
|
texture_cache.ClearMeta(base + metadata_b),
|
||||||
"shared-page metadata did not retain logical clear state");
|
"shared-page metadata did not retain logical clear state");
|
||||||
@@ -4576,6 +4582,11 @@ public:
|
|||||||
texture_cache.IsMeta(base + metadata_b) &&
|
texture_cache.IsMeta(base + metadata_b) &&
|
||||||
texture_cache.IsMetaCleared(base + metadata_b, 0),
|
texture_cache.IsMetaCleared(base + metadata_b, 0),
|
||||||
"image discovery retired aliased metadata or its live depth image");
|
"image discovery retired aliased metadata or its live depth image");
|
||||||
|
Require(name, "registered DCC/HTile separation",
|
||||||
|
texture_cache.TouchMeta(base + metadata_a, 0, false) &&
|
||||||
|
!texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) &&
|
||||||
|
!texture_cache.IsMetaCleared(base + metadata_a, 0),
|
||||||
|
"a PS5 DCC fill changed registered HTile state");
|
||||||
Require(
|
Require(
|
||||||
name, "metadata first-touch state",
|
name, "metadata first-touch state",
|
||||||
texture_cache.TouchMeta(base + metadata_b, 0, true) &&
|
texture_cache.TouchMeta(base + metadata_b, 0, true) &&
|
||||||
@@ -6313,7 +6324,8 @@ public:
|
|||||||
HW::Shader shaders{};
|
HW::Shader shaders{};
|
||||||
registers.SetColorBase(0, {.addr = base});
|
registers.SetColorBase(0, {.addr = base});
|
||||||
registers.SetColorInfo(
|
registers.SetColorInfo(
|
||||||
0, {.format = Prospero::ChannelLayout::k10_10_10_2,
|
0, {.dcc_compression_enable = true,
|
||||||
|
.format = Prospero::ChannelLayout::k10_10_10_2,
|
||||||
.channel_type = Prospero::ChannelType::kUNorm,
|
.channel_type = Prospero::ChannelType::kUNorm,
|
||||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||||
registers.SetColorAttrib2(0, {.height = 31, .width = 31});
|
registers.SetColorAttrib2(0, {.height = 31, .width = 31});
|
||||||
@@ -6323,6 +6335,10 @@ public:
|
|||||||
.dimension = 2,
|
.dimension = 2,
|
||||||
.cmask_pipe_aligned = true,
|
.cmask_pipe_aligned = true,
|
||||||
.dcc_pipe_aligned = true});
|
.dcc_pipe_aligned = true});
|
||||||
|
constexpr uint64_t dcc_address = base + allocation_size - 0x1000;
|
||||||
|
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
||||||
|
registers.SetColorClearWord0(0, {.word0 = 0});
|
||||||
|
registers.SetColorClearWord1(0, {.word1 = 0});
|
||||||
registers.SetRenderTargetMask(0x0f);
|
registers.SetRenderTargetMask(0x0f);
|
||||||
scheduler.Begin(registers, user_config, shaders);
|
scheduler.Begin(registers, user_config, shaders);
|
||||||
|
|
||||||
@@ -6330,6 +6346,10 @@ public:
|
|||||||
auto &texture_cache = resources.GetTextureCache();
|
auto &texture_cache = resources.GetTextureCache();
|
||||||
auto &executor = context.GetRenderExecutor();
|
auto &executor = context.GetRenderExecutor();
|
||||||
resources.MapMemory(base, allocation_size);
|
resources.MapMemory(base, allocation_size);
|
||||||
|
Require(name, "deferred DCC clear",
|
||||||
|
!texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) &&
|
||||||
|
!texture_cache.IsMeta(dcc_address),
|
||||||
|
"a pre-registration DCC clear was not deferred");
|
||||||
|
|
||||||
RenderColorInfo color{};
|
RenderColorInfo color{};
|
||||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||||
@@ -6337,6 +6357,7 @@ public:
|
|||||||
const auto attachment =
|
const auto attachment =
|
||||||
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
||||||
const auto &image = texture_cache.GetImage(color.image_id);
|
const auto &image = texture_cache.GetImage(color.image_id);
|
||||||
|
uint32_t dcc_clear_value = 0xffffffffu;
|
||||||
Require(
|
Require(
|
||||||
name, "captured 3D target",
|
name, "captured 3D target",
|
||||||
color.image_id && attachment != nullptr &&
|
color.image_id && attachment != nullptr &&
|
||||||
@@ -6346,6 +6367,13 @@ public:
|
|||||||
color.desc.info.pitch == 128 &&
|
color.desc.info.pitch == 128 &&
|
||||||
color.desc.info.data.size == allocation_size &&
|
color.desc.info.data.size == allocation_size &&
|
||||||
color.desc.info.mip_layout[0].size == 0x10000 &&
|
color.desc.info.mip_layout[0].size == 0x10000 &&
|
||||||
|
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
||||||
|
color.desc.info.metadata.range.address == dcc_address &&
|
||||||
|
color.metadata_clear_supported &&
|
||||||
|
texture_cache.IsMetaCleared(dcc_address, 7,
|
||||||
|
&dcc_clear_value) &&
|
||||||
|
dcc_clear_value == 0 &&
|
||||||
|
!texture_cache.ClearMeta(dcc_address) &&
|
||||||
color.desc.view_info.type == vk::ImageViewType::e2D &&
|
color.desc.view_info.type == vk::ImageViewType::e2D &&
|
||||||
color.desc.view_info.layer_count == 1 &&
|
color.desc.view_info.layer_count == 1 &&
|
||||||
image.backing.image_type == vk::ImageType::e3D &&
|
image.backing.image_type == vk::ImageType::e3D &&
|
||||||
@@ -6372,8 +6400,21 @@ public:
|
|||||||
sliced_color.image_view != nullptr &&
|
sliced_color.image_view != nullptr &&
|
||||||
sliced_color.desc.view_info.base_layer == 7 &&
|
sliced_color.desc.view_info.base_layer == 7 &&
|
||||||
sliced_rendering.num_color_attachments == 1 &&
|
sliced_rendering.num_color_attachments == 1 &&
|
||||||
sliced_rendering.num_layers == 1,
|
sliced_rendering.num_layers == 1 &&
|
||||||
|
sliced_rendering.color_attachments[0].is_clear &&
|
||||||
|
sliced_rendering.color_attachments[0].clear_value ==
|
||||||
|
vk::ClearColorValue {}.uint32 &&
|
||||||
|
!texture_cache.IsMetaCleared(dcc_address, 7),
|
||||||
"a nonzero 3D attachment slice was treated as a Vulkan array layer");
|
"a nonzero 3D attachment slice was treated as a Vulkan array layer");
|
||||||
|
dcc_clear_value = 0;
|
||||||
|
Require(name, "registered DCC code state",
|
||||||
|
texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0xffffffffu) &&
|
||||||
|
!texture_cache.IsMetaCleared(dcc_address, 7,
|
||||||
|
&dcc_clear_value) &&
|
||||||
|
dcc_clear_value == 0xffffffffu &&
|
||||||
|
texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) &&
|
||||||
|
texture_cache.IsMetaCleared(dcc_address, 7),
|
||||||
|
"DCC fill codes were not classified as clear or uncompressed");
|
||||||
|
|
||||||
auto storage_desc = color.desc;
|
auto storage_desc = color.desc;
|
||||||
storage_desc.type = BindingType::Storage;
|
storage_desc.type = BindingType::Storage;
|
||||||
|
|||||||
Reference in New Issue
Block a user