mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
texture cache: select color-metadata clears on the GPU with conditional rendering
This commit is contained in:
@@ -265,6 +265,7 @@ set(gpu_tiler_shader_names
|
|||||||
promote_d16
|
promote_d16
|
||||||
demote_d16
|
demote_d16
|
||||||
color_transform
|
color_transform
|
||||||
|
color_clear
|
||||||
)
|
)
|
||||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ enum class Counter {
|
|||||||
TextureEvictions,
|
TextureEvictions,
|
||||||
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
||||||
ColorMetadataReadbackNs,
|
ColorMetadataReadbackNs,
|
||||||
|
ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata.
|
||||||
BufferUploadBytes,
|
BufferUploadBytes,
|
||||||
BufferDownloadBytes,
|
BufferDownloadBytes,
|
||||||
BufferEvictions,
|
BufferEvictions,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ struct GraphicContext {
|
|||||||
bool attachment_feedback_loop_enabled = false;
|
bool attachment_feedback_loop_enabled = false;
|
||||||
bool provoking_vertex_last_enabled = false;
|
bool provoking_vertex_last_enabled = false;
|
||||||
bool maintenance8_enabled = false;
|
bool maintenance8_enabled = false;
|
||||||
|
bool conditional_rendering_enabled = false;
|
||||||
bool supports_block_texel_view = false;
|
bool supports_block_texel_view = false;
|
||||||
bool mesh_shader_enabled = false;
|
bool mesh_shader_enabled = false;
|
||||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
|||||||
total_size += Common::AlignUp(end - start, 64);
|
total_size += Common::AlignUp(end - start, 64);
|
||||||
});
|
});
|
||||||
m_gpu_modified_ranges.Subtract(address, bytes);
|
m_gpu_modified_ranges.Subtract(address, bytes);
|
||||||
|
ForgetColorMetadataChecks(address, bytes);
|
||||||
});
|
});
|
||||||
if (copies.empty()) {
|
if (copies.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -519,6 +520,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
|||||||
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
|
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
|
||||||
if (is_written) {
|
if (is_written) {
|
||||||
m_gpu_modified_ranges.Add(vaddr, size);
|
m_gpu_modified_ranges.Add(vaddr, size);
|
||||||
|
ForgetColorMetadataChecks(vaddr, size);
|
||||||
}
|
}
|
||||||
return {&buffer, buffer.Offset(vaddr)};
|
return {&buffer, buffer.Offset(vaddr)};
|
||||||
}
|
}
|
||||||
@@ -623,6 +625,29 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
|
|||||||
return address + m_slot_buffers[id].Size() > vaddr;
|
return address + m_slot_buffers[id].Size() > vaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferCache::MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) {
|
||||||
|
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||||
|
if ((keys & (1u << slot)) != 0) {
|
||||||
|
m_checked_color_metadata[slot].Add(vaddr, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferCache::IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const {
|
||||||
|
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||||
|
if ((keys & (1u << slot)) != 0 && !m_checked_color_metadata[slot].Contains(vaddr, size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferCache::ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size) {
|
||||||
|
for (auto& checked: m_checked_color_metadata) {
|
||||||
|
checked.Subtract(vaddr, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||||
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -79,6 +80,10 @@ public:
|
|||||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||||
|
// Color metadata the GPU found free of the clear keys in `keys` (bits of code >> 5) stays so
|
||||||
|
// until a GPU write or download.
|
||||||
|
void MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys);
|
||||||
|
[[nodiscard]] bool IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const;
|
||||||
void ProcessFaultBuffer();
|
void ProcessFaultBuffer();
|
||||||
void SynchronizeBda(const RangeSet& mapped);
|
void SynchronizeBda(const RangeSet& mapped);
|
||||||
void QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
void QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||||
@@ -124,6 +129,7 @@ private:
|
|||||||
// Synchronous downloads publish before returning; asynchronous callers wait before reuse.
|
// Synchronous downloads publish before returning; asynchronous callers wait before reuse.
|
||||||
template <bool async>
|
template <bool async>
|
||||||
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||||
|
void ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size);
|
||||||
|
|
||||||
GraphicContext& m_graphics;
|
GraphicContext& m_graphics;
|
||||||
CommandScheduler& m_scheduler;
|
CommandScheduler& m_scheduler;
|
||||||
@@ -135,6 +141,8 @@ private:
|
|||||||
BufferMap m_buffers;
|
BufferMap m_buffers;
|
||||||
PageTable m_page_table;
|
PageTable m_page_table;
|
||||||
RangeSet m_gpu_modified_ranges;
|
RangeSet m_gpu_modified_ranges;
|
||||||
|
// One per TileManager::ColorClearSlots key slot.
|
||||||
|
std::array<RangeSet, 8> m_checked_color_metadata;
|
||||||
MemoryTracker m_memory_tracker;
|
MemoryTracker m_memory_tracker;
|
||||||
StreamBuffer m_staging_buffer;
|
StreamBuffer m_staging_buffer;
|
||||||
StreamBuffer m_stream_buffer;
|
StreamBuffer m_stream_buffer;
|
||||||
|
|||||||
+69
-5
@@ -1067,15 +1067,78 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
|||||||
if (first >= layers || count > layers - first) {
|
if (first >= layers || count > layers - first) {
|
||||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||||
}
|
}
|
||||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
const auto slice_size = range.size / layers;
|
||||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||||
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
|
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||||
|
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||||
|
uint32_t keys = 0;
|
||||||
|
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||||
|
clears[key].aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
|
if (DecodeColorClear(desc, static_cast<uint8_t>(key << 5u),
|
||||||
|
clears[key].clearValue.color)) {
|
||||||
|
keys |= 1u << key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No selected key to clear, or none left since the last check.
|
||||||
|
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::scoped_lock lock {m_lock};
|
||||||
|
auto& image = m_slot_images[id];
|
||||||
|
// Compressed images cannot upload, so they must already hold native contents.
|
||||||
|
const bool native_current = (image.usage.render_target || image.IsGpuModified()) &&
|
||||||
|
!image.IsBufferModified() && !image.IsCpuDirty();
|
||||||
|
// 2D-array attachment views select 2D layers or, of a volume, depth slices.
|
||||||
|
if (m_graphics.conditional_rendering_enabled &&
|
||||||
|
image.backing.image_type != vk::ImageType::e1D &&
|
||||||
|
count <= m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers &&
|
||||||
|
(image.backing.usage & vk::ImageUsageFlagBits::eColorAttachment) &&
|
||||||
|
(m_graphics.GetFormatProperties(view.format).optimalTilingFeatures &
|
||||||
|
vk::FormatFeatureFlagBits::eColorAttachment) &&
|
||||||
|
(desc.info.metadata.compression == VideoOutCompression::Uncompressed ||
|
||||||
|
native_current)) {
|
||||||
|
// The GPU selects each slice's clear, so the CPU never waits for the metadata.
|
||||||
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||||
|
InitializeImage(id);
|
||||||
|
const bool expand = desc.type != BindingType::VideoOut;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
const auto command = m_scheduler.Current().Handle();
|
||||||
|
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||||
|
image_first, count, nullptr, command);
|
||||||
|
vk::ClearRect rect {.rect = area, .layerCount = 1};
|
||||||
|
for (uint32_t slice = 0; slice < count; slice++) {
|
||||||
|
rect.baseArrayLayer = slice;
|
||||||
|
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||||
|
if ((keys & (1u << key)) != 0) {
|
||||||
|
const vk::ConditionalRenderingBeginInfoEXT predicate {
|
||||||
|
.buffer = predicates,
|
||||||
|
.offset = (slice * clears.size() + key) * sizeof(uint32_t)};
|
||||||
|
command.beginConditionalRenderingEXT(&predicate);
|
||||||
|
command.clearAttachments(1, &clears[key], 1, &rect);
|
||||||
|
command.endConditionalRenderingEXT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command.endRendering();
|
||||||
|
CommitGpuWrite(image);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finish native metadata writes before reading backing bytes. This can submit the
|
||||||
|
// scheduler, so discovery runs before final draw uploads and never holds the texture
|
||||||
|
// lock across it.
|
||||||
Common::DebugCounters::ScopedTimer timer(
|
Common::DebugCounters::ScopedTimer timer(
|
||||||
Common::DebugCounters::Counter::ColorMetadataReadbacks,
|
Common::DebugCounters::Counter::ColorMetadataReadbacks,
|
||||||
Common::DebugCounters::Counter::ColorMetadataReadbackNs);
|
Common::DebugCounters::Counter::ColorMetadataReadbackNs);
|
||||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||||
}
|
}
|
||||||
const auto slice_size = range.size / layers;
|
|
||||||
for (uint32_t slice = 0; slice < count; slice++) {
|
for (uint32_t slice = 0; slice < count; slice++) {
|
||||||
const auto address = range.address + slice_size * (first + slice);
|
const auto address = range.address + slice_size * (first + slice);
|
||||||
uint8_t code = 0;
|
uint8_t code = 0;
|
||||||
@@ -1768,6 +1831,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
|||||||
copy_size != image.info.data.size)) {
|
copy_size != image.info.data.size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
ForgetColorMetadataChecks(image.info.data.address, copy_size);
|
||||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||||
#include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h"
|
#include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h"
|
||||||
|
#include "gpu_tiler_shaders/gpu_tiler_color_clear_spv.h"
|
||||||
#include "graphics/host_gpu/graphicContext.h"
|
#include "graphics/host_gpu/graphicContext.h"
|
||||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||||
@@ -71,8 +72,8 @@ TileManager::~TileManager() {
|
|||||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto pipeline:
|
for (auto pipeline: {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform,
|
||||||
{m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform}) {
|
m_color_clear}) {
|
||||||
if (pipeline != nullptr) {
|
if (pipeline != nullptr) {
|
||||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||||
}
|
}
|
||||||
@@ -627,4 +628,60 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||||
|
bool expand) {
|
||||||
|
struct Check {
|
||||||
|
uint32_t metadata_base;
|
||||||
|
uint32_t words;
|
||||||
|
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);
|
||||||
|
if (m_color_clear == nullptr) {
|
||||||
|
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||||
|
m_clear_predicates = std::make_unique<Buffer>(
|
||||||
|
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
|
||||||
|
vk::BufferUsageFlagBits::eStorageBuffer |
|
||||||
|
vk::BufferUsageFlagBits::eConditionalRenderingEXT,
|
||||||
|
uint64_t {max_slices} * ColorClearSlots * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
const auto source = BindStorage(metadata, metadata.size);
|
||||||
|
const vk::DescriptorBufferInfo infos[] {source.info,
|
||||||
|
{m_clear_predicates->Handle(), 0, VK_WHOLE_SIZE}};
|
||||||
|
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||||
|
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||||
|
writes[index].dstBinding = index;
|
||||||
|
writes[index].descriptorCount = 1;
|
||||||
|
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||||
|
writes[index].pBufferInfo = &infos[index];
|
||||||
|
}
|
||||||
|
// Orders the metadata writers and the previous check's predicate reads before this check.
|
||||||
|
vk::MemoryBarrier barrier {
|
||||||
|
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
|
||||||
|
.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||||
|
m_scheduler.EndRendering();
|
||||||
|
auto command = m_scheduler.Current().Handle();
|
||||||
|
command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||||
|
vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, nullptr,
|
||||||
|
0, nullptr);
|
||||||
|
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};
|
||||||
|
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||||
|
&push);
|
||||||
|
command.dispatch(slices, 1, 1);
|
||||||
|
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||||
|
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||||
|
vk::PipelineStageFlagBits::eAllCommands, {}, 1, &barrier, 0, nullptr,
|
||||||
|
0, nullptr);
|
||||||
|
return m_clear_predicates->Handle();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics
|
} // namespace Libs::Graphics
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ public:
|
|||||||
const D16Layout& layout);
|
const D16Layout& layout);
|
||||||
[[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host);
|
[[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host);
|
||||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct TileManagerTestAccess;
|
friend struct TileManagerTestAccess;
|
||||||
@@ -132,6 +139,8 @@ private:
|
|||||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||||
vk::Pipeline m_color_transform = nullptr;
|
vk::Pipeline m_color_transform = nullptr;
|
||||||
|
vk::Pipeline m_color_clear = nullptr;
|
||||||
|
std::unique_ptr<Buffer> m_clear_predicates;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libs::Graphics
|
} // namespace Libs::Graphics
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// One workgroup per metadata slice; see TileManager::CheckColorClear.
|
||||||
|
layout(local_size_x = 256) in;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0, std430) buffer Metadata { uint data[]; } metadata;
|
||||||
|
layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; } predicates;
|
||||||
|
|
||||||
|
layout(push_constant, std430) uniform Check {
|
||||||
|
uint metadata_base;
|
||||||
|
uint words;
|
||||||
|
uint keys;
|
||||||
|
uint expand;
|
||||||
|
} params;
|
||||||
|
|
||||||
|
const uint Slots = 8u; // TileManager::ColorClearSlots
|
||||||
|
|
||||||
|
shared uint key;
|
||||||
|
shared uint mismatch;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const uint slice = gl_WorkGroupID.x;
|
||||||
|
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||||
|
// Each invocation reads, and may rewrite, only its own words.
|
||||||
|
if (gl_LocalInvocationIndex == 0u) {
|
||||||
|
key = metadata.data[base] & 0xffu;
|
||||||
|
mismatch = 0u;
|
||||||
|
}
|
||||||
|
barrier();
|
||||||
|
const uint pattern = key * 0x01010101u;
|
||||||
|
bool same = true;
|
||||||
|
for (uint i = gl_LocalInvocationIndex; same && i < params.words; i += gl_WorkGroupSize.x) {
|
||||||
|
same = metadata.data[base + i] == pattern;
|
||||||
|
}
|
||||||
|
if (!same) {
|
||||||
|
atomicOr(mismatch, 1u);
|
||||||
|
}
|
||||||
|
barrier();
|
||||||
|
const uint slot = key >> 5u;
|
||||||
|
const bool hit = mismatch == 0u && (key & 0x1fu) == 0u && (params.keys & (1u << slot)) != 0u;
|
||||||
|
if (gl_LocalInvocationIndex < Slots) {
|
||||||
|
predicates.data[slice * Slots + gl_LocalInvocationIndex] =
|
||||||
|
uint(hit && slot == gl_LocalInvocationIndex);
|
||||||
|
}
|
||||||
|
if (hit && params.expand != 0u) {
|
||||||
|
for (uint i = gl_LocalInvocationIndex; i < params.words; i += gl_WorkGroupSize.x) {
|
||||||
|
metadata.data[base + i] = 0xffffffffu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -313,6 +313,7 @@ void GpuActivityRows(const Snapshot& shown) {
|
|||||||
Row("DCC/CMASK readbacks",
|
Row("DCC/CMASK readbacks",
|
||||||
Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6,
|
Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6,
|
||||||
Rate(shown, Counter::ColorMetadataReadbacks)));
|
Rate(shown, Counter::ColorMetadataReadbacks)));
|
||||||
|
Row("DCC/CMASK GPU checks", Fmt("%.0f/s", Rate(shown, Counter::ColorMetadataChecks)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilationRows(const Snapshot& shown) {
|
void CompilationRows(const Snapshot& shown) {
|
||||||
|
|||||||
@@ -499,6 +499,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
image_atomic_int64.pNext = supported_features2.pNext;
|
image_atomic_int64.pNext = supported_features2.pNext;
|
||||||
supported_features2.pNext = &image_atomic_int64;
|
supported_features2.pNext = &image_atomic_int64;
|
||||||
}
|
}
|
||||||
|
const bool conditional_rendering_extension =
|
||||||
|
HasExtension(device_extensions, VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
|
||||||
|
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering {};
|
||||||
|
if (conditional_rendering_extension) {
|
||||||
|
conditional_rendering.pNext = supported_features2.pNext;
|
||||||
|
supported_features2.pNext = &conditional_rendering;
|
||||||
|
}
|
||||||
const bool barycentric_extension =
|
const bool barycentric_extension =
|
||||||
HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {};
|
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {};
|
||||||
@@ -572,6 +579,8 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||||
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||||
|
graphics.conditional_rendering_enabled =
|
||||||
|
conditional_rendering_extension && conditional_rendering.conditionalRendering;
|
||||||
LOGF("Vulkan maintenance8 support: %s\n",
|
LOGF("Vulkan maintenance8 support: %s\n",
|
||||||
graphics.maintenance8_enabled ? "true" : "false");
|
graphics.maintenance8_enabled ? "true" : "false");
|
||||||
LOGF("Vulkan depth feedback support: %s\n",
|
LOGF("Vulkan depth feedback support: %s\n",
|
||||||
@@ -670,6 +679,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||||
create_info.pNext = &maintenance8;
|
create_info.pNext = &maintenance8;
|
||||||
}
|
}
|
||||||
|
if (graphics.conditional_rendering_enabled) {
|
||||||
|
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||||
|
create_info.pNext = &conditional_rendering;
|
||||||
|
}
|
||||||
create_info.pQueueCreateInfos = &queue_create_info;
|
create_info.pQueueCreateInfos = &queue_create_info;
|
||||||
create_info.queueCreateInfoCount = 1;
|
create_info.queueCreateInfoCount = 1;
|
||||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||||
@@ -1046,7 +1059,8 @@ void WindowContext::CreateVulkan() {
|
|||||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME}) {
|
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
||||||
|
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}) {
|
||||||
if (HasExtension(available_extensions, extension)) {
|
if (HasExtension(available_extensions, extension)) {
|
||||||
device_extensions.push_back(extension);
|
device_extensions.push_back(extension);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10266,6 +10266,32 @@ public:
|
|||||||
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
|
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
|
||||||
std::vector<u32>{0x5a5a5a5a},
|
std::vector<u32>{0x5a5a5a5a},
|
||||||
"clearing one volume slice changed untouched slice contents");
|
"clearing one volume slice changed untouched slice contents");
|
||||||
|
// A GPU writer clears slice 9's metadata; its bind selects the clear on the GPU.
|
||||||
|
auto [writer, writer_offset] = resources.GetBufferCache().ObtainBuffer(
|
||||||
|
dcc_address + 9 * metadata_slice_size, metadata_slice_size, true);
|
||||||
|
writer->Fill(writer_offset, metadata_slice_size, 0);
|
||||||
|
Common::DebugCounters::SetEnabled(true);
|
||||||
|
const auto readbacks =
|
||||||
|
Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataReadbacks);
|
||||||
|
const auto checks =
|
||||||
|
Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||||
|
RenderExecutorTestAccess::ResetBindings(executor);
|
||||||
|
registers.SetColorView(0, {.base_array_slice_index = 9, .last_array_slice_index = 9});
|
||||||
|
RenderColorInfo gpu_slice{};
|
||||||
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||||
|
executor, scheduler.Current(), gpu_slice, 0);
|
||||||
|
Common::DebugCounters::SetEnabled(false);
|
||||||
|
Require(name, "GPU-written volume metadata clears on the GPU",
|
||||||
|
gpu_slice.image_id == color.image_id &&
|
||||||
|
Common::DebugCounters::Get(
|
||||||
|
Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks &&
|
||||||
|
Common::DebugCounters::Get(
|
||||||
|
Common::DebugCounters::Counter::ColorMetadataChecks) == checks + 1 &&
|
||||||
|
ReadCachedTexel(name, context, color.image_id, {0, 0, 9}) ==
|
||||||
|
std::vector<u32>{0} &&
|
||||||
|
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
|
||||||
|
std::vector<u32>{0x5a5a5a5a},
|
||||||
|
"a volume slice's GPU-written metadata was read back or cleared other slices");
|
||||||
auto storage_desc = color.desc;
|
auto storage_desc = color.desc;
|
||||||
storage_desc.type = BindingType::Storage;
|
storage_desc.type = BindingType::Storage;
|
||||||
storage_desc.info.metadata = {};
|
storage_desc.info.metadata = {};
|
||||||
@@ -12271,14 +12297,43 @@ public:
|
|||||||
const auto readbacks =
|
const auto readbacks =
|
||||||
Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataReadbacks);
|
Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataReadbacks);
|
||||||
bind();
|
bind();
|
||||||
Common::DebugCounters::SetEnabled(false);
|
|
||||||
Require(name, "metadata readback counter",
|
|
||||||
Common::DebugCounters::Get(
|
|
||||||
Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks + 1,
|
|
||||||
"binding GPU-written metadata did not count exactly one readback");
|
|
||||||
Require(name, "same-value raw native re-clear",
|
Require(name, "same-value raw native re-clear",
|
||||||
read_texel() == expected,
|
read_texel() == expected,
|
||||||
"a repeated metadata fill did not clear an already drawn target");
|
"a repeated metadata fill did not clear an already drawn target");
|
||||||
|
const auto checks =
|
||||||
|
Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||||
|
bind();
|
||||||
|
Require(name, "checked metadata skips rebinding",
|
||||||
|
Common::DebugCounters::Get(
|
||||||
|
Common::DebugCounters::Counter::ColorMetadataChecks) == checks,
|
||||||
|
"rebinding unwritten GPU-checked metadata repeated its clear check");
|
||||||
|
check_expanded_metadata();
|
||||||
|
paint();
|
||||||
|
fill_metadata(metadata_words - 1, true);
|
||||||
|
bind();
|
||||||
|
Require(name, "partial raw native metadata fill", retains_painted_texel(),
|
||||||
|
"a nonuniform GPU-written metadata slice cleared the whole target");
|
||||||
|
fill_metadata(metadata_words, true);
|
||||||
|
bind();
|
||||||
|
Require(name, "complete raw native fill after partial fill", read_texel() == expected,
|
||||||
|
"completing a GPU-written metadata fill did not restore its clear");
|
||||||
|
if (layout == Prospero::ChannelLayout::k16_16_16_16 && !fill_case.cmask &&
|
||||||
|
!fill_case.reuse_unorm) {
|
||||||
|
auto video = color.desc;
|
||||||
|
video.type = BindingType::VideoOut;
|
||||||
|
paint();
|
||||||
|
fill_metadata(metadata_words, true);
|
||||||
|
const bool cleared =
|
||||||
|
texture_cache.FindImage(video) == color.image_id && read_texel() == expected;
|
||||||
|
std::vector<uint32_t> keys(metadata_words);
|
||||||
|
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
|
||||||
|
Require(name, "video-out clear keeps its metadata keys",
|
||||||
|
cleared &&
|
||||||
|
LibKernel::Memory::TryReadBacking(dcc_address, keys.data(), metadata_size) &&
|
||||||
|
std::ranges::all_of(keys,
|
||||||
|
[&](uint32_t word) { return word == fill_case.fill; }),
|
||||||
|
"a video-out bind skipped its clear or expanded the flip's metadata keys");
|
||||||
|
}
|
||||||
paint();
|
paint();
|
||||||
WriteMetadata(context, dcc_address, metadata_size, UINT32_MAX);
|
WriteMetadata(context, dcc_address, metadata_size, UINT32_MAX);
|
||||||
fill_metadata(metadata_words - 1);
|
fill_metadata(metadata_words - 1);
|
||||||
@@ -12287,15 +12342,15 @@ public:
|
|||||||
retains_painted_texel(),
|
retains_painted_texel(),
|
||||||
"a nonuniform metadata allocation cleared the whole target");
|
"a nonuniform metadata allocation cleared the whole target");
|
||||||
fill_metadata(metadata_words);
|
fill_metadata(metadata_words);
|
||||||
if (fill_case.goat) {
|
|
||||||
Require(name, "repeated Goat fill stays CPU-owned",
|
|
||||||
!resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size),
|
|
||||||
"re-clearing expanded metadata required another GPU readback");
|
|
||||||
}
|
|
||||||
bind();
|
bind();
|
||||||
Require(name, "complete fill after partial fill",
|
Require(name, "complete fill after partial fill",
|
||||||
read_texel() == expected,
|
read_texel() == expected,
|
||||||
"completing the metadata overwrite did not restore its clear");
|
"completing the metadata overwrite did not restore its clear");
|
||||||
|
Common::DebugCounters::SetEnabled(false);
|
||||||
|
Require(name, "GPU-written metadata stays on the GPU",
|
||||||
|
Common::DebugCounters::Get(
|
||||||
|
Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks,
|
||||||
|
"binding GPU-written color metadata read it back");
|
||||||
|
|
||||||
// Consuming a clear key updates metadata backing without invalidating
|
// Consuming a clear key updates metadata backing without invalidating
|
||||||
// a pooled image whose first texel precedes the metadata range.
|
// a pooled image whose first texel precedes the metadata range.
|
||||||
@@ -20434,6 +20489,7 @@ private:
|
|||||||
m_runtime_context.queue = m_queue;
|
m_runtime_context.queue = m_queue;
|
||||||
m_runtime_context.shader_image_int64_atomics_enabled = true;
|
m_runtime_context.shader_image_int64_atomics_enabled = true;
|
||||||
m_runtime_context.maintenance8_enabled = true;
|
m_runtime_context.maintenance8_enabled = true;
|
||||||
|
m_runtime_context.conditional_rendering_enabled = true;
|
||||||
m_runtime_context.attachment_feedback_loop_enabled = m_rasterization_supported;
|
m_runtime_context.attachment_feedback_loop_enabled = m_rasterization_supported;
|
||||||
m_runtime_context.provoking_vertex_last_enabled = m_rasterization_supported;
|
m_runtime_context.provoking_vertex_last_enabled = m_rasterization_supported;
|
||||||
const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{
|
const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{
|
||||||
@@ -20717,7 +20773,10 @@ private:
|
|||||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2{};
|
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2{};
|
||||||
robustness2.nullDescriptor = true;
|
robustness2.nullDescriptor = true;
|
||||||
robustness2.pNext = &maintenance8;
|
robustness2.pNext = &maintenance8;
|
||||||
device_info.pNext = &robustness2;
|
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering{};
|
||||||
|
conditional_rendering.conditionalRendering = true;
|
||||||
|
conditional_rendering.pNext = &robustness2;
|
||||||
|
device_info.pNext = &conditional_rendering;
|
||||||
vk::PhysicalDeviceFeatures device_features{};
|
vk::PhysicalDeviceFeatures device_features{};
|
||||||
device_features.shaderStorageImageWriteWithoutFormat = true;
|
device_features.shaderStorageImageWriteWithoutFormat = true;
|
||||||
device_features.shaderImageGatherExtended = true;
|
device_features.shaderImageGatherExtended = true;
|
||||||
@@ -20736,7 +20795,8 @@ private:
|
|||||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||||
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||||
VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
||||||
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME};
|
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME,
|
||||||
|
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME};
|
||||||
if (m_rasterization_supported) {
|
if (m_rasterization_supported) {
|
||||||
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||||
device_extensions.push_back(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
|
device_extensions.push_back(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
|
||||||
|
|||||||
Reference in New Issue
Block a user