mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 17:18:14 +00:00
texture_cache: apply DCC fast clears of mipmapped color targets
Co-authored-by: Artyom Zabroda <artyomzabroda@gmail.com>
This commit is contained in:
1 parent
ae67e2db51
commit
a5cfdcfd32
7 files changed
+189
-79
No files matched your search
@@ -259,6 +259,17 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t FirstTailLevel(const MipTailLayout& tail, uint32_t width, uint32_t height,
|
||||
uint32_t levels) {
|
||||
for (uint32_t level = 0; levels > 1 && level < levels; ++level) {
|
||||
if (ShiftCeil(width, level) <= tail.width_limit &&
|
||||
ShiftCeil(height, level) <= tail.height_limit && levels - level <= tail.max_levels) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
|
||||
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
|
||||
out = {bytes, 1, 1};
|
||||
@@ -336,23 +347,14 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
|
||||
TileSurfaceLayout result {};
|
||||
result.description = description;
|
||||
result.texture = texture;
|
||||
result.first_tail_level = description.levels;
|
||||
const uint32_t block_slices =
|
||||
volume ? ShiftCeil(description.depth, std::countr_zero(block.block_depth))
|
||||
: description.layers;
|
||||
|
||||
MipTailLayout tail {};
|
||||
const bool has_tail = GetMipTailLayout(block, tail);
|
||||
if (has_tail && description.levels > 1) {
|
||||
for (uint32_t level = 0; level < description.levels; ++level) {
|
||||
if (ShiftCeil(width0, level) <= tail.width_limit &&
|
||||
ShiftCeil(height0, level) <= tail.height_limit &&
|
||||
description.levels - level <= tail.max_levels) {
|
||||
result.first_tail_level = level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.first_tail_level = GetMipTailLayout(block, tail)
|
||||
? FirstTailLevel(tail, width0, height0, description.levels)
|
||||
: description.levels;
|
||||
|
||||
for (uint32_t level = 0; level < result.first_tail_level; ++level) {
|
||||
auto& mip = result.mips[level];
|
||||
@@ -1213,44 +1215,52 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t block_width, uint32_t block_height,
|
||||
TileSizeAlign& total_size) {
|
||||
if (width == 0 || height == 0 || slices == 0) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
|
||||
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
|
||||
const uint64_t blocks = blocks_x * blocks_y;
|
||||
if (blocks > UINT32_MAX / 4096u / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
|
||||
total_size.align = 4096;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2, TileSizeOffset* mips) {
|
||||
total_size = {};
|
||||
if (levels != 1 || num_fragments_log2 != 0 ||
|
||||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
|
||||
TileBlockLayout block {};
|
||||
MipTailLayout tail {};
|
||||
if (slices == 0 || levels > 16 || num_fragments_log2 != 0 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth) ||
|
||||
!TileGetBlockLayout(tile == Prospero::TileMode::kDepth ? TileBlockFamily::Depth64KB
|
||||
: TileBlockFamily::RenderTarget64KB,
|
||||
bytes_per_element, block) ||
|
||||
!GetMipTailLayout(block, tail)) {
|
||||
return false;
|
||||
}
|
||||
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data.
|
||||
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data. Each slice stores
|
||||
// its mips backwards through the first tail mip, which fits one block; later tail mips have
|
||||
// none. Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
|
||||
return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u),
|
||||
1u << (coverage_bits / 2u), total_size);
|
||||
uint64_t slice_size = 0;
|
||||
for (uint32_t level = std::min(FirstTailLevel(tail, width, height, levels) + 1u, levels);
|
||||
level-- > 0;) {
|
||||
const uint64_t size = uint64_t {ShiftCeil(width, level + (coverage_bits + 1u) / 2u)} *
|
||||
ShiftCeil(height, level + coverage_bits / 2u) * 4096u;
|
||||
if (mips != nullptr) {
|
||||
mips[level] = {.size = static_cast<uint32_t>(size),
|
||||
.offset = static_cast<uint32_t>(slice_size)};
|
||||
}
|
||||
slice_size += size;
|
||||
}
|
||||
if (slice_size == 0 || slice_size > UINT32_MAX / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size = {static_cast<uint32_t>(slice_size * slices), 4096};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size) {
|
||||
total_size = {};
|
||||
// Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels.
|
||||
return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size);
|
||||
const uint64_t blocks = uint64_t {ShiftCeil(width, 10)} * ShiftCeil(height, 9);
|
||||
if (levels != 1 || blocks == 0 || slices == 0 || blocks > UINT32_MAX / 4096u / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size = {static_cast<uint32_t>(blocks * slices * 4096u), 4096};
|
||||
return true;
|
||||
}
|
||||
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
|
||||
@@ -123,9 +123,11 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
// mips, when given, receives the size and offset within a slice of each level that has metadata.
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0,
|
||||
TileSizeOffset* mips = nullptr);
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size);
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
|
||||
+38
-17
@@ -1043,34 +1043,51 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
desc.info.metadata.kind != ImageMetadataKind::Cmask) {
|
||||
return;
|
||||
}
|
||||
const auto range = desc.info.metadata.range;
|
||||
const auto range = desc.info.metadata.range;
|
||||
const auto& view = desc.view_info;
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// Native color metadata must not retain a reused HTile clear flag.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1) {
|
||||
if (range.size == 0 || view.level_count != 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto layers = desc.info.TransferLayers();
|
||||
// These one-mip surfaces use complete 4 KiB color metadata blocks.
|
||||
// Color metadata slices consist of complete 4 KiB blocks.
|
||||
constexpr uint64_t MetadataBlockSize = 0x1000;
|
||||
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
|
||||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
|
||||
EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n");
|
||||
}
|
||||
const auto& view = desc.view_info;
|
||||
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : metadata_base_layer;
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
|
||||
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
const auto first = volume_texture ? 0u : metadata_base_layer;
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count =
|
||||
volume_texture ? std::max(desc.info.extent.depth >> view.base_level, 1u) : view.layer_count;
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||
const auto slice_size = range.size / layers;
|
||||
// Each slice holds the viewed mip's metadata in one run; DCC mips past the first tail mip
|
||||
// have none. FindImage moves base_level only for one-mip descriptors, which keep the slice.
|
||||
TileSizeOffset mip {.size = static_cast<uint32_t>(slice_size)};
|
||||
if (desc.info.metadata.kind == ImageMetadataKind::Dcc && desc.info.resources.levels > 1) {
|
||||
std::array<TileSizeOffset, 16> mips {};
|
||||
TileSizeAlign total {};
|
||||
(void)TileGetDccSize(desc.info.extent.width, desc.info.extent.height, layers,
|
||||
desc.info.bytes_per_block, desc.info.resources.levels,
|
||||
desc.info.tile_mode, total, 0, mips.data());
|
||||
mip = mips[view.base_level];
|
||||
}
|
||||
if (mip.size == 0) {
|
||||
return;
|
||||
}
|
||||
const GuestRange viewed {range.address + slice_size * first + mip.offset,
|
||||
slice_size * (count - 1) + mip.size};
|
||||
const auto run = [&](uint32_t slice) { return viewed.address + slice_size * slice; };
|
||||
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||
uint32_t keys = 0;
|
||||
@@ -1082,7 +1099,11 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
}
|
||||
}
|
||||
// No selected key to clear, or none left since the last check.
|
||||
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||
bool checked = true;
|
||||
for (uint32_t slice = 0; slice < count && checked; slice++) {
|
||||
checked = m_buffer_cache.IsColorMetadataChecked(run(slice), mip.size, keys);
|
||||
}
|
||||
if (checked) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
@@ -1107,9 +1128,9 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
const auto [buffer, offset] =
|
||||
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
|
||||
const auto predicates = m_tiler.CheckColorClear(
|
||||
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
|
||||
if (expand) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
|
||||
{buffer->Handle(), offset, viewed.size}, slice_size, count, keys, expand);
|
||||
for (uint32_t slice = 0; slice < count && expand; slice++) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(run(slice), mip.size, keys);
|
||||
}
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||
@@ -1142,7 +1163,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||
}
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
const auto address = run(slice);
|
||||
uint8_t code = 0;
|
||||
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
|
||||
EXIT("TextureCache: failed to read color metadata backing\n");
|
||||
@@ -1151,7 +1172,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
if (!DecodeColorClear(desc, code, clear.color)) {
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> bytes(slice_size);
|
||||
std::vector<uint8_t> bytes(mip.size);
|
||||
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
|
||||
EXIT("TextureCache: failed to read color metadata slice\n");
|
||||
}
|
||||
@@ -1175,7 +1196,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
// to overlapping image data. Invalidate the buffer before updating its backing.
|
||||
if (desc.type != BindingType::VideoOut) {
|
||||
std::fill(bytes.begin(), bytes.end(), uint8_t {0xff});
|
||||
m_buffer_cache.InvalidateMemory(address, slice_size);
|
||||
m_buffer_cache.InvalidateMemory(address, bytes.size());
|
||||
LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,19 +630,20 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand) {
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
|
||||
uint32_t keys, bool expand) {
|
||||
struct Check {
|
||||
uint32_t metadata_base;
|
||||
uint32_t words;
|
||||
uint32_t stride;
|
||||
uint32_t keys;
|
||||
uint32_t expand;
|
||||
};
|
||||
static_assert(sizeof(Check) <= sizeof(Conversion));
|
||||
const uint32_t max_slices =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX);
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % 4 != 0 || stride % 4 != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX || stride * (slices - 1) >= metadata.size);
|
||||
if (m_color_clear == nullptr) {
|
||||
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||
m_clear_predicates = std::make_unique<Buffer>(
|
||||
@@ -673,8 +674,9 @@ vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
|
||||
expand};
|
||||
const Check push {source.base,
|
||||
static_cast<uint32_t>((metadata.size - stride * (slices - 1)) / 4),
|
||||
static_cast<uint32_t>(stride / 4), keys, expand};
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch(slices, 1, 1);
|
||||
|
||||
@@ -56,11 +56,11 @@ public:
|
||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
||||
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
|
||||
static constexpr uint32_t ColorClearSlots = 8;
|
||||
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
|
||||
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
|
||||
// `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand);
|
||||
// metadata spans `slices` equal runs `stride` bytes apart. Returns the buffer whose dword
|
||||
// slice * ColorClearSlots + (key >> 5) is set when every byte of that run equals a key whose
|
||||
// bit `keys` selects; a hit expands to 0xff when `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint64_t stride, uint32_t slices,
|
||||
uint32_t keys, bool expand);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
|
||||
@@ -9,6 +9,7 @@ layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[];
|
||||
layout(push_constant, std430) uniform Check {
|
||||
uint metadata_base;
|
||||
uint words;
|
||||
uint stride;
|
||||
uint keys;
|
||||
uint expand;
|
||||
} params;
|
||||
@@ -20,7 +21,7 @@ shared uint mismatch;
|
||||
|
||||
void main() {
|
||||
const uint slice = gl_WorkGroupID.x;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.stride;
|
||||
// Each invocation reads, and may rewrite, only its own words.
|
||||
if (gl_LocalInvocationIndex == 0u) {
|
||||
key = metadata.data[base] & 0xffu;
|
||||
|
||||
Reference in new issue
Block a user