render: apply DCC clears before binding images

Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
This commit is contained in:
nmzik
2026-09-06 08:00:59 +04:00
parent 0456f5a06b
commit 44f982bca7
11 changed files with 435 additions and 211 deletions
+25
View File
@@ -1221,6 +1221,31 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
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) {
total_size = {};
if (width == 0 || height == 0 || slices == 0 || 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)) {
return false;
}
// Gen5 color metadata 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.
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u);
const uint32_t block_height = 1u << (coverage_bits / 2u);
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 TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
+3
View File
@@ -122,6 +122,9 @@ 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);
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);
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
+183 -45
View File
@@ -32,6 +32,78 @@ namespace {
constexpr uint64_t NumFramesBeforeRemoval = 32;
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, vk::Format format,
uint32_t fill, vk::ClearColorValue& clear) {
const auto code = static_cast<uint8_t>(fill);
if (fill != static_cast<uint32_t>(code) * 0x01010101u) {
return false;
}
const auto& metadata = desc.info.metadata;
if (code == 0x20) {
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
return desc.type == TextureCache::BindingType::RenderTarget &&
metadata.dcc_clear_register_valid &&
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
}
if (code != 0x00 && code != 0x40 && code != 0x80 && code != 0xc0) {
return false;
}
clear = {};
if (code == 0x00) {
return true;
}
switch (format) {
case vk::Format::eR8Unorm:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eR4G4B4A4UnormPack16:
case vk::Format::eR16Unorm:
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16Sfloat:
case vk::Format::eR16G16Sfloat:
case vk::Format::eR16G16B16A16Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32G32Sfloat:
case vk::Format::eR32G32B32A32Sfloat:
case vk::Format::eB10G11R11UfloatPack32: break;
default: return false;
}
const float rgb = (code & 0x80u) != 0 ? 1.0f : 0.0f;
const float alpha = (code & 0x40u) != 0 ? 1.0f : 0.0f;
std::array<float, 4> channels {rgb, rgb, rgb, alpha};
if (!metadata.dcc_alpha_msb) {
std::swap(channels[0], channels[3]);
}
// DCC clear decoding clamps missing lanes before applying the format swizzle.
const auto components = vk::componentCount(format);
if (components == 1) {
channels[0] = channels[3];
} else if (components == 2) {
channels[1] = channels[3];
}
switch (format) {
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eR5G6B5UnormPack16: std::swap(channels[0], channels[2]); break;
case vk::Format::eR4G4B4A4UnormPack16:
std::reverse(channels.begin(), channels.end());
break;
default: break;
}
clear.float32 = channels;
return true;
}
[[nodiscard]] const char* BindingTypeName(TextureCache::BindingType type) {
switch (type) {
case TextureCache::BindingType::Texture: return "Texture";
@@ -1062,6 +1134,56 @@ void TextureCache::InitializeImage(ImageId id) {
}
}
void TextureCache::PrepareDccClear(ImageId id, const ImageDesc& desc) {
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return;
}
auto& image = m_slot_images[id];
image.info.metadata = desc.info.metadata;
auto [entry, inserted] = m_surface_metas.try_emplace(
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
auto& metadata = entry->second;
if (!inserted && metadata.type == MetaDataInfo::Type::PendingDcc) {
metadata.type = MetaDataInfo::Type::Dcc;
} else if (metadata.type != MetaDataInfo::Type::Dcc) {
EXIT("TextureCache: image reuses non-DCC metadata\n");
}
if (metadata.clear_mask == 0 || image.info.resources.levels != 1 ||
desc.info.metadata.range.size == 0 || metadata.fill_size < desc.info.metadata.range.size) {
return;
}
vk::ClearValue clear {};
if (!DecodeDccClear(desc, image.backing.format, metadata.fill_value, clear.color)) {
return;
}
const auto& view = desc.view_info;
const bool volume_texture = image.info.IsVolume() && view.type == vk::ImageViewType::e3D;
const auto first = volume_texture ? 0u : view.base_layer;
const auto count = volume_texture ? std::max(image.info.extent.depth >> view.base_level, 1u)
: view.layer_count;
if (first >= 32 || count > 32 - first) {
return;
}
// The metadata fill covers the complete allocation. Consume each layer only after its
// native image contents exist; already materialized layers may have been rendered since.
for (uint32_t layer = first; layer < first + count;) {
if ((metadata.clear_mask & (1u << layer)) == 0) {
layer++;
continue;
}
const auto start = layer;
uint32_t mask = 0;
do {
mask |= 1u << layer++;
} while (layer < first + count && (metadata.clear_mask & (1u << layer)) != 0);
ClearImage(m_scheduler.Current(), id,
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count, start,
layer - start},
clear);
metadata.clear_mask &= ~mask;
}
}
void TextureCache::RefreshImage(ImageId id) {
TrackImage(id);
auto& image = m_slot_images[id];
@@ -1253,6 +1375,7 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
PrepareDccClear(id, desc);
RefreshImage(id);
}
switch (desc.type) {
@@ -1285,20 +1408,8 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
TouchImage(image);
image.MarkGpuModified();
image.usage.render_target = true;
PrepareDccClear(id, desc);
RefreshImage(id);
// 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);
TrackImageDownload(id, image);
const auto view = image.FindView(desc.view_info);
@@ -1422,8 +1533,8 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
}
clear.depthStencil.stencil = stencil_clear;
}
ClearImage(command, selected, {aspect, 0, image.info.resources.levels, 0, image.backing.layers},
clear);
ClearImage(command, selected,
{aspect, 0, image.info.resources.levels, 0, image.info.TransferLayers()}, clear);
return true;
}
@@ -1432,27 +1543,70 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id,
auto& image = m_slot_images[id];
const auto aspects = image.info.IsDepth() ? ImageViewOps::DepthAspectMask(image.backing.format)
: vk::ImageAspectFlagBits::eColor;
EXIT_IF(command.IsInvalid() || image.depth_id || !range.aspectMask ||
range.levelCount == 0 || range.baseMipLevel >= image.info.resources.levels ||
EXIT_IF(range.baseMipLevel >= image.info.resources.levels);
const auto layers = image.info.IsVolume()
? std::max(image.info.extent.depth >> range.baseMipLevel, 1u)
: image.backing.layers;
EXIT_IF(command.IsInvalid() || image.depth_id || !range.aspectMask || range.levelCount == 0 ||
range.levelCount > image.info.resources.levels - range.baseMipLevel ||
range.layerCount == 0 || range.baseArrayLayer >= image.backing.layers ||
range.layerCount > image.backing.layers - range.baseArrayLayer ||
range.layerCount == 0 || range.baseArrayLayer >= layers ||
range.layerCount > layers - range.baseArrayLayer ||
(range.aspectMask & aspects) != range.aspectMask);
if (image.IsBufferModified() || image.IsCpuDirty()) {
const bool full_image = range.aspectMask == aspects && range.baseMipLevel == 0 &&
range.levelCount == image.info.resources.levels &&
range.baseArrayLayer == 0 && range.layerCount == layers;
TrackImage(id);
if (!full_image && (image.IsBufferModified() || image.IsCpuDirty())) {
InitializeImage(id);
if (image.info.samples == 1 && (image.IsBufferModified() || image.IsCpuDirty())) {
EXIT("TextureCache: image clear retained guest ownership\n");
}
}
command.EndRendering();
if (image.info.IsVolume() && !full_image) {
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
range.levelCount != 1);
ImageViewInfo view {};
view.format = image.backing.format;
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
view.base_level = range.baseMipLevel;
view.base_layer = range.baseArrayLayer;
view.layer_count = range.layerCount;
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
vk::AccessFlagBits2::eColorAttachmentWrite, {}, command.Handle());
vk::RenderingAttachmentInfo attachment {};
attachment.imageView = image.FindView(view);
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
attachment.loadOp = vk::AttachmentLoadOp::eClear;
attachment.storeOp = vk::AttachmentStoreOp::eStore;
attachment.clearValue = clear;
vk::RenderingInfo rendering {};
rendering.renderArea.extent = {
std::max(image.info.extent.width >> range.baseMipLevel, 1u),
std::max(image.info.extent.height >> range.baseMipLevel, 1u)};
rendering.layerCount = range.layerCount;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
command.Handle().beginRendering(&rendering);
command.Handle().endRendering();
CommitGpuWrite(image);
return;
}
image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {},
command.Handle());
auto native_range = range;
if (image.info.IsVolume()) {
native_range.baseArrayLayer = 0;
native_range.layerCount = 1;
}
if (range.aspectMask == vk::ImageAspectFlagBits::eColor) {
command.Handle().clearColorImage(image.backing.image, vk::ImageLayout::eTransferDstOptimal,
&clear.color, 1, &range);
&clear.color, 1, &native_range);
} else {
command.Handle().clearDepthStencilImage(
image.backing.image, vk::ImageLayout::eTransferDstOptimal, &clear.depthStencil, 1, &range);
command.Handle().clearDepthStencilImage(image.backing.image,
vk::ImageLayout::eTransferDstOptimal,
&clear.depthStencil, 1, &native_range);
}
CommitGpuWrite(image);
}
@@ -1757,7 +1911,7 @@ bool TextureCache::ClearMeta(uint64_t address) {
return true;
}
bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
void TextureCache::TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid DCC fill range\n");
}
@@ -1778,31 +1932,15 @@ bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t f
}
}();
std::scoped_lock lock {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) {
// The guest dispatch still writes metadata. An unknown address remains PendingDcc until an
// image descriptor confirms its role; never reinterpret CMask/FMask/HTile as DCC.
const auto found = m_surface_metas.try_emplace(address).first;
if (found->second.type == MetaDataInfo::Type::PendingDcc ||
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 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) {
+5 -5
View File
@@ -72,9 +72,8 @@ public:
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[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);
// Record deferred DCC state while the original guest dispatch writes the metadata.
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -88,7 +87,7 @@ private:
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,
// PendingDcc retains that exact fill until an image binding classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
@@ -138,6 +137,7 @@ private:
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id);
void PrepareDccClear(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id);
[[nodiscard]] TextureTransferPlan
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
@@ -147,7 +147,7 @@ private:
uint64_t destination_size, DownloadPlan plan);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
// Caller holds m_lock. Clears only the selected aspects and subresources.
// Caller holds m_lock. Volume layer ranges select depth slices.
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
const vk::ClearValue& clear);
void PrepareImageCopy(Image& image);
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -21,41 +22,22 @@ namespace Libs::Graphics {
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::eR8Unorm:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eR4G4B4A4UnormPack16:
case vk::Format::eR16Unorm:
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16Sfloat:
case vk::Format::eR16G16Sfloat:
case vk::Format::eR16G16B16A16Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32G32Sfloat:
case vk::Format::eR32G32B32A32Sfloat:
case vk::Format::eB10G11R11UfloatPack32:
info.metadata_fixed_clear_supported = has_dcc;
break;
default: info.metadata_fixed_clear_supported = false; break;
static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
switch (info.format) {
case Prospero::ChannelLayout::k10_10_10_2:
case Prospero::ChannelLayout::k10_10_10_2Float:
case Prospero::ChannelLayout::k5_5_5_1: return true;
case Prospero::ChannelLayout::k2_10_10_10:
case Prospero::ChannelLayout::k1_5_5_5: return false;
default: break;
}
if (!info.metadata_clear_supported) {
info.color_clear_value = {};
const auto components =
Prospero::ResolveRenderTargetFormat(info.format, info.channel_type).components;
if (components == 1) {
return info.channel_order != Prospero::ChannelOrder::kStandard;
}
return components == 3 || info.channel_order == Prospero::ChannelOrder::kStandard ||
info.channel_order == Prospero::ChannelOrder::kAlt;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
@@ -329,10 +311,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
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;
TileSizeAlign metadata_size {};
(void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element,
levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size};
desc.info.metadata.dcc_clear_word = rt.clear_word0.word0;
desc.info.metadata.dcc_clear_register_valid = true;
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
}
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
@@ -378,7 +364,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
r.guest_array_layer = view.base_layer;
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.export_mapping = target_format.export_mapping;
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
BindRenderTarget(r.image_id);
}
@@ -19,9 +19,6 @@ struct RenderColorInfo {
uint32_t guest_mip_level = 0;
uint32_t guest_array_layer = 0;
Prospero::ColorComponentMapping export_mapping;
bool metadata_clear_supported = false;
bool metadata_fixed_clear_supported = false;
vk::ClearColorValue color_clear_value {};
[[nodiscard]] vk::Extent2D Extent() const {
return {std::max(desc.info.extent.width >> guest_mip_level, 1u),
@@ -23,8 +23,11 @@ struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
uint32_t dcc_clear_word = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
bool dcc_clear_register_valid = false;
bool dcc_alpha_msb = true;
};
struct ImageSubresources {
@@ -30,6 +30,7 @@
#include <algorithm>
#include <atomic>
#include <bit>
#include <fmt/format.h>
#include <limits>
#include <span>
@@ -758,6 +759,16 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format);
desc.info.samples = samples;
desc.info.tile_mode = tile;
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth &&
!desc.info.IsDepth()) {
TileSizeAlign metadata_size {};
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
desc.info.bytes_per_block, levels, tile, metadata_size,
std::countr_zero(samples));
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
desc.info.metadata.dcc_alpha_msb = descriptor.DccAlphaPos();
}
if (samples > 1) {
desc.info.mip_layout[0] = {0, size.size, pitch, height};
} else {
@@ -189,19 +189,15 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
return false;
}
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);
// Track deferred DCC state while the original dispatch writes the metadata allocation.
cache.TrackDccFill(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
LOGF("GraphicsRenderDispatchDirect: metadata fill 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);
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
}
return registered_metadata;
return false;
}
static std::atomic<uint32_t> logged_clears {0};
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
+2 -75
View File
@@ -215,15 +215,14 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LOGF(
"DrawTargetState[%u]: frame=%d %s target=%s addr=0x%010" PRIx64
" extent=%ux%u prim=%u index_count=%u flags=0x%08" PRIx32 " color_mask=0x%08" PRIx32
" clear=%s clear_rgba=(%.3f,%.3f,%.3f,%.3f) cc_mode=%u cc_op=0x%02x"
" cc_mode=%u cc_op=0x%02x"
" blend=%s src=%u dst=%u comb=%u ps_tex=%d sampled=%d storage=%d ps_kill=%s target_mode0=%u"
" depth_test=%s depth_write=%s depth_func=%u depth_clear=%s viewport=(%.1f,%.1f %.1fx%.1f) "
"scissor=(%d,%d)-(%d,%d)\n",
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name, RenderColorTypeName(color),
color.desc.info.data.address, extent.width, extent.height,
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags, ctx.GetRenderTargetMask(),
"false", color.color_clear_value.float32[0], color.color_clear_value.float32[1],
color.color_clear_value.float32[2], color.color_clear_value.float32[3], cc.mode, cc.op,
cc.mode, cc.op,
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
static_cast<int>(ps_resources.images.size()), static_cast<int>(sampled_images),
static_cast<int>(ps_resources.images.size() - sampled_images),
@@ -460,70 +459,6 @@ struct DrawCallInfo {
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,
uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
@@ -581,14 +516,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
auto& attachment = state.color_attachments[i];
attachment.image_view = image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
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 = metadata_clear;
}
if (depth.image_id) {
const auto owner = cache.m_slot_images.try_get(depth.image_id);
+175 -36
View File
@@ -5796,10 +5796,10 @@ public:
texture_cache.FindImage(metadata_depth_a);
const auto metadata_depth_b_id =
texture_cache.FindImage(metadata_depth_b);
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
Require(
name, "pending DCC/HTile separation",
!texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) &&
!texture_cache.IsMeta(base + metadata_a),
!texture_cache.IsMeta(base + metadata_a),
"an unclassified PS5 DCC fill was exposed as registered metadata");
const auto metadata_depth_a_view =
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
@@ -5832,11 +5832,11 @@ public:
texture_cache.IsMeta(base + metadata_b) &&
texture_cache.IsMetaCleared(base + metadata_b, 0),
"image discovery retired aliased metadata or its live depth image");
const bool touched_metadata = texture_cache.TouchMeta(base + metadata_a, 0, false);
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
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),
touched_metadata && !texture_cache.IsMetaCleared(base + metadata_a, 0),
"a PS5 DCC fill changed registered HTile state");
Require(
name, "metadata first-touch state",
@@ -7649,7 +7649,9 @@ public:
void CheckRenderExecutorColorVolumeDiscovery() {
constexpr const char *name = "RenderExecutorColorVolumeDiscovery";
constexpr uintptr_t base = 0x0000000203e00000ull;
constexpr uint64_t allocation_size = 0x200000;
constexpr uint64_t color_size = 0x200000;
constexpr uint64_t metadata_size = 0x20000;
constexpr uint64_t allocation_size = color_size + metadata_size;
constexpr uint64_t allocation_alignment = 0x10000;
EnsureRuntimeContext();
@@ -7689,7 +7691,7 @@ public:
.tile_mode = Prospero::TileMode::kRenderTarget,
.dimension = 2,
.metadata_pipe_aligned = true});
constexpr uint64_t dcc_address = base + allocation_size - 0x1000;
constexpr uint64_t dcc_address = base + color_size;
registers.SetColorDccAddr(0, {.addr = dcc_address});
registers.SetColorClearWord0(0, {.word0 = 0});
registers.SetColorClearWord1(0, {.word1 = 0});
@@ -7700,9 +7702,9 @@ public:
auto &texture_cache = resources.GetTextureCache();
auto &executor = context.GetRenderExecutor();
resources.MapMemory(base, allocation_size);
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
Require(name, "deferred DCC clear",
!texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) &&
!texture_cache.IsMeta(dcc_address),
!texture_cache.IsMeta(dcc_address),
"a pre-registration DCC clear was not deferred");
RenderColorInfo color{};
@@ -7719,11 +7721,10 @@ public:
color.desc.info.extent == vk::Extent3D{32, 32, 32} &&
color.desc.info.resources == ImageSubresources{1, 1} &&
color.desc.info.pitch == 128 &&
color.desc.info.data.size == allocation_size &&
color.desc.info.data.size == color_size &&
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 &&
@@ -7753,23 +7754,17 @@ public:
sliced_color.desc.view_info.base_layer == 7 &&
sliced_rendering.num_color_attachments == 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");
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");
Require(name, "3D slice clear and preservation",
ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) ==
std::vector<u32>{0} &&
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
std::vector<u32>{0x5a5a5a5a},
"clearing one volume slice changed untouched slice contents");
auto storage_desc = color.desc;
storage_desc.type = BindingType::Storage;
storage_desc.info.metadata = {};
storage_desc.info.guest_format = Prospero::BufferFormat::k10_10_10_2UNorm;
storage_desc.view_info.type = vk::ImageViewType::e3D;
storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
@@ -7789,7 +7784,7 @@ public:
TextureCacheTestAccess::TryDownload(texture_cache, storage_id),
"the 3D render target could not be queued for guest-layout readback");
auto mirror = resources.GetBufferCache().ObtainBuffer(
base, allocation_size, false, true);
base, color_size, false, true);
Require(name, "volume mirror", mirror.first != nullptr,
"the 3D render-target readback has no BufferCache owner");
auto slice_probe =
@@ -7816,6 +7811,17 @@ public:
"render-target upload/readback lost the final Z slice");
DestroyBuffer(&slice_probe);
dcc_clear_value = 0;
texture_cache.TrackDccFill(dcc_address, metadata_size, 0xffffffffu);
const bool uncompressed =
!texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) &&
dcc_clear_value == 0xffffffffu;
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
Require(
name, "registered DCC code state",
uncompressed && texture_cache.IsMetaCleared(dcc_address, 7),
"DCC fill codes were not classified as clear or uncompressed");
for (const uint32_t base_slice : {0u, 7u}) {
RenderExecutorTestAccess::ResetBindings(executor);
registers.SetColorView(0, {.base_array_slice_index = base_slice,
@@ -7829,7 +7835,7 @@ public:
Require(name, "3D export bound exceeds depth",
volume_color.image_id == color.image_id &&
volume_color.desc.info.extent == vk::Extent3D{32, 32, 32} &&
volume_color.desc.info.data.size == allocation_size &&
volume_color.desc.info.data.size == color_size &&
volume_color.desc.view_info.type == vk::ImageViewType::e2DArray &&
volume_color.desc.view_info.base_layer == base_slice &&
volume_color.desc.view_info.layer_count == 32 - base_slice &&
@@ -7855,6 +7861,40 @@ public:
std::printf("[gpu] %-32s ok\n", name);
}
std::vector<u32> ReadCachedTexel(const char *name, RenderContext &context,
ImageId id, vk::Offset3D offset = {}) {
auto &scheduler = context.GetCommandScheduler();
auto &image = context.GetTextureCache().GetImage(id);
auto probe = CreateHostBuffer(name, image.info.bytes_per_block,
vk::BufferUsageFlagBits::eTransferDst, {});
scheduler.Current().EndRendering();
image.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {},
scheduler.Current().Handle());
vk::BufferImageCopy copy{};
copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
copy.imageOffset = offset;
copy.imageExtent = vk::Extent3D{1, 1, 1};
scheduler.Current().Handle().copyImageToBuffer(
image.backing.image, vk::ImageLayout::eTransferSrcOptimal,
probe.buffer, 1, &copy);
vk::BufferMemoryBarrier barrier{};
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = probe.buffer;
barrier.size = probe.size;
scheduler.Current().Handle().pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0,
nullptr);
scheduler.Finish();
auto result = ReadBuffer(name, probe, image.info.bytes_per_block / 4);
DestroyBuffer(&probe);
return result;
}
void CheckRenderExecutorDccFixedClearFloat() {
constexpr const char *name = "RenderExecutorDccFixedClearFloat";
constexpr uintptr_t base = 0x0000000204100000ull;
@@ -7863,11 +7903,11 @@ public:
constexpr uint64_t dcc_address = base + 0x100000;
struct FillCase {
uint32_t fill;
std::array<uint32_t, 4> clear;
std::array<uint32_t, 2> texel;
};
constexpr std::array cases{
FillCase{0x40404040u, {0, 0, 0, 0x3f800000u}},
FillCase{0x80808080u, {0x3f800000u, 0x3f800000u, 0x3f800000u, 0}},
FillCase{0x40404040u, {0, 0x3c000000u}},
FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}},
};
EnsureRuntimeContext();
@@ -7913,9 +7953,9 @@ public:
auto &texture_cache = resources.GetTextureCache();
auto &executor = context.GetRenderExecutor();
resources.MapMemory(base, allocation_size);
texture_cache.TrackDccFill(dcc_address, 0x1000, fill_case.fill);
Require(name, "deferred fixed-code fill",
!texture_cache.TryConsumeDccFill(dcc_address, 0x1000,
fill_case.fill),
!texture_cache.IsMeta(dcc_address),
"a pre-registration DCC fill was not deferred");
RenderColorInfo color{};
@@ -7928,14 +7968,14 @@ public:
color.image_id &&
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
color.desc.info.metadata.range.address == dcc_address &&
color.metadata_fixed_clear_supported &&
rendering.num_color_attachments == 1 &&
rendering.color_attachments[0].is_clear &&
rendering.color_attachments[0].clear_value ==
fill_case.clear &&
!texture_cache.IsMetaCleared(dcc_address, 0),
"a DCC fixed clear code was not materialised on an RGBA16F "
"target");
Require(name, "GPU float clear value",
ReadCachedTexel(name, context, color.image_id) ==
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
"RGBA16F native image did not receive the fixed clear value");
RenderExecutorTestAccess::ResetBindings(executor);
resources.UnmapMemory(base, allocation_size);
@@ -7952,6 +7992,100 @@ public:
std::printf("[gpu] %-32s ok\n", name);
}
void CheckSampledDccClear() {
constexpr const char *name = "SampledDccClear";
constexpr uintptr_t base = 0x0000000205000000ull;
constexpr uint64_t color_size = 0x3fc0000;
constexpr uint64_t metadata_size = 0x48000;
constexpr uint64_t dcc_address = base + color_size;
constexpr uint64_t allocation_size = 0x4020000;
constexpr uint64_t alignment = 0x10000;
EnsureRuntimeContext();
int64_t direct_offset = -1;
Require(name, "direct allocation",
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
allocation_size, alignment, 0, &direct_offset) == 0,
"captured scene allocation failed");
void *mapped = reinterpret_cast<void *>(base);
Require(name, "direct mapping",
Libs::LibKernel::Memory::KernelMapDirectMemory(
&mapped, allocation_size, 0x3, 0x10, direct_offset, alignment) == 0 &&
mapped == reinterpret_cast<void *>(base),
"captured scene mapping failed");
for (const auto initial_size : {metadata_size - 0x1000, metadata_size}) {
std::memset(mapped, 0x5a, color_size);
std::memset(reinterpret_cast<void *>(dcc_address), 0x40, metadata_size);
RenderContext context(m_runtime_context);
auto &scheduler = context.GetCommandScheduler();
HW::Context registers{};
HW::UserConfig user_config{};
HW::Shader shaders{};
scheduler.Begin(registers, user_config, shaders);
auto &resources = context.GetGpuResources();
auto &cache = resources.GetTextureCache();
auto &executor = context.GetRenderExecutor();
resources.MapMemory(base, allocation_size);
cache.TrackDccFill(dcc_address, initial_size, 0x40404040u);
Require(name, "deferred metadata fill",
!cache.IsMeta(dcc_address),
"unknown metadata was consumed before descriptor discovery");
// PPSA19244's first sampled scene descriptor, with relocated allocations.
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
0xc4700000u, 0x021bc3bfu, 0x91b00facu, 0, 0x00700000u,
0x006b0000u | (static_cast<uint32_t>(dcc_address >> 8u) << 24u),
static_cast<uint32_t>(dcc_address >> 16u)}};
ShaderRecompiler::IR::DescriptorValue value{};
value.dword_count = 8;
std::copy_n(descriptor.fields, 8, value.dwords.begin());
ShaderRecompiler::IR::ImageResource resource{};
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
resource.numeric_class = Prospero::TextureNumericClass::Float;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
resource.read = true;
auto binding = RenderExecutorTestAccess::ResolveTexture(executor, resource, value);
Require(name, "sampled metadata discovery",
binding.desc.info.extent == vk::Extent3D{3840, 2160, 1} &&
binding.desc.info.data.size == color_size &&
binding.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
binding.desc.info.metadata.range.address == dcc_address &&
binding.desc.info.metadata.range.size == metadata_size,
"captured sampled descriptor lost its DCC allocation");
Require(name, "sampled view", cache.FindTexture(binding.image_id, binding.desc) != nullptr,
"captured scene did not produce a sampled view");
if (initial_size != metadata_size) {
Require(name, "partial metadata coverage",
ReadCachedTexel(name, context, binding.image_id) ==
std::vector<u32>{0x5a5a5a5au, 0x5a5a5a5au},
"an incomplete metadata fill cleared the whole scene");
cache.TrackDccFill(dcc_address, metadata_size, 0x40404040u);
(void)cache.FindTexture(binding.image_id, binding.desc);
}
Require(name, "opaque black before first sample",
ReadCachedTexel(name, context, binding.image_id, {3839, 2159, 0}) ==
std::vector<u32>{0, 0x3c000000u} &&
!cache.IsMetaCleared(dcc_address, 0),
"sampling read stale color bytes instead of the DCC opaque black clear");
cache.TrackDccFill(dcc_address, metadata_size, 0x80808080u);
(void)cache.FindTexture(binding.image_id, binding.desc);
Require(name, "later sampled clear",
ReadCachedTexel(name, context, binding.image_id) ==
std::vector<u32>{0x3c003c00u, 0x00003c00u},
"an existing sampled image retained the previous DCC clear");
resources.UnmapMemory(base, allocation_size);
scheduler.Finish();
}
Require(name, "unmap",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
"captured scene mapping release failed");
Require(name, "release",
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
direct_offset, allocation_size) == 0,
"captured scene allocation release failed");
std::printf("[gpu] %-32s ok\n", name);
}
void CheckComparisonDepthTexture() {
constexpr const char *name = "ComparisonDepthTexture";
constexpr uintptr_t base = 0x0000000204200000ull;
@@ -10379,7 +10513,7 @@ public:
static_cast<size_t>(image->height) *
image->layers * image->dwords_per_pixel;
auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32),
vk::BufferUsageFlagBits::eTransferDst, {});
vk::BufferUsageFlagBits::eTransferDst, {});
vk::CommandBuffer cmd = BeginCommands(shader_name, "readback");
AddImageBarrier(
@@ -27472,6 +27606,7 @@ int main(int argc, char **argv) {
vulkan.CheckRenderExecutorColor1DDiscovery();
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorDccFixedClearFloat();
vulkan.CheckSampledDccClear();
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
vulkan.CheckUnifiedTextureCacheFlow();
@@ -27487,6 +27622,9 @@ int main(int argc, char **argv) {
CheckDynamicRenderingState();
VulkanHarness vulkan;
vulkan.CheckComputeMetaClearClassification();
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorDccFixedClearFloat();
vulkan.CheckSampledDccClear();
vulkan.CheckRenderExecutorStencilBindingDiscovery();
return 0;
}
@@ -27664,6 +27802,7 @@ int main(int argc, char **argv) {
vulkan.CheckRenderExecutorColor1DDiscovery();
vulkan.CheckRenderExecutorColorVolumeDiscovery();
vulkan.CheckRenderExecutorDccFixedClearFloat();
vulkan.CheckSampledDccClear();
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
vulkan.CheckRenderExecutorStencilBindingDiscovery();