mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e42513faf | ||
|
|
596ecdcca0 | ||
|
|
5d83ce82af |
No files matched your search
+100
-13
@@ -252,7 +252,7 @@ void TextureCache::DeleteImage(ImageId id) {
|
||||
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
||||
}
|
||||
m_download_images.erase(id);
|
||||
if (owner->info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
if (owner->info.HasMetadata()) {
|
||||
m_surface_metas.erase(owner->info.metadata.range.address);
|
||||
}
|
||||
UnregisterImage(id);
|
||||
@@ -1327,6 +1327,19 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
|
||||
image.MarkGpuModified();
|
||||
image.usage.render_target = true;
|
||||
RefreshImage(id, desc);
|
||||
// DCC uses a separate metadata allocation. Register it when the color target is bound,
|
||||
// matching the point where CMask/FMask will be registered. Preserve a PendingDcc entry
|
||||
// because the metadata fill may have run before this bind.
|
||||
if (desc.info.metadata.kind == ImageMetadataKind::Dcc) {
|
||||
image.info.metadata = desc.info.metadata;
|
||||
auto [metadata, inserted] = m_surface_metas.try_emplace(
|
||||
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
|
||||
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||
metadata->second.type = MetaDataInfo::Type::Dcc;
|
||||
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::Dcc) {
|
||||
EXIT("TextureCache: color target reuses non-DCC metadata\n");
|
||||
}
|
||||
}
|
||||
CommitGpuWrite(image);
|
||||
TrackImageDownloadLocked(id, image);
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
@@ -1349,8 +1362,20 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
RefreshImage(id, desc);
|
||||
if (desc.info.HasMetadata()) {
|
||||
image.info.metadata = desc.info.metadata;
|
||||
m_surface_metas.try_emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.clear_mask = image.info.htile_clear_mask});
|
||||
auto [metadata, inserted] =
|
||||
m_surface_metas.try_emplace(desc.info.metadata.range.address,
|
||||
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
|
||||
.clear_mask = image.info.htile_clear_mask});
|
||||
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||
// PendingDcc fills use DCC-specific encoding. Do not reinterpret a speculative
|
||||
// DCC/buffer fill as HTile state if the address is later classified as depth metadata.
|
||||
metadata->second.type = MetaDataInfo::Type::HTile;
|
||||
metadata->second.clear_mask = image.info.htile_clear_mask;
|
||||
metadata->second.fill_value = 0xffffffffu;
|
||||
metadata->second.fill_size = 0;
|
||||
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
|
||||
EXIT("TextureCache: depth target reuses non-HTile metadata\n");
|
||||
}
|
||||
}
|
||||
CommitGpuWrite(image);
|
||||
if (desc.info.HasStencil()) {
|
||||
@@ -1826,33 +1851,90 @@ void TextureCache::ClearGpuModified(ImageId id) {
|
||||
}
|
||||
|
||||
bool TextureCache::IsMeta(uint64_t address) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
return m_surface_metas.contains(address);
|
||||
}
|
||||
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end()) {
|
||||
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
|
||||
}
|
||||
|
||||
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (fill_value != nullptr) {
|
||||
*fill_value = found->second.fill_value;
|
||||
}
|
||||
return (found->second.clear_mask & (1u << slice)) != 0;
|
||||
}
|
||||
|
||||
bool TextureCache::ClearMeta(uint64_t address) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end()) {
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
|
||||
// validated fill value, so an arbitrary compute write must not clear it.
|
||||
return false;
|
||||
}
|
||||
found->second.clear_mask = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid DCC fill range\n");
|
||||
}
|
||||
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
|
||||
// and mark only recognized deferred-clear encodings as logically clear.
|
||||
const auto dcc_clear_mask = [fill_value] {
|
||||
const auto code = static_cast<uint8_t>(fill_value);
|
||||
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
|
||||
return 0u;
|
||||
}
|
||||
switch (code) {
|
||||
case 0x00:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
case 0x80:
|
||||
case 0xc0: return UINT32_MAX;
|
||||
default: return 0u;
|
||||
}
|
||||
}();
|
||||
CacheLock lock(*this, m_lock);
|
||||
auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end()) {
|
||||
// This dispatch may precede color-target discovery. Retain it in the shared
|
||||
// metadata map, but PendingDcc remains invisible to IsMeta until registration. Returning
|
||||
// false lets the guest dispatch execute and initialize memory while the type is uncertain.
|
||||
m_surface_metas.emplace(address, MetaDataInfo {.type = MetaDataInfo::Type::PendingDcc,
|
||||
.clear_mask = dcc_clear_mask,
|
||||
.fill_value = fill_value,
|
||||
.fill_size = size});
|
||||
return false;
|
||||
}
|
||||
if (found->second.type == MetaDataInfo::Type::PendingDcc) {
|
||||
found->second.clear_mask = dcc_clear_mask;
|
||||
found->second.fill_value = fill_value;
|
||||
found->second.fill_size = size;
|
||||
return false;
|
||||
}
|
||||
if (found->second.type == MetaDataInfo::Type::Dcc) {
|
||||
found->second.clear_mask = dcc_clear_mask;
|
||||
found->second.fill_value = fill_value;
|
||||
found->second.fill_size = size;
|
||||
return true;
|
||||
}
|
||||
// The strict DCC path must not reinterpret a CMask/FMask/HTile allocation as DCC.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto found = m_surface_metas.find(address);
|
||||
if (found == m_surface_metas.end()) {
|
||||
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
|
||||
slice >= 32) {
|
||||
return false;
|
||||
}
|
||||
if (is_clear) {
|
||||
@@ -1867,8 +1949,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
}
|
||||
CacheLock lock(*this, m_lock);
|
||||
auto images = FindImagesInRegion(address, size, false);
|
||||
CacheLock lock(*this, m_lock);
|
||||
const auto end = address + size;
|
||||
for (auto metadata = m_surface_metas.lower_bound(address);
|
||||
metadata != m_surface_metas.end() && metadata->first < end;) {
|
||||
metadata = m_surface_metas.erase(metadata);
|
||||
}
|
||||
auto images = FindImagesInRegion(address, size, false);
|
||||
if (!images.empty()) {
|
||||
m_scheduler.Finish();
|
||||
}
|
||||
|
||||
+15
-1
@@ -69,8 +69,12 @@ public:
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
|
||||
uint32_t* fill_value = nullptr);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
|
||||
// False may still record PendingDcc state, but the guest dispatch must execute.
|
||||
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
@@ -88,7 +92,17 @@ 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,
|
||||
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||
// Keep all surface metadata in this one shadPS4-style entry so CMask/FMask can be
|
||||
// registered beside HTile and DCC without introducing parallel tracking paths.
|
||||
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
|
||||
|
||||
Type type = Type::PendingDcc;
|
||||
uint32_t clear_mask = 0;
|
||||
uint32_t fill_value = 0xffffffffu;
|
||||
uint64_t fill_size = 0;
|
||||
};
|
||||
|
||||
struct OverlapResult {
|
||||
|
||||
@@ -21,6 +21,28 @@ 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::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
info.metadata_fixed_clear_supported = has_dcc;
|
||||
break;
|
||||
default: info.metadata_fixed_clear_supported = false; break;
|
||||
}
|
||||
if (!info.metadata_clear_supported) {
|
||||
info.color_clear_value = {};
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
RenderColorInfo& r,
|
||||
@@ -55,20 +77,22 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
}
|
||||
|
||||
// No color output
|
||||
r.type = RenderColorType::NoColorOutput;
|
||||
r.desc = {};
|
||||
r.base_addr = 0;
|
||||
r.image_id = {};
|
||||
r.image_view = nullptr;
|
||||
r.format = vk::Format::eUndefined;
|
||||
r.extent = {};
|
||||
r.base_mip_level = 0;
|
||||
r.base_array_layer = 0;
|
||||
r.buffer_size = 0;
|
||||
r.samples = 1;
|
||||
r.export_mapping = {};
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
r.type = RenderColorType::NoColorOutput;
|
||||
r.desc = {};
|
||||
r.base_addr = 0;
|
||||
r.image_id = {};
|
||||
r.image_view = nullptr;
|
||||
r.format = vk::Format::eUndefined;
|
||||
r.extent = {};
|
||||
r.base_mip_level = 0;
|
||||
r.base_array_layer = 0;
|
||||
r.buffer_size = 0;
|
||||
r.samples = 1;
|
||||
r.export_mapping = {};
|
||||
r.color_clear_enable = false;
|
||||
r.metadata_clear_supported = false;
|
||||
r.metadata_fixed_clear_supported = false;
|
||||
r.color_clear_value = {};
|
||||
return;
|
||||
}
|
||||
const auto samples = render_sample_count(rt.attrib.num_fragments);
|
||||
@@ -107,9 +131,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
}
|
||||
}
|
||||
|
||||
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
|
||||
// ROP3 Copy is the normal color write path, not a render-target clear.
|
||||
// SRGB clear words are still encoded as normalized component values.
|
||||
// Color-control state selects the color-buffer operation and logical blend operation.
|
||||
// The normal copy operation is a regular color write, not an attachment clear.
|
||||
// Nonlinear clear values are still stored as normalized components.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
r.color_clear_enable = false;
|
||||
@@ -309,6 +333,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
desc.info.bytes_per_block = bytes_per_element;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||
if (has_dcc) {
|
||||
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
|
||||
// TextureCache can associate metadata fills observed before target registration.
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range.address = rt.dcc_addr.addr;
|
||||
}
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
const auto& mip = volume_layout.mips[level];
|
||||
@@ -355,7 +386,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
|
||||
r.samples = samples;
|
||||
r.export_mapping = target_format.export_mapping;
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
|
||||
BindRenderTarget(r.image_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ struct RenderColorInfo {
|
||||
uint32_t target_slot = 0;
|
||||
uint32_t samples = 1;
|
||||
Prospero::ColorComponentMapping export_mapping;
|
||||
bool color_clear_enable = false;
|
||||
bool color_clear_enable = false;
|
||||
bool metadata_clear_supported = false;
|
||||
bool metadata_fixed_clear_supported = false;
|
||||
vk::ClearColorValue color_clear_value {};
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabili
|
||||
guest_wave_size <= capabilities.max_subgroup_size) {
|
||||
return {ShaderSubgroupMode::Controlled, guest_wave_size};
|
||||
}
|
||||
if (!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(program)) {
|
||||
if (!ShaderRecompiler::Spirv::GetProgramRequirements(program).requires_exact_subgroup) {
|
||||
return {ShaderSubgroupMode::FlattenedMasks, 0};
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -139,6 +139,21 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
|
||||
}
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
|
||||
// before the render target is bound. TryConsumeDccFill either consumes registered state
|
||||
// or retains a PendingDcc fill while allowing the dispatch to run.
|
||||
const bool registered_metadata =
|
||||
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
|
||||
static std::atomic<uint32_t> logged_metadata_clears {0};
|
||||
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
|
||||
descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
if (registered_metadata) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static std::atomic<uint32_t> logged_clears {0};
|
||||
|
||||
@@ -377,8 +377,8 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
|
||||
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
|
||||
}
|
||||
|
||||
// CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component
|
||||
// write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black.
|
||||
// Color-control operation selects special color-buffer paths, not the normal component write
|
||||
// mask. Attachment availability therefore follows the target write mask.
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
ret.color_write_enable[i] =
|
||||
(i < color_count &&
|
||||
@@ -552,6 +552,71 @@ 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);
|
||||
@@ -592,14 +657,20 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
|
||||
view.layer_count},
|
||||
buffer.Handle());
|
||||
state.width = std::min(state.width, target.extent.width);
|
||||
state.height = std::min(state.height, target.extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
attachment.image_view = target.image_view;
|
||||
attachment.image_layout = layout;
|
||||
attachment.clear_value = target.color_clear_value.uint32;
|
||||
attachment.is_clear = target.color_clear_enable;
|
||||
state.width = std::min(state.width, target.extent.width);
|
||||
state.height = std::min(state.height, target.extent.height);
|
||||
state.num_layers = std::min(state.num_layers, view.layer_count);
|
||||
auto& attachment = state.color_attachments[i];
|
||||
attachment.image_view = target.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 = target.color_clear_enable || metadata_clear;
|
||||
}
|
||||
if (depth.image_id) {
|
||||
const auto owner = cache.ResolveOwner(depth.image_id);
|
||||
@@ -687,10 +758,11 @@ enum class CbColorMode : uint8_t {
|
||||
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto mode = ctx.GetColorControl().mode;
|
||||
// These AGC CB modes run color-buffer metadata/decompression operations. The shader is a
|
||||
// dummy vehicle for the CB, and its exported color must not be applied as a normal draw.
|
||||
// Kyty currently stores host images as expanded Vulkan images and does not track CMASK/DCC
|
||||
// metadata state, so the matching host operation is a no-op.
|
||||
// These special modes run color-buffer metadata or decompression operations. The shader is a
|
||||
// vehicle for that operation, and its exported color must not be applied as a normal draw.
|
||||
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
|
||||
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
|
||||
// future CMask/FMask support can consume its state through the same TextureCache path.
|
||||
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
|
||||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
|
||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||
@@ -948,7 +1020,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
|
||||
}
|
||||
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
|
||||
|
||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||
state.ps_active = DrawHasActivePixelShader(buffer);
|
||||
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
|
||||
static_cast<bool>(state.depth_info.image_id));
|
||||
if (state.color_count == 0 && !with_depth && !state.ps_active) {
|
||||
|
||||
@@ -942,6 +942,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
if (!IR::AllocateBindings(ir, layout_options, error)) {
|
||||
return false;
|
||||
}
|
||||
ir.spirv_requirements = Spirv::GetProgramRequirements(ir);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
|
||||
@@ -277,38 +277,108 @@ void CollectValueRegisterMirrors(const IR::ValueProgram& program,
|
||||
}
|
||||
}
|
||||
|
||||
bool ValueProgramHasOpcode(const IR::ValueProgram& program, IR::ValueOpcode opcode) {
|
||||
return std::ranges::any_of(program.blocks, [opcode](const IR::Block* block) {
|
||||
return std::ranges::any_of(
|
||||
*block, [opcode](const IR::Inst& inst) { return inst.GetOpcode() == opcode; });
|
||||
});
|
||||
}
|
||||
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
IR::SpirvRequirements GetProgramRequirements(const IR::Program& program) {
|
||||
if (program.spirv_requirements.has_value()) {
|
||||
return *program.spirv_requirements;
|
||||
}
|
||||
IR::SpirvRequirements requirements;
|
||||
if (program.values == nullptr) {
|
||||
return requirements;
|
||||
}
|
||||
const auto MarkExactSubgroup = [&] {
|
||||
requirements.requires_exact_subgroup = true;
|
||||
requirements.subgroup_ballot = true;
|
||||
};
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::Ballot:
|
||||
case IR::ValueOpcode::Ballot: MarkExactSubgroup(); break;
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane: {
|
||||
MarkExactSubgroup();
|
||||
requirements.subgroup_shuffle = true;
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::DppMoveU32) {
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane:
|
||||
case IR::ValueOpcode::WriteLane:
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
MarkExactSubgroup();
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::Permlane16U32:
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume:
|
||||
case IR::ValueOpcode::GdsDataAppend:
|
||||
case IR::ValueOpcode::GdsDataConsume: return true;
|
||||
case IR::ValueOpcode::GdsDataConsume: {
|
||||
MarkExactSubgroup();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::SwizzleU32: {
|
||||
MarkExactSubgroup();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
requirements.function_lds = program.stage != ShaderType::Compute;
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume: {
|
||||
MarkExactSubgroup();
|
||||
requirements.subgroup_shuffle = true;
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case IR::ValueOpcode::LoadSharedU8:
|
||||
case IR::ValueOpcode::LoadSharedU16:
|
||||
case IR::ValueOpcode::LoadSharedU32:
|
||||
case IR::ValueOpcode::WriteSharedU8:
|
||||
case IR::ValueOpcode::WriteSharedU16:
|
||||
case IR::ValueOpcode::WriteSharedU32:
|
||||
case IR::ValueOpcode::SharedAtomicFMin32:
|
||||
case IR::ValueOpcode::SharedAtomicFMax32:
|
||||
case IR::ValueOpcode::SharedAtomicSwap32:
|
||||
case IR::ValueOpcode::SharedAtomicIAdd32:
|
||||
case IR::ValueOpcode::SharedAtomicISub32:
|
||||
case IR::ValueOpcode::SharedAtomicSMin32:
|
||||
case IR::ValueOpcode::SharedAtomicUMin32:
|
||||
case IR::ValueOpcode::SharedAtomicSMax32:
|
||||
case IR::ValueOpcode::SharedAtomicUMax32:
|
||||
case IR::ValueOpcode::SharedAtomicAnd32:
|
||||
case IR::ValueOpcode::SharedAtomicOr32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32: {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (program.stage != ShaderType::Compute &&
|
||||
index < program.values->memory_info.size() &&
|
||||
program.values->memory_info[index].kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::LaneId:
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
break;
|
||||
case IR::ValueOpcode::ImageQueryLod: requirements.compute_derivatives = true; break;
|
||||
case IR::ValueOpcode::ImageGatherRaw:
|
||||
requirements.image_gather_extended = true;
|
||||
break;
|
||||
case IR::ValueOpcode::SetAttribute: {
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (program.stage == ShaderType::Pixel &&
|
||||
index < program.values->export_info.size() &&
|
||||
program.values->export_info[index].vm) {
|
||||
requirements.pixel_valid_mask = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return requirements;
|
||||
}
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
@@ -344,11 +414,12 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
|
||||
return false;
|
||||
}
|
||||
EmitterState state(program, resources, input_info);
|
||||
const auto requirements = GetProgramRequirements(program);
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
state.exact_subgroup_operations =
|
||||
!state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program);
|
||||
!state.per_invocation_masks && requirements.requires_exact_subgroup;
|
||||
state.registers.reserve(InitialEmitterVectorReserve);
|
||||
state.inputs.reserve(InitialEmitterVectorReserve);
|
||||
state.outputs.reserve(InitialEmitterVectorReserve);
|
||||
@@ -356,15 +427,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
|
||||
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
|
||||
CollectValueRegisterMirrors(value_program, state.registers);
|
||||
CopyProgramInputsAndOutputs(state, program);
|
||||
state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program) ||
|
||||
(!state.per_invocation_masks &&
|
||||
ValueProgramHasOpcode(value_program, IR::ValueOpcode::Ballot));
|
||||
state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program);
|
||||
state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program);
|
||||
state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program);
|
||||
state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program);
|
||||
state.needs_function_lds = ProgramNeedsFunctionLds(program);
|
||||
state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program);
|
||||
state.needs_subgroup_ballot = requirements.subgroup_ballot;
|
||||
state.needs_subgroup_shuffle = requirements.subgroup_shuffle;
|
||||
state.needs_subgroup_local_invocation_id = requirements.subgroup_local_invocation_id;
|
||||
state.needs_compute_derivatives = requirements.compute_derivatives;
|
||||
state.needs_image_gather_extended = requirements.image_gather_extended;
|
||||
state.needs_function_lds = requirements.function_lds;
|
||||
state.needs_pixel_valid_mask = requirements.pixel_valid_mask;
|
||||
AllocateInputVariables(state);
|
||||
AllocateOutputVariables(state);
|
||||
AllocateDescriptorVariables(state);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program);
|
||||
IR::SpirvRequirements GetProgramRequirements(const IR::Program& program);
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info, std::vector<uint32_t>& spirv, std::string* error);
|
||||
|
||||
@@ -88,161 +88,6 @@ uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ProgramNeedsComputeDerivatives(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::ImageQueryLod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsImageGatherExtended(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::ImageGatherRaw) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsFunctionLds(const IR::Program& program) {
|
||||
if (program.stage == ShaderType::Compute || program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::SwizzleU32: return true;
|
||||
case IR::ValueOpcode::LoadSharedU8:
|
||||
case IR::ValueOpcode::LoadSharedU16:
|
||||
case IR::ValueOpcode::LoadSharedU32:
|
||||
case IR::ValueOpcode::WriteSharedU8:
|
||||
case IR::ValueOpcode::WriteSharedU16:
|
||||
case IR::ValueOpcode::WriteSharedU32:
|
||||
case IR::ValueOpcode::SharedAtomicFMin32:
|
||||
case IR::ValueOpcode::SharedAtomicFMax32:
|
||||
case IR::ValueOpcode::SharedAtomicSwap32:
|
||||
case IR::ValueOpcode::SharedAtomicIAdd32:
|
||||
case IR::ValueOpcode::SharedAtomicISub32:
|
||||
case IR::ValueOpcode::SharedAtomicSMin32:
|
||||
case IR::ValueOpcode::SharedAtomicUMin32:
|
||||
case IR::ValueOpcode::SharedAtomicSMax32:
|
||||
case IR::ValueOpcode::SharedAtomicUMax32:
|
||||
case IR::ValueOpcode::SharedAtomicAnd32:
|
||||
case IR::ValueOpcode::SharedAtomicOr32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32:
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume: {
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index < program.values->memory_info.size() &&
|
||||
program.values->memory_info[index].kind == IR::ResourceKind::Lds) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsPixelValidMask(const IR::Program& program) {
|
||||
if (program.stage != ShaderType::Pixel || program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() != IR::ValueOpcode::SetAttribute) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<IR::ExportFlags>().index;
|
||||
if (index < program.values->export_info.size() &&
|
||||
program.values->export_info[index].vm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupBallot(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation) {
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::WqmMask) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ProgramRequiresExactSubgroupSize(program);
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane:
|
||||
case IR::ValueOpcode::Permlane16U32:
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume:
|
||||
case IR::ValueOpcode::GdsDataAppend:
|
||||
case IR::ValueOpcode::GdsDataConsume:
|
||||
case IR::ValueOpcode::SwizzleU32: return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
case IR::ValueOpcode::LaneId:
|
||||
case IR::ValueOpcode::WriteLane:
|
||||
case IR::ValueOpcode::Permlane16U32:
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume:
|
||||
case IR::ValueOpcode::GdsDataAppend:
|
||||
case IR::ValueOpcode::GdsDataConsume:
|
||||
case IR::ValueOpcode::SwizzleU32: return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t PointerForRegister(const EmitterState& state, IR::Register reg) {
|
||||
for (const auto& binding: state.registers) {
|
||||
if (binding.reg == reg) {
|
||||
|
||||
@@ -631,20 +631,6 @@ void CopyProgramInputsAndOutputs(EmitterState& state, const IR::Program& program
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||
|
||||
bool ProgramNeedsComputeDerivatives(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsImageGatherExtended(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsFunctionLds(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsPixelValidMask(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsSubgroupBallot(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsSubgroupShuffle(const IR::Program& program);
|
||||
|
||||
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program);
|
||||
|
||||
uint32_t PointerForRegister(const EmitterState& state, IR::Register reg);
|
||||
|
||||
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
@@ -1079,11 +1078,96 @@ bool IsLoopControlGateway(const Graph& graph, const NaturalLoop& loop, uint32_t
|
||||
is_control_target(block->terminator.false_block);
|
||||
}
|
||||
|
||||
bool HasLinearPathTo(const Graph& graph, uint32_t start, uint32_t target) {
|
||||
std::vector<bool> visited(graph.blocks.size(), false);
|
||||
for (auto block_id = start; block_id != target;) {
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr || block_id >= visited.size() || visited[block_id] ||
|
||||
block->successors.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
visited[block_id] = true;
|
||||
block_id = block->successors.front();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasLinearPathToTerminal(const Graph& graph, uint32_t start) {
|
||||
std::vector<bool> visited(graph.blocks.size(), false);
|
||||
for (auto block_id = start;;) {
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr || block_id >= visited.size() || visited[block_id]) {
|
||||
return false;
|
||||
}
|
||||
if (block->successors.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (block->successors.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
visited[block_id] = true;
|
||||
block_id = block->successors.front();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEnclosingLinearExit(const Graph& graph, uint32_t header, uint32_t block_id) {
|
||||
// AGC commonly lowers nested early returns through a terminal epilogue shared with an
|
||||
// enclosing conditional. Such a path is an exit boundary, not part of the inner selection.
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr || graph.Dominates(header, block_id) ||
|
||||
!HasLinearPathToTerminal(graph, block_id) || block->predecessors.empty()) {
|
||||
return false;
|
||||
}
|
||||
return std::ranges::all_of(block->predecessors, [&](uint32_t predecessor) {
|
||||
return graph.Dominates(header, predecessor) || graph.Dominates(predecessor, header);
|
||||
});
|
||||
}
|
||||
|
||||
bool CanReachBefore(const Graph& graph, uint32_t start, uint32_t target, uint32_t stop) {
|
||||
std::vector<uint32_t> pending = {start};
|
||||
std::vector<bool> visited(graph.blocks.size(), false);
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (block_id == target) {
|
||||
return true;
|
||||
}
|
||||
if (block_id == stop || block_id >= visited.size() || visited[block_id]) {
|
||||
continue;
|
||||
}
|
||||
visited[block_id] = true;
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block != nullptr) {
|
||||
pending.insert(pending.end(), block->successors.begin(), block->successors.end());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t FindSelectionMerge(const Graph& graph, const BasicBlock& block) {
|
||||
const auto global_merge = graph.FindNearestCommonPostDominator(block.terminator.true_block,
|
||||
block.terminator.false_block);
|
||||
const auto* loop = FindInnermostContainingLoop(graph, block.id);
|
||||
if (loop == nullptr) {
|
||||
const auto* global_block = graph.FindBlock(global_merge);
|
||||
const auto true_target = block.terminator.true_block;
|
||||
const auto false_target = block.terminator.false_block;
|
||||
if (global_block != nullptr && global_block->successors.empty()) {
|
||||
// A terminal common post-dominator can hide the local continuation of a nested
|
||||
// early-exit selection. Prefer that continuation as the structured merge.
|
||||
const bool true_exits = HasLinearPathTo(graph, true_target, global_merge);
|
||||
const bool false_exits = HasLinearPathTo(graph, false_target, global_merge);
|
||||
if (true_exits != false_exits) {
|
||||
return true_exits ? false_target : true_target;
|
||||
}
|
||||
const bool false_reaches_true =
|
||||
CanReachBefore(graph, false_target, true_target, global_merge);
|
||||
const bool true_reaches_false =
|
||||
CanReachBefore(graph, true_target, false_target, global_merge);
|
||||
if (false_reaches_true != true_reaches_false) {
|
||||
return false_reaches_true ? true_target : false_target;
|
||||
}
|
||||
}
|
||||
return global_merge;
|
||||
}
|
||||
|
||||
@@ -1269,17 +1353,24 @@ std::vector<uint32_t> SelectionRegion(const Graph& graph, const BasicBlock& head
|
||||
std::vector<uint32_t> region;
|
||||
std::vector<uint32_t> pending = {header.terminator.true_block, header.terminator.false_block};
|
||||
const auto* loop = FindInnermostContainingLoop(graph, header.id);
|
||||
const auto global_merge = graph.FindNearestCommonPostDominator(header.terminator.true_block,
|
||||
header.terminator.false_block);
|
||||
while (!pending.empty()) {
|
||||
const auto block_id = pending.back();
|
||||
pending.pop_back();
|
||||
if (block_id == merge || Contains(region, block_id) ||
|
||||
(loop != nullptr && (block_id == loop->merge || block_id == loop->continue_block))) {
|
||||
(loop != nullptr && (block_id == loop->merge || block_id == loop->continue_block)) ||
|
||||
IsEnclosingLinearExit(graph, header.id, block_id) ||
|
||||
(block_id == global_merge && HasLinearPathToTerminal(graph, block_id))) {
|
||||
continue;
|
||||
}
|
||||
const auto* block = graph.FindBlock(block_id);
|
||||
if (block == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (block->successors.empty()) {
|
||||
continue;
|
||||
}
|
||||
AddUnique(region, block_id);
|
||||
pending.insert(pending.end(), block->successors.begin(), block->successors.end());
|
||||
}
|
||||
@@ -1449,8 +1540,8 @@ uint32_t Graph::FindNearestCommonPostDominator(uint32_t block_a, uint32_t block_
|
||||
namespace {
|
||||
|
||||
struct GotoRouteBlocks {
|
||||
uint32_t before = UINT32_MAX;
|
||||
std::array<uint32_t, 5> blocks {};
|
||||
uint32_t before = UINT32_MAX;
|
||||
std::vector<uint32_t> blocks;
|
||||
};
|
||||
|
||||
void PlaceGotoRouteBlocks(Graph& graph, uint32_t original_block_count,
|
||||
@@ -1514,6 +1605,48 @@ uint32_t AppendGotoSetBlock(Graph& graph, uint32_t route_variable, bool value, u
|
||||
return block_id;
|
||||
}
|
||||
|
||||
void AppendEnclosingRouteBlocks(Graph& graph, uint32_t original_block_count,
|
||||
uint32_t route_variable, uint32_t shared, uint32_t other,
|
||||
uint32_t route_select, uint32_t route_root,
|
||||
std::vector<uint32_t>& route_blocks) {
|
||||
for (uint32_t depth = 0; depth < original_block_count; depth++) {
|
||||
uint32_t predecessor = UINT32_MAX;
|
||||
uint32_t leaf = UINT32_MAX;
|
||||
for (uint32_t candidate = 0; candidate < original_block_count; candidate++) {
|
||||
const auto* candidate_block = graph.FindBlock(candidate);
|
||||
if (candidate_block == nullptr ||
|
||||
candidate_block->terminator.kind != TerminatorKind::ConditionalBranch) {
|
||||
continue;
|
||||
}
|
||||
const auto candidate_true = candidate_block->terminator.true_block;
|
||||
const auto candidate_false = candidate_block->terminator.false_block;
|
||||
if (candidate_true == route_root &&
|
||||
(candidate_false == shared || candidate_false == other)) {
|
||||
predecessor = candidate;
|
||||
leaf = candidate_false;
|
||||
break;
|
||||
}
|
||||
if (candidate_false == route_root &&
|
||||
(candidate_true == shared || candidate_true == other)) {
|
||||
predecessor = candidate;
|
||||
leaf = candidate_true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (predecessor == UINT32_MAX) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto routed_leaf =
|
||||
AppendGotoSetBlock(graph, route_variable, leaf == other, route_select);
|
||||
auto* predecessor_block = graph.FindBlock(predecessor);
|
||||
ReplaceValue(predecessor_block->successors, leaf, routed_leaf);
|
||||
ReplaceTerminatorTarget(predecessor_block->terminator, leaf, routed_leaf);
|
||||
route_blocks.push_back(routed_leaf);
|
||||
route_root = predecessor;
|
||||
}
|
||||
}
|
||||
|
||||
// Turn H0 -> shared/H1, H1 -> shared/other into nested selections whose empty
|
||||
// forwarding blocks set one typed SSA route value. Guest semantic blocks remain unique.
|
||||
bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t outer_id,
|
||||
@@ -1538,66 +1671,120 @@ bool RouteOneSharedArm(Graph& graph, uint32_t original_block_count, uint32_t out
|
||||
|
||||
const bool true_is_shared = inner->terminator.true_block == shared;
|
||||
const bool false_is_shared = inner->terminator.false_block == shared;
|
||||
if (true_is_shared == false_is_shared) {
|
||||
continue;
|
||||
}
|
||||
const auto other =
|
||||
true_is_shared ? inner->terminator.false_block : inner->terminator.true_block;
|
||||
if (other == outer_id || other == inner_id || other == shared) {
|
||||
continue;
|
||||
}
|
||||
const auto first_arm = std::min(shared, other);
|
||||
if (first_arm >= original_block_count || outer_id >= inner_id || inner_id >= first_arm) {
|
||||
continue;
|
||||
}
|
||||
if (graph.FindNearestCommonPostDominator(shared, other) == UINT32_MAX) {
|
||||
continue;
|
||||
if (true_is_shared != false_is_shared) {
|
||||
const auto other =
|
||||
true_is_shared ? inner->terminator.false_block : inner->terminator.true_block;
|
||||
if (other == outer_id || other == inner_id || other == shared) {
|
||||
continue;
|
||||
}
|
||||
const auto first_arm = std::min(shared, other);
|
||||
if (first_arm >= original_block_count || outer_id >= inner_id ||
|
||||
inner_id >= first_arm ||
|
||||
graph.FindNearestCommonPostDominator(shared, other) == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto route_select = AppendGotoSelectBlock(graph, route_variable, other, shared);
|
||||
const auto inner_merge = AppendSyntheticBranchBlock(graph, route_select);
|
||||
const auto outer_shared =
|
||||
AppendGotoSetBlock(graph, route_variable, false, route_select);
|
||||
const auto inner_shared = AppendGotoSetBlock(graph, route_variable, false, inner_merge);
|
||||
const auto inner_other = AppendGotoSetBlock(graph, route_variable, true, inner_merge);
|
||||
|
||||
outer = graph.FindBlock(outer_id);
|
||||
auto* mutable_inner = graph.FindBlock(inner_id);
|
||||
outer->terminator.true_block = inner_is_true ? inner_id : outer_shared;
|
||||
outer->terminator.false_block = inner_is_true ? outer_shared : inner_id;
|
||||
outer->successors = {outer->terminator.true_block, outer->terminator.false_block};
|
||||
mutable_inner->terminator.true_block = true_is_shared ? inner_shared : inner_other;
|
||||
mutable_inner->terminator.false_block = true_is_shared ? inner_other : inner_shared;
|
||||
mutable_inner->successors = {mutable_inner->terminator.true_block,
|
||||
mutable_inner->terminator.false_block};
|
||||
|
||||
std::vector<uint32_t> route_blocks = {inner_shared, inner_other, inner_merge,
|
||||
outer_shared};
|
||||
AppendEnclosingRouteBlocks(graph, original_block_count, route_variable, shared, other,
|
||||
route_select, outer_id, route_blocks);
|
||||
route_blocks.push_back(route_select);
|
||||
route = {first_arm, std::move(route_blocks)};
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto route_select = AppendGotoSelectBlock(graph, route_variable, other, shared);
|
||||
const auto inner_merge = AppendSyntheticBranchBlock(graph, route_select);
|
||||
const auto outer_shared = AppendGotoSetBlock(graph, route_variable, false, route_select);
|
||||
const auto inner_shared = AppendGotoSetBlock(graph, route_variable, false, inner_merge);
|
||||
const auto inner_other = AppendGotoSetBlock(graph, route_variable, true, inner_merge);
|
||||
// H0 -> continuation/H1, H1 -> exit/body, body -> continuation. Route both
|
||||
// destinations after joining H0 and H1 so neither selection has a nonlocal edge.
|
||||
for (const bool exit_is_true: {true, false}) {
|
||||
const auto other =
|
||||
exit_is_true ? inner->terminator.true_block : inner->terminator.false_block;
|
||||
const auto body =
|
||||
exit_is_true ? inner->terminator.false_block : inner->terminator.true_block;
|
||||
if (other == outer_id || other == inner_id || other == shared || body == outer_id ||
|
||||
body == inner_id || body == shared || !graph.Dominates(inner_id, body) ||
|
||||
!graph.PostDominates(shared, body)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outer = graph.FindBlock(outer_id);
|
||||
auto* mutable_inner = graph.FindBlock(inner_id);
|
||||
outer->terminator.true_block = inner_is_true ? inner_id : outer_shared;
|
||||
outer->terminator.false_block = inner_is_true ? outer_shared : inner_id;
|
||||
outer->successors = {outer->terminator.true_block, outer->terminator.false_block};
|
||||
mutable_inner->terminator.true_block = true_is_shared ? inner_shared : inner_other;
|
||||
mutable_inner->terminator.false_block = true_is_shared ? inner_other : inner_shared;
|
||||
mutable_inner->successors = {mutable_inner->terminator.true_block,
|
||||
mutable_inner->terminator.false_block};
|
||||
route = {first_arm, {inner_shared, inner_other, inner_merge, outer_shared, route_select}};
|
||||
return true;
|
||||
std::vector<uint32_t> inner_shared_predecessors;
|
||||
const auto* shared_block = graph.FindBlock(shared);
|
||||
for (const auto predecessor: shared_block->predecessors) {
|
||||
if (predecessor != inner_id && graph.Dominates(inner_id, predecessor)) {
|
||||
inner_shared_predecessors.push_back(predecessor);
|
||||
}
|
||||
}
|
||||
const auto first_arm = std::min(shared, other);
|
||||
if (inner_shared_predecessors.empty() || first_arm >= original_block_count ||
|
||||
outer_id >= inner_id || inner_id >= first_arm ||
|
||||
graph.FindNearestCommonPostDominator(shared, other) == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto route_select = AppendGotoSelectBlock(graph, route_variable, other, shared);
|
||||
const auto inner_merge = AppendSyntheticBranchBlock(graph, route_select);
|
||||
const auto outer_shared =
|
||||
AppendGotoSetBlock(graph, route_variable, false, route_select);
|
||||
const auto inner_shared = AppendGotoSetBlock(graph, route_variable, false, inner_merge);
|
||||
const auto inner_other = AppendGotoSetBlock(graph, route_variable, true, inner_merge);
|
||||
|
||||
outer = graph.FindBlock(outer_id);
|
||||
auto* mutable_inner = graph.FindBlock(inner_id);
|
||||
outer->terminator.true_block = inner_is_true ? inner_id : outer_shared;
|
||||
outer->terminator.false_block = inner_is_true ? outer_shared : inner_id;
|
||||
outer->successors = {outer->terminator.true_block, outer->terminator.false_block};
|
||||
if (exit_is_true) {
|
||||
mutable_inner->terminator.true_block = inner_other;
|
||||
} else {
|
||||
mutable_inner->terminator.false_block = inner_other;
|
||||
}
|
||||
ReplaceValue(mutable_inner->successors, other, inner_other);
|
||||
for (const auto predecessor: inner_shared_predecessors) {
|
||||
auto* predecessor_block = graph.FindBlock(predecessor);
|
||||
ReplaceValue(predecessor_block->successors, shared, inner_shared);
|
||||
ReplaceTerminatorTarget(predecessor_block->terminator, shared, inner_shared);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> route_blocks = {inner_shared, inner_other, inner_merge,
|
||||
outer_shared};
|
||||
AppendEnclosingRouteBlocks(graph, original_block_count, route_variable, shared, other,
|
||||
route_select, outer_id, route_blocks);
|
||||
route_blocks.push_back(route_select);
|
||||
route = {first_arm, std::move(route_blocks)};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RouteSharedSelectionArms(Graph& graph) {
|
||||
bool RouteSharedSelectionArm(Graph& graph, uint32_t route_variable) {
|
||||
if (graph.irreducible) {
|
||||
return false;
|
||||
}
|
||||
const auto route_budget = static_cast<uint32_t>(graph.blocks.size());
|
||||
bool found = false;
|
||||
for (uint32_t route_variable = 0; route_variable < route_budget; route_variable++) {
|
||||
const auto block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
bool routed = false;
|
||||
for (uint32_t block_id = 0; block_id < block_count; block_id++) {
|
||||
GotoRouteBlocks route;
|
||||
if (!RouteOneSharedArm(graph, block_count, block_id, route_variable, route)) {
|
||||
continue;
|
||||
}
|
||||
const auto block_count = static_cast<uint32_t>(graph.blocks.size());
|
||||
for (uint32_t block_id = 0; block_id < block_count; block_id++) {
|
||||
GotoRouteBlocks route;
|
||||
if (RouteOneSharedArm(graph, block_count, block_id, route_variable, route)) {
|
||||
PlaceGotoRouteBlocks(graph, block_count, route);
|
||||
RebuildPredecessors(graph);
|
||||
RecomputeAnalyses(graph);
|
||||
found = true;
|
||||
routed = true;
|
||||
break;
|
||||
}
|
||||
if (!routed) {
|
||||
return found;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1922,17 +2109,25 @@ bool Structurize(Graph& graph, std::string* error) {
|
||||
if (StructurizeImpl(graph, error)) {
|
||||
return true;
|
||||
}
|
||||
if (!RouteSharedSelectionArms(original)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Graph failed_graph = std::move(graph);
|
||||
std::string failed_error = error != nullptr ? *error : std::string {};
|
||||
graph = std::move(original);
|
||||
if (error != nullptr) {
|
||||
error->clear();
|
||||
}
|
||||
if (StructurizeImpl(graph, error)) {
|
||||
return true;
|
||||
const auto route_budget = static_cast<uint32_t>(graph.blocks.size());
|
||||
// Apply one route at a time and retry. Eagerly routing every matching diamond can
|
||||
// rewrite unrelated selections that were already structurally valid.
|
||||
for (uint32_t route_variable = 0; route_variable < route_budget; route_variable++) {
|
||||
if (!RouteSharedSelectionArm(graph, route_variable)) {
|
||||
break;
|
||||
}
|
||||
Graph routed = graph;
|
||||
if (error != nullptr) {
|
||||
error->clear();
|
||||
}
|
||||
if (StructurizeImpl(routed, error)) {
|
||||
graph = std::move(routed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
graph = std::move(failed_graph);
|
||||
if (error != nullptr) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
@@ -351,6 +352,17 @@ struct ShaderInfo {
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct SpirvRequirements {
|
||||
bool requires_exact_subgroup = false;
|
||||
bool subgroup_ballot = false;
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
bool pixel_valid_mask = false;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
@@ -369,6 +381,8 @@ struct Program {
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
|
||||
std::optional<SpirvRequirements> spirv_requirements;
|
||||
};
|
||||
|
||||
bool LowerProgram(const Decoder::Program& decoded, const CFG::Graph& cfg, ShaderType stage,
|
||||
|
||||
@@ -4542,10 +4542,15 @@ public:
|
||||
};
|
||||
auto metadata_depth_a = MakeMetadataDepth(metadata_data_a, metadata_a);
|
||||
auto metadata_depth_b = MakeMetadataDepth(metadata_data_b, metadata_b);
|
||||
metadata_depth_a.info.htile_clear_mask = 0;
|
||||
const auto metadata_depth_a_id =
|
||||
texture_cache.FindImage(metadata_depth_a);
|
||||
const auto metadata_depth_b_id =
|
||||
texture_cache.FindImage(metadata_depth_b);
|
||||
Require(name, "pending DCC/HTile separation",
|
||||
!texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) &&
|
||||
!texture_cache.IsMeta(base + metadata_a),
|
||||
"an unclassified PS5 DCC fill was exposed as registered metadata");
|
||||
const auto metadata_depth_a_view =
|
||||
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
|
||||
const auto metadata_depth_b_view =
|
||||
@@ -4553,6 +4558,7 @@ public:
|
||||
Require(name, "shared-page metadata state",
|
||||
metadata_depth_a_view != nullptr &&
|
||||
metadata_depth_b_view != nullptr &&
|
||||
!texture_cache.IsMetaCleared(base + metadata_a, 0) &&
|
||||
texture_cache.ClearMeta(base + metadata_a) &&
|
||||
texture_cache.ClearMeta(base + metadata_b),
|
||||
"shared-page metadata did not retain logical clear state");
|
||||
@@ -4576,6 +4582,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");
|
||||
Require(name, "registered DCC/HTile separation",
|
||||
texture_cache.TouchMeta(base + metadata_a, 0, false) &&
|
||||
!texture_cache.TryConsumeDccFill(base + metadata_a, 0x80, 0) &&
|
||||
!texture_cache.IsMetaCleared(base + metadata_a, 0),
|
||||
"a PS5 DCC fill changed registered HTile state");
|
||||
Require(
|
||||
name, "metadata first-touch state",
|
||||
texture_cache.TouchMeta(base + metadata_b, 0, true) &&
|
||||
@@ -6313,7 +6324,8 @@ public:
|
||||
HW::Shader shaders{};
|
||||
registers.SetColorBase(0, {.addr = base});
|
||||
registers.SetColorInfo(
|
||||
0, {.format = Prospero::ChannelLayout::k10_10_10_2,
|
||||
0, {.dcc_compression_enable = true,
|
||||
.format = Prospero::ChannelLayout::k10_10_10_2,
|
||||
.channel_type = Prospero::ChannelType::kUNorm,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorAttrib2(0, {.height = 31, .width = 31});
|
||||
@@ -6323,6 +6335,10 @@ public:
|
||||
.dimension = 2,
|
||||
.cmask_pipe_aligned = true,
|
||||
.dcc_pipe_aligned = true});
|
||||
constexpr uint64_t dcc_address = base + allocation_size - 0x1000;
|
||||
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
||||
registers.SetColorClearWord0(0, {.word0 = 0});
|
||||
registers.SetColorClearWord1(0, {.word1 = 0});
|
||||
registers.SetRenderTargetMask(0x0f);
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
|
||||
@@ -6330,6 +6346,10 @@ public:
|
||||
auto &texture_cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size);
|
||||
Require(name, "deferred DCC clear",
|
||||
!texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) &&
|
||||
!texture_cache.IsMeta(dcc_address),
|
||||
"a pre-registration DCC clear was not deferred");
|
||||
|
||||
RenderColorInfo color{};
|
||||
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
||||
@@ -6337,6 +6357,7 @@ public:
|
||||
const auto attachment =
|
||||
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
||||
const auto &image = texture_cache.GetImage(color.image_id);
|
||||
uint32_t dcc_clear_value = 0xffffffffu;
|
||||
Require(
|
||||
name, "captured 3D target",
|
||||
color.image_id && attachment != nullptr &&
|
||||
@@ -6346,6 +6367,13 @@ public:
|
||||
color.desc.info.pitch == 128 &&
|
||||
color.desc.info.data.size == allocation_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 &&
|
||||
color.desc.view_info.layer_count == 1 &&
|
||||
image.backing.image_type == vk::ImageType::e3D &&
|
||||
@@ -6372,8 +6400,21 @@ public:
|
||||
sliced_color.image_view != nullptr &&
|
||||
sliced_color.desc.view_info.base_layer == 7 &&
|
||||
sliced_rendering.num_color_attachments == 1 &&
|
||||
sliced_rendering.num_layers == 1,
|
||||
sliced_rendering.num_layers == 1 &&
|
||||
sliced_rendering.color_attachments[0].is_clear &&
|
||||
sliced_rendering.color_attachments[0].clear_value ==
|
||||
vk::ClearColorValue {}.uint32 &&
|
||||
!texture_cache.IsMetaCleared(dcc_address, 7),
|
||||
"a nonzero 3D attachment slice was treated as a Vulkan array layer");
|
||||
dcc_clear_value = 0;
|
||||
Require(name, "registered DCC code state",
|
||||
texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0xffffffffu) &&
|
||||
!texture_cache.IsMetaCleared(dcc_address, 7,
|
||||
&dcc_clear_value) &&
|
||||
dcc_clear_value == 0xffffffffu &&
|
||||
texture_cache.TryConsumeDccFill(dcc_address, 0x1000, 0) &&
|
||||
texture_cache.IsMetaCleared(dcc_address, 7),
|
||||
"DCC fill codes were not classified as clear or uncompressed");
|
||||
|
||||
auto storage_desc = color.desc;
|
||||
storage_desc.type = BindingType::Storage;
|
||||
|
||||
+148
-23
@@ -668,6 +668,69 @@ void TestNormalizedImageContracts() {
|
||||
"Vulkan image-view compatibility classes diverged from production");
|
||||
}
|
||||
|
||||
void TestSpirvRequirementsAnalysis() {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
|
||||
Program program;
|
||||
program.stage = ShaderType::Pixel;
|
||||
program.values = std::make_shared<ValueProgram>();
|
||||
program.values->block_storage.push_back(std::make_unique<Block>());
|
||||
auto *block = program.values->block_storage.back().get();
|
||||
program.values->blocks.push_back(block);
|
||||
program.values->memory_info.push_back({.kind = ResourceKind::Lds});
|
||||
program.values->export_info.push_back({.vm = true});
|
||||
|
||||
block->AppendNewInst(ValueOpcode::DppMoveU32, {Value(0u), Value(true)});
|
||||
block->AppendNewInst(ValueOpcode::ImageQueryLod,
|
||||
{Value(0u), Value(0u), Value(0u)});
|
||||
block->AppendNewInst(ValueOpcode::ImageGatherRaw,
|
||||
{Value(0u), Value(0u), Value(0u)});
|
||||
auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32,
|
||||
{Value(0u), Value(0u), Value(true)});
|
||||
shared.SetFlags(MemoryFlags{.index = 0});
|
||||
auto &export_value =
|
||||
block->AppendNewInst(ValueOpcode::SetAttribute, {Value(0u), Value(true)});
|
||||
export_value.SetFlags(ExportFlags{.index = 0});
|
||||
|
||||
const auto requirements =
|
||||
ShaderRecompiler::Spirv::GetProgramRequirements(program);
|
||||
Check(requirements.requires_exact_subgroup && requirements.subgroup_ballot &&
|
||||
requirements.subgroup_shuffle &&
|
||||
requirements.subgroup_local_invocation_id &&
|
||||
requirements.compute_derivatives &&
|
||||
requirements.image_gather_extended && requirements.function_lds &&
|
||||
requirements.pixel_valid_mask,
|
||||
"consolidated SPIR-V requirements missed an IR dependency");
|
||||
|
||||
program.stage = ShaderType::Compute;
|
||||
const auto compute_requirements =
|
||||
ShaderRecompiler::Spirv::GetProgramRequirements(program);
|
||||
Check(!compute_requirements.function_lds &&
|
||||
!compute_requirements.pixel_valid_mask,
|
||||
"stage-specific SPIR-V requirements leaked into compute");
|
||||
|
||||
program.stage = ShaderType::Pixel;
|
||||
shared.SetFlags(MemoryFlags{.index = 1});
|
||||
export_value.SetFlags(ExportFlags{.index = 1});
|
||||
const auto invalid_metadata =
|
||||
ShaderRecompiler::Spirv::GetProgramRequirements(program);
|
||||
Check(!invalid_metadata.function_lds && !invalid_metadata.pixel_valid_mask,
|
||||
"invalid SPIR-V requirement metadata was accepted");
|
||||
|
||||
Program empty;
|
||||
const auto empty_requirements =
|
||||
ShaderRecompiler::Spirv::GetProgramRequirements(empty);
|
||||
Check(!empty_requirements.requires_exact_subgroup &&
|
||||
!empty_requirements.subgroup_ballot &&
|
||||
!empty_requirements.subgroup_shuffle &&
|
||||
!empty_requirements.subgroup_local_invocation_id &&
|
||||
!empty_requirements.compute_derivatives &&
|
||||
!empty_requirements.image_gather_extended &&
|
||||
!empty_requirements.function_lds &&
|
||||
!empty_requirements.pixel_valid_mask,
|
||||
"empty IR unexpectedly requested SPIR-V features");
|
||||
}
|
||||
|
||||
void TestNativeSubgroupPolicy() {
|
||||
const auto SetTypedValues = [](ShaderRecompiler::IR::Program &program,
|
||||
ShaderRecompiler::IR::ValueOpcode opcode =
|
||||
@@ -686,6 +749,9 @@ void TestNativeSubgroupPolicy() {
|
||||
block->AppendNewInst(opcode, {Value(&resource), Value(0u), Value(true),
|
||||
Value(1u), Value(0u)});
|
||||
}
|
||||
program.spirv_requirements.reset();
|
||||
program.spirv_requirements =
|
||||
ShaderRecompiler::Spirv::GetProgramRequirements(program);
|
||||
};
|
||||
|
||||
GraphicContext context;
|
||||
@@ -717,7 +783,8 @@ void TestNativeSubgroupPolicy() {
|
||||
|
||||
ShaderRecompiler::IR::Program cross_lane = safe;
|
||||
SetTypedValues(cross_lane, ShaderRecompiler::IR::ValueOpcode::ReadLane);
|
||||
Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(cross_lane) &&
|
||||
Check(ShaderRecompiler::Spirv::GetProgramRequirements(cross_lane)
|
||||
.requires_exact_subgroup &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
|
||||
vk::ShaderStageFlagBits::eVertex,
|
||||
cross_lane)
|
||||
@@ -734,7 +801,8 @@ void TestNativeSubgroupPolicy() {
|
||||
ShaderRecompiler::IR::Program zero_exec = safe;
|
||||
zero_exec.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
SetTypedValues(zero_exec);
|
||||
Check(!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(zero_exec) &&
|
||||
Check(!ShaderRecompiler::Spirv::GetProgramRequirements(zero_exec)
|
||||
.requires_exact_subgroup &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
|
||||
vk::ShaderStageFlagBits::eCompute,
|
||||
zero_exec)
|
||||
@@ -744,18 +812,20 @@ void TestNativeSubgroupPolicy() {
|
||||
|
||||
ShaderRecompiler::IR::Program selective_exec = safe;
|
||||
SetTypedValues(selective_exec, ShaderRecompiler::IR::ValueOpcode::Ballot);
|
||||
Check(
|
||||
ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(selective_exec),
|
||||
"selective EXEC write was classified mask-free");
|
||||
Check(ShaderRecompiler::Spirv::GetProgramRequirements(selective_exec)
|
||||
.requires_exact_subgroup,
|
||||
"selective EXEC write was classified mask-free");
|
||||
|
||||
ShaderRecompiler::IR::Program carry = safe;
|
||||
SetTypedValues(carry, ShaderRecompiler::IR::ValueOpcode::Ballot);
|
||||
Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(carry),
|
||||
Check(ShaderRecompiler::Spirv::GetProgramRequirements(carry)
|
||||
.requires_exact_subgroup,
|
||||
"lane-varying VCC carry producer was classified mask-free");
|
||||
|
||||
ShaderRecompiler::IR::Program vcc_branch = safe;
|
||||
SetTypedValues(vcc_branch, ShaderRecompiler::IR::ValueOpcode::Ballot);
|
||||
Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(vcc_branch),
|
||||
Check(ShaderRecompiler::Spirv::GetProgramRequirements(vcc_branch)
|
||||
.requires_exact_subgroup,
|
||||
"whole-wave VCC branch was classified mask-free");
|
||||
|
||||
ShaderRecompiler::IR::Program ds_partial = safe;
|
||||
@@ -7147,30 +7217,83 @@ void TestNewShaderRecompilerCfgOverlappingEarlyExitLadder() {
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error);
|
||||
Check(!structured && Common::ContainsStr(error, "externally entered region"),
|
||||
"overlapping early-exit ladder should require dispatcher without "
|
||||
"cloning");
|
||||
Check(structured, error.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"failed structurization duplicated semantic instructions");
|
||||
for (size_t index = original_block_count; index < graph.blocks.size();
|
||||
index++) {
|
||||
Check(graph.blocks[index].inst_begin == graph.blocks[index].inst_end,
|
||||
"structurization added a semantic block clone");
|
||||
}
|
||||
"early-exit ladder routing changed semantic instruction coverage");
|
||||
Check(graph.blocks.size() > original_block_count,
|
||||
"early-exit ladder routing did not add forwarding blocks");
|
||||
const auto route_selects = std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
});
|
||||
const auto route_sets = std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
});
|
||||
Check(route_selects != 0u && route_sets >= 3u,
|
||||
"early-exit ladder lacks explicit typed route state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.dump_ir = true;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
|
||||
error.c_str());
|
||||
Check(result.program.dispatcher_fallback &&
|
||||
Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
|
||||
"overlapping early-exit ladder did not select dispatcher");
|
||||
Check(Common::ContainsStr(result.ir_dump, "externally entered region"),
|
||||
"overlapping early-exit ladder lost its fallback reason");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) != 0u,
|
||||
"overlapping early-exit dispatcher lacks OpSwitch");
|
||||
Check(!result.program.dispatcher_fallback &&
|
||||
Common::ContainsStr(result.ir_dump, "mode=structured"),
|
||||
"overlapping early-exit ladder did not stay structured");
|
||||
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"overlapping early-exit ladder unexpectedly used dispatcher OpSwitch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x06, 0, 0), // enclosing condition
|
||||
EncodeSopp(0x04, 4), // enclosing -> continuation or inner header
|
||||
EncodeSopc(0x06, 1, 1), // inner early-exit condition
|
||||
EncodeSopp(0x04, 4), // inner -> shared terminal or body
|
||||
EncodeSMovB32(2, 129), // inner body
|
||||
EncodeSopp(0x02, 0), // body -> enclosing continuation
|
||||
EncodeSMovB32(3, 129), // enclosing continuation
|
||||
EncodeSopp(0x02, 0), // continuation -> shared terminal
|
||||
EncodeSMovB32(4, 129), // shared terminal epilogue
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
std::string error;
|
||||
Check(ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded,
|
||||
&error),
|
||||
error.c_str());
|
||||
ShaderRecompiler::CFG::Graph graph;
|
||||
Check(ShaderRecompiler::CFG::BuildGraph(decoded, graph, &error),
|
||||
error.c_str());
|
||||
const auto original_coverage =
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
const bool structured = ShaderRecompiler::CFG::Structurize(graph, &error);
|
||||
Check(structured, error.c_str());
|
||||
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
|
||||
original_coverage,
|
||||
"nested early-exit structurization changed semantic coverage");
|
||||
const auto route_selects = std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.condition ==
|
||||
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
|
||||
});
|
||||
const auto route_sets = std::ranges::count_if(graph.blocks, [](const auto &block) {
|
||||
return block.terminator.goto_value >= 0;
|
||||
});
|
||||
Check(route_selects == 1u && route_sets == 3u,
|
||||
"nested early exit lacks complete typed route state");
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
|
||||
error.c_str());
|
||||
Check(!result.program.dispatcher_fallback &&
|
||||
Common::ContainsStr(result.ir_dump, "mode=structured") &&
|
||||
SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
|
||||
"nested early exit to a shared terminal did not stay structured");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
@@ -9334,6 +9457,7 @@ int main() {
|
||||
TestResourceDescriptorClassification();
|
||||
TestNativeShaderResourceDependencies();
|
||||
TestNormalizedImageContracts();
|
||||
TestSpirvRequirementsAnalysis();
|
||||
TestNativeSubgroupPolicy();
|
||||
TestNewShaderRecompilerSMovB32();
|
||||
TestNewShaderRecompilerCapturedVop1SdwaByteConvert();
|
||||
@@ -9374,6 +9498,7 @@ int main() {
|
||||
TestNewShaderRecompilerCfgExecSccSharedArm();
|
||||
TestNewShaderRecompilerCfgLoopSharedRegion();
|
||||
TestNewShaderRecompilerCfgOverlappingEarlyExitLadder();
|
||||
TestNewShaderRecompilerCfgNestedEarlyExitSharedTerminal();
|
||||
TestNewShaderRecompilerCfgExternallyEnteredSelectionDispatcher();
|
||||
TestNewShaderRecompilerCfgIrreducibleDispatcher();
|
||||
TestComputeShaderInputWaveSize();
|
||||
|
||||
Reference in new issue
Block a user