mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37df7f3278 | |||
| 33208a545a | |||
| 4059179e9d | |||
| fe3857a171 | |||
| c3e7ec8285 | |||
| 763d3be3a9 | |||
| d8c4a9af38 |
@@ -189,6 +189,10 @@ bool SyncRawImageBuffersEnabled() {
|
||||
return g_config->sync_raw_image_buffers;
|
||||
}
|
||||
|
||||
bool SupportZmaskEnabled() {
|
||||
return g_config->support_zmask;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ struct ConfigOptions {
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool support_zmask = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
uint32_t trophy_duration_seconds = 6;
|
||||
@@ -151,6 +152,7 @@ bool GpuAssistedValidationEnabled();
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool SupportZmaskEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool TrophyEnabled();
|
||||
uint32_t GetTrophyDurationSeconds();
|
||||
|
||||
@@ -89,42 +89,6 @@ static void MountSandboxDirs() {
|
||||
MountOrCreateDir("_TempData/" + title_id, "/temp");
|
||||
}
|
||||
|
||||
static bool ClearDirectoryContents(const std::filesystem::path& dir) {
|
||||
bool ok = true;
|
||||
|
||||
for (const auto& entry: Common::File::GetDirEntries(dir)) {
|
||||
if (entry.name == "." || entry.name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto path = dir / entry.name;
|
||||
|
||||
if (entry.is_file) {
|
||||
Common::File::RemoveReadonly(path);
|
||||
ok = Common::File::DeleteFile(path) && ok;
|
||||
} else {
|
||||
ok = ClearDirectoryContents(path) && ok;
|
||||
ok = Common::File::DeleteDirectory(path) && ok;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void ClearDebugTextureFolder() {
|
||||
const std::string debug_texture_folder = "_Textures";
|
||||
|
||||
if (!Common::File::IsDirectoryExisting(debug_texture_folder)) {
|
||||
Common::File::CreateDirectories(debug_texture_folder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ClearDirectoryContents(debug_texture_folder)) {
|
||||
LOGF_COLOR(Log::Color::BrightYellow, "TextureDump: failed to completely clear %s\n",
|
||||
debug_texture_folder.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json,
|
||||
Common::Subsystems& subsystems) {
|
||||
EXIT_IF(!Common::Thread::IsMainThread());
|
||||
@@ -198,8 +162,6 @@ void Run(const RunOptions& options) {
|
||||
Common::Subsystems subsystems(true);
|
||||
Init(options.config, param_json, subsystems);
|
||||
|
||||
ClearDebugTextureFolder();
|
||||
|
||||
PrintSystemInfo();
|
||||
std::string title_id;
|
||||
Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id);
|
||||
|
||||
+11
-15
@@ -263,15 +263,6 @@ void TextureCache::DeleteImage(ImageId id) {
|
||||
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
||||
}
|
||||
m_download_images.erase(id);
|
||||
if (image->info.HasMetadata()) {
|
||||
const auto metadata = m_surface_metas.find(image->info.metadata.range.address);
|
||||
if (metadata != m_surface_metas.end() &&
|
||||
image->info.metadata.kind == ImageMetadataKind::Htile &&
|
||||
metadata->second.type == MetaDataInfo::Type::HTile) {
|
||||
// A later binding may have reused this address for another metadata type.
|
||||
m_surface_metas.erase(metadata);
|
||||
}
|
||||
}
|
||||
UnregisterImage(id);
|
||||
if (m_scheduler.Active()) {
|
||||
m_scheduler.DeferOperation([this, id] { m_slot_images.erase(id); });
|
||||
@@ -725,6 +716,13 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
"TextureCache: unsupported unequal-sample depth overlap copy (%u -> %u)\n",
|
||||
cached.backing.samples, replacement.backing.samples);
|
||||
}
|
||||
if (info.IsDepth() && info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
// Copied depth supersedes pending clears from the previous native image.
|
||||
const auto metadata = m_surface_metas.find(info.metadata.range.address);
|
||||
if (metadata != m_surface_metas.end()) {
|
||||
metadata->second.clear_mask.reset();
|
||||
}
|
||||
}
|
||||
FreeImage(cached_id);
|
||||
return replacement_id;
|
||||
}
|
||||
@@ -1042,7 +1040,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// Native color metadata must not retain a reused HTile/CMask/FMask clear flag.
|
||||
// Native color metadata must not retain a reused HTile clear flag.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1) {
|
||||
return;
|
||||
@@ -1420,11 +1418,8 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
image.info.metadata = desc.info.metadata;
|
||||
if (desc.info.HasMetadata()) {
|
||||
const auto [metadata, inserted] = m_surface_metas.try_emplace(desc.info.metadata.range.address);
|
||||
if (inserted) {
|
||||
metadata->second.type = MetaDataInfo::Type::HTile;
|
||||
if (image.info.htile_initial_clear) {
|
||||
metadata->second.clear_mask.set();
|
||||
}
|
||||
if (inserted && image.info.htile_initial_clear) {
|
||||
metadata->second.clear_mask.set();
|
||||
}
|
||||
}
|
||||
RefreshImage(id);
|
||||
@@ -1885,6 +1880,7 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
EXIT("TextureCache: invalid unmap range\n");
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
// HTILE belongs to guest memory, independently of native image replacements.
|
||||
for (auto metadata = m_surface_metas.begin(); metadata != m_surface_metas.end();) {
|
||||
const auto base = metadata->first;
|
||||
if (base >= address && base < address + size) {
|
||||
|
||||
@@ -81,9 +81,6 @@ public:
|
||||
|
||||
private:
|
||||
struct MetaDataInfo {
|
||||
enum class Type : uint8_t { CMask, FMask, HTile };
|
||||
|
||||
Type type;
|
||||
// PS5 DB_DEPTH_VIEW encodes 13-bit base and last array-slice indices.
|
||||
std::bitset<8192> clear_mask;
|
||||
};
|
||||
|
||||
@@ -220,8 +220,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
}
|
||||
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||
const bool has_cmask = !rt.info.dcc_compression_enable && rt.info.cmask_fast_clear_enable &&
|
||||
rt.cmask.addr != 0 && samples == 1 &&
|
||||
!rt.info.fmask_compression_enable &&
|
||||
rt.cmask.addr != 0 &&
|
||||
!rt.attrib3.write_vrs_rate_hint_to_cmask;
|
||||
if (has_dcc || has_cmask) {
|
||||
TileSizeAlign metadata_size {};
|
||||
@@ -233,7 +232,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
} else {
|
||||
(void)TileGetCmaskSize(width, height, layers, levels, metadata_size);
|
||||
}
|
||||
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
|
||||
// DCC owns the clear when both planes are enabled.
|
||||
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
|
||||
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
|
||||
desc.info.metadata.clear_word =
|
||||
|
||||
@@ -294,16 +294,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
EXIT_NOT_IMPLEMENTED(rt.dcc.overwrite_combiner_disable != false);
|
||||
// EXIT_NOT_IMPLEMENTED(rt.dcc.data_write_on_dcc_clear_to_reg != false);
|
||||
EXIT_NOT_IMPLEMENTED(rt.dcc.dcc_clear_key_enable != false);
|
||||
if (rt.cmask.addr != 0x0000000000000000 || rt.fmask.addr != 0x0000000000000000 ||
|
||||
rt.dcc_addr.addr != 0x0000000000000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring PS5 metadata addresses cmask=0x%016" PRIx64
|
||||
" fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
|
||||
rt.cmask.addr, rt.fmask.addr, rt.dcc_addr.addr);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,9 +126,13 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
|
||||
if (size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
|
||||
EXIT("storage buffer range is unsupported\n");
|
||||
}
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
|
||||
address, size, resource.written,
|
||||
const bool metadata_read = resource.read && Config::SupportZmaskEnabled() &&
|
||||
(address & 0x7fffu) == 0 &&
|
||||
context.GetTextureCache().IsMeta(address);
|
||||
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
|
||||
address, size, resource.written || metadata_read,
|
||||
resource.formatted || (resource.read && Config::SyncRawImageBuffersEnabled()), id);
|
||||
if (metadata_read) buffer->Fill(offset, size, 0xf); // HTILE ZMask::kUncompressed.
|
||||
const auto aligned_offset = Common::AlignDown(offset, alignment);
|
||||
const auto adjustment = offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
|
||||
@@ -580,8 +580,8 @@ void PipelineCache::InitializeDriverCache() {
|
||||
return;
|
||||
}
|
||||
if (!initial_data.empty()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: loaded {} bytes from {}", initial_data.size(),
|
||||
path);
|
||||
PipelineCacheLog("Vulkan pipeline cache: loaded {:.2f} MB from {}",
|
||||
static_cast<double>(initial_data.size()) / 1'000'000.0, path);
|
||||
} else {
|
||||
PipelineCacheLog("Vulkan pipeline cache: initialized empty");
|
||||
}
|
||||
@@ -621,8 +621,8 @@ void PipelineCache::Save() {
|
||||
}
|
||||
if (result != vk::Result::eSuccess || size == 0 ||
|
||||
size > std::numeric_limits<uint32_t>::max()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
|
||||
vk::to_string(result), size);
|
||||
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {:.2f} MB)",
|
||||
vk::to_string(result), static_cast<double>(size) / 1'000'000.0);
|
||||
return;
|
||||
}
|
||||
payload.resize(size);
|
||||
@@ -654,7 +654,8 @@ void PipelineCache::Save() {
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
return;
|
||||
}
|
||||
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
|
||||
PipelineCacheLog("Vulkan pipeline cache: saved {:.2f} MB to {}",
|
||||
static_cast<double>(payload.size()) / 1'000'000.0,
|
||||
Common::PathToString(m_driver_cache_path));
|
||||
m_driver_cache_dirty = false;
|
||||
}
|
||||
|
||||
@@ -203,9 +203,9 @@ private:
|
||||
void ResetBindings();
|
||||
void UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer);
|
||||
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode);
|
||||
[[nodiscard]] bool TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode);
|
||||
|
||||
RenderContext& m_context;
|
||||
GraphicsBindings m_graphics_bindings;
|
||||
|
||||
@@ -128,9 +128,9 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
@@ -186,14 +186,26 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
|
||||
size)) {
|
||||
return false;
|
||||
}
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
return false;
|
||||
const bool image_clear =
|
||||
cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear);
|
||||
if (!image_clear) {
|
||||
// CPU fills invalidate overlapping cached images. Preserve the native dispatch for
|
||||
// partial or ambiguous image backings, including pooled metadata aliases.
|
||||
if ((descriptor.Base48() & 3u) != 0 ||
|
||||
!m_context.IsMapped(descriptor.Base48(), size) ||
|
||||
cache.IsRegionRegistered(descriptor.Base48(), size)) {
|
||||
return false;
|
||||
}
|
||||
// Metadata need not have an image backing yet. Keep proven fills in guest memory
|
||||
// when CPU-owned, so color-clear discovery can read them without a GPU download.
|
||||
m_context.GetBufferCache().FillBuffer(descriptor.Base48(), size, packed_clear, false);
|
||||
}
|
||||
static std::atomic<uint32_t> logged_clears {0};
|
||||
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: compute image clear shader=0x%016" PRIx64
|
||||
LOGF("GraphicsRenderDispatchDirect: compute %s shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
|
||||
image_clear ? "image clear" : "buffer fill", input.stage.program->shader_hash,
|
||||
descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -308,8 +320,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (resources.specialization_reads.empty()) {
|
||||
// Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes.
|
||||
UpdateComputeMetaClears(input_info, buffer);
|
||||
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode)) {
|
||||
if (TryConsumeComputeFill(input_info, buffer, thread_group_x, thread_group_y,
|
||||
thread_group_z, mode)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,7 +495,6 @@ struct ShaderInfo {
|
||||
static constexpr uint32_t MaxBufferRoots = 64;
|
||||
static constexpr uint32_t MaxImages = 64;
|
||||
static constexpr uint32_t MaxSamplers = 32;
|
||||
static constexpr uint32_t MaxSampledPairs = 64;
|
||||
|
||||
std::vector<BufferResource> buffers;
|
||||
std::vector<ImageResource> images;
|
||||
|
||||
@@ -3823,9 +3823,6 @@ private:
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
|
||||
Fail(pc, "sampled image/sampler pair limit exceeded");
|
||||
}
|
||||
m_info.sampled_pairs.push_back({image, sampler, pc});
|
||||
}
|
||||
|
||||
|
||||
@@ -174,12 +174,6 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_sync_raw_image_buffers">
|
||||
<property name="toolTip"><string>Can fix missing textures and incorrect transparency in some games. May reduce performance.</string></property>
|
||||
<property name="text"><string>Accurate texture synchronization</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_hdr">
|
||||
<property name="toolTip"><string>Enable HDR10 output for supported games. Requires HDR enabled on the host display.</string></property>
|
||||
<property name="text"><string>Enable HDR display support</string></property>
|
||||
@@ -483,6 +477,31 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rendering_compatibility_group">
|
||||
<property name="title"><string>Rendering compatibility</string></property>
|
||||
<layout class="QVBoxLayout" name="renderingCompatibilityLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_rendering_compatibility">
|
||||
<property name="text"><string>These settings may help with rendering bugs at a slight performance cost.</string></property>
|
||||
<property name="wordWrap"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_sync_raw_image_buffers">
|
||||
<property name="toolTip"><string>Can fix missing textures and incorrect transparency (Resident Evil 2).</string></property>
|
||||
<property name="text"><string>Accurate texture synchronization</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_support_zmask">
|
||||
<property name="toolTip"><string>Can restore missing geometry (High on Life 2).</string></property>
|
||||
<property name="text"><string>Support ZMask</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="debugSpacer">
|
||||
<property name="orientation"><enum>Qt::Vertical</enum></property>
|
||||
|
||||
@@ -127,6 +127,7 @@ public:
|
||||
bool hide_cursor_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool support_zmask = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
int trophy_duration_seconds = 6;
|
||||
@@ -166,6 +167,7 @@ public:
|
||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||
support_zmask = other.support_zmask;
|
||||
tessellation_enabled = other.tessellation_enabled;
|
||||
trophy_enabled = other.trophy_enabled;
|
||||
trophy_duration_seconds = other.trophy_duration_seconds;
|
||||
|
||||
@@ -14,6 +14,7 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(hide_cursor_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||
KYTY_CFG_SET(support_zmask);
|
||||
KYTY_CFG_SET(tessellation_enabled);
|
||||
KYTY_CFG_SET(trophy_enabled);
|
||||
KYTY_CFG_SET(trophy_duration_seconds);
|
||||
@@ -63,6 +64,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(hide_cursor_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||
KYTY_CFG_GET(support_zmask);
|
||||
KYTY_CFG_GET(tessellation_enabled);
|
||||
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
|
||||
KYTY_CFG_GET(trophy_duration_seconds);
|
||||
|
||||
@@ -287,6 +287,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||
m_ui->checkBox_support_zmask->setChecked(info.support_zmask);
|
||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||
m_ui->checkBox_network_connected->setChecked(info.network_connected);
|
||||
@@ -480,6 +481,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||
info.support_zmask = ui.checkBox_support_zmask->isChecked();
|
||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||
info.network_connected = ui.checkBox_network_connected->isChecked();
|
||||
|
||||
@@ -262,6 +262,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
}
|
||||
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
|
||||
args << "--sync-raw-image-buffers" << BoolArg(info.sync_raw_image_buffers);
|
||||
args << "--support-zmask" << BoolArg(info.support_zmask);
|
||||
args << "--trophy-notifications" << BoolArg(info.trophy_enabled);
|
||||
args << "--trophy-duration" << QString::number(info.trophy_duration_seconds);
|
||||
args << "--perf-overlay" << BoolArg(info.perf_overlay_enabled);
|
||||
|
||||
@@ -91,6 +91,8 @@ static void PrintUsage() {
|
||||
" --readback-linear-images <true|false> Read back writable linear images on submit.\n");
|
||||
::printf(
|
||||
" --sync-raw-image-buffers <true|false> Synchronize raw reads of GPU images. Default: false.\n");
|
||||
::printf(
|
||||
" --support-zmask <true|false> Fill HTILE buffers with uncompressed ZMask. Default: false.\n");
|
||||
::printf(
|
||||
" --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n");
|
||||
::printf(
|
||||
@@ -477,6 +479,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--support-zmask") {
|
||||
if (!ParseBool(value, options.config.support_zmask)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--trophy-notifications") {
|
||||
if (!ParseBool(value, options.config.trophy_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
|
||||
@@ -4929,6 +4929,38 @@ void TestImagesSamplersAndAliases() {
|
||||
"buffer/image descriptor alias was not linked");
|
||||
}
|
||||
|
||||
void TestIndependentImageSamplerPairs() {
|
||||
Fixture fixture(ShaderType::Pixel);
|
||||
const auto address = fixture.ImageAddress();
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
for (uint32_t use = 0; use < 144; ++use) {
|
||||
const auto pair = use % 72;
|
||||
const auto pc = use * 4;
|
||||
const auto image = fixture.Image(
|
||||
{Value(pair / 8), Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u), Value(0u), Value(0u)}, pc);
|
||||
const auto sampler = fixture.Sampler(
|
||||
{Value(pair % 8), Value(0u), Value(0u), Value(0u)}, pc);
|
||||
fixture.Emit(ValueOpcode::ImageSampleRaw,
|
||||
{image, sampler, address, Value(true)},
|
||||
fixture.AddMemory(memory, pc));
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
|
||||
Check(fixture.program.info.images.size() == 9 &&
|
||||
fixture.program.info.samplers.size() == 8 &&
|
||||
fixture.program.info.sampled_pairs.size() == 72,
|
||||
"distinct image/sampler combinations were limited or duplicated");
|
||||
for (uint32_t index = 0; index < 72; ++index) {
|
||||
const auto &pair = fixture.program.info.sampled_pairs[index];
|
||||
Check(pair.image == index / 8 && pair.sampler == index % 8 &&
|
||||
pair.first_use_pc == index * 4,
|
||||
"sampled pair lost its independent resources or first use");
|
||||
}
|
||||
}
|
||||
|
||||
void TestSampleAdjustSamplerScratch() {
|
||||
Fixture fixture(ShaderType::Pixel);
|
||||
const auto active = fixture.Emit(
|
||||
@@ -8255,6 +8287,7 @@ int main() {
|
||||
Run("scalar/vector alias", TestScalarAndVectorBufferAlias);
|
||||
Run("runtime unsigned min", TestRuntimeUnsignedMinDescriptor);
|
||||
Run("images and samplers", TestImagesSamplersAndAliases);
|
||||
Run("independent image/sampler pairs", TestIndependentImageSamplerPairs);
|
||||
Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch);
|
||||
Run("FMASK load specialization", TestFmaskLoadSpecialization);
|
||||
Run("gather LOD sampler validation", TestGatherLodSamplerValidation);
|
||||
|
||||
@@ -390,7 +390,6 @@ struct TextureCacheTestAccess {
|
||||
static void RegisterHtileMeta(TextureCache &cache, uint64_t address) {
|
||||
std::lock_guard lock(cache.m_lock);
|
||||
auto &metadata = cache.m_surface_metas[address];
|
||||
metadata.type = TextureCache::MetaDataInfo::Type::HTile;
|
||||
metadata.clear_mask.reset();
|
||||
}
|
||||
|
||||
@@ -408,10 +407,10 @@ struct RenderExecutorTestAccess {
|
||||
executor.DrawAuto(0, command, args);
|
||||
}
|
||||
|
||||
static bool TryConsumeComputeImageClear(RenderExecutor &executor,
|
||||
static bool TryConsumeComputeFill(RenderExecutor &executor,
|
||||
const ShaderComputeInputInfo &input, CommandBuffer &command,
|
||||
uint32_t x, uint32_t y, uint32_t z, uint32_t mode) {
|
||||
return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode);
|
||||
return executor.TryConsumeComputeFill(input, command, x, y, z, mode);
|
||||
}
|
||||
|
||||
static void UpdateComputeMetaClears(RenderExecutor &executor,
|
||||
@@ -6572,6 +6571,11 @@ public:
|
||||
htile_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||
htile_depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
const auto htile_depth_image = texture_cache.FindImage(htile_depth);
|
||||
(void)texture_cache.FindDepthTarget(htile_depth_image, htile_depth);
|
||||
Require(name, "HTile depth metadata registration",
|
||||
texture_cache.ClearMeta(htile_depth.info.metadata.range.address) &&
|
||||
texture_cache.TouchMeta(htile_depth.info.metadata.range.address, 0, false),
|
||||
"depth target did not register independent slice clear state");
|
||||
vk::ClearValue htile_clear{};
|
||||
htile_clear.depthStencil.depth = 0.25f;
|
||||
TextureCacheTestAccess::ClearImage(
|
||||
@@ -6605,6 +6609,11 @@ public:
|
||||
Require(name, "HTile depth storage prefix view",
|
||||
texture_cache.FindTexture(id, htile_storage) != nullptr,
|
||||
"converted depth prefix has no storage view");
|
||||
Require(name, "HTile metadata survives native depth replacement",
|
||||
texture_cache.IsMeta(htile_depth.info.metadata.range.address) &&
|
||||
!texture_cache.IsMetaCleared(htile_depth.info.metadata.range.address, 0) &&
|
||||
texture_cache.IsMetaCleared(htile_depth.info.metadata.range.address, 1),
|
||||
"storage conversion discarded guest metadata or its slice clear state");
|
||||
htile_storage_image = id;
|
||||
}
|
||||
auto htile_readback = CreateHostBuffer(
|
||||
@@ -6625,6 +6634,11 @@ public:
|
||||
std::vector<u32>(htile_layers, std::bit_cast<u32>(0.25f)),
|
||||
"partial storage conversion lost native depth data outside its view");
|
||||
DestroyBuffer(&htile_readback);
|
||||
texture_cache.UnmapMemory(htile_depth.info.metadata.range.address,
|
||||
htile_depth.info.metadata.range.size);
|
||||
Require(name, "HTile metadata allocation unmap",
|
||||
!texture_cache.IsMeta(htile_depth.info.metadata.range.address),
|
||||
"unmapped guest metadata retained its registration");
|
||||
|
||||
auto layered_stencil_desc = MakeLinearDesc(
|
||||
base + 0x27d4000, 6 * 4 * sizeof(float),
|
||||
@@ -7898,9 +7912,16 @@ public:
|
||||
"retiring the old DCC image erased the live HTile slice state");
|
||||
texture_cache.UnmapMemory(reused_depth.info.data.address,
|
||||
reused_depth.info.data.size);
|
||||
Require(name, "HTile allocation outlives native depth retirement",
|
||||
texture_cache.IsMeta(base + 0x28000) &&
|
||||
!texture_cache.IsMetaCleared(base + 0x28000, 0) &&
|
||||
texture_cache.IsMetaCleared(base + 0x28000, 1),
|
||||
"retiring depth data erased an independent metadata allocation");
|
||||
texture_cache.UnmapMemory(reused_depth.info.metadata.range.address,
|
||||
reused_depth.info.metadata.range.size);
|
||||
Require(name, "reused metadata final retirement",
|
||||
!texture_cache.IsMeta(base + 0x28000),
|
||||
"retiring the HTile image left its metadata registered");
|
||||
"unmapping the HTile allocation left its metadata registered");
|
||||
|
||||
constexpr uint64_t partial_unmap_image_offset = 0x2700000;
|
||||
auto partial_unmap_image =
|
||||
@@ -10465,22 +10486,34 @@ public:
|
||||
vk::Extent3D extent = {1, 1, 1}, uint32_t layer = 0,
|
||||
uint32_t mip = 0) {
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
auto &image = context.GetTextureCache().GetImage(id);
|
||||
const auto bytes = image.info.bytes_per_block * extent.width *
|
||||
auto *image = &context.GetTextureCache().GetImage(id);
|
||||
std::unique_ptr<Libs::Graphics::Image> resolved;
|
||||
if (image->backing.samples > 1) {
|
||||
auto info = image->info;
|
||||
info.data = {};
|
||||
info.metadata = {};
|
||||
info.samples = 1;
|
||||
info.resources = {1, 1};
|
||||
resolved = std::make_unique<Libs::Graphics::Image>(m_runtime_context, scheduler, info);
|
||||
resolved->Resolve(*image, {mip, 1, layer, 1}, {});
|
||||
image = resolved.get();
|
||||
layer = mip = 0;
|
||||
}
|
||||
const auto bytes = image->info.bytes_per_block * extent.width *
|
||||
extent.height * extent.depth;
|
||||
auto probe = CreateHostBuffer(name, bytes,
|
||||
vk::BufferUsageFlagBits::eTransferDst, {});
|
||||
scheduler.Current().EndRendering();
|
||||
image.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
image->Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {},
|
||||
scheduler.Current().Handle());
|
||||
vk::BufferImageCopy copy{};
|
||||
copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
copy.imageSubresource = {image->info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor, mip, layer, 1};
|
||||
copy.imageOffset = offset;
|
||||
copy.imageExtent = extent;
|
||||
scheduler.Current().Handle().copyImageToBuffer(
|
||||
image.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
image->backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
probe.buffer, 1, ©);
|
||||
vk::BufferMemoryBarrier barrier{};
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
@@ -11487,7 +11520,7 @@ public:
|
||||
clear.float32[0] == 1.0f && clear.float32[1] == 1.0f,
|
||||
"a repeated DWORD fill was decoded as a two-register clear");
|
||||
constexpr uintptr_t base = 0x0000000204100000ull;
|
||||
constexpr uint64_t allocation_size = 0x400000;
|
||||
constexpr uint64_t allocation_size = 0x1000000;
|
||||
constexpr uint64_t allocation_alignment = 0x10000;
|
||||
struct FillCase {
|
||||
uint32_t fill;
|
||||
@@ -11499,6 +11532,8 @@ public:
|
||||
Prospero::ChannelType type = Prospero::ChannelType::kFloat;
|
||||
uint32_t clear_word = 0;
|
||||
bool eliminate_fast_clear = false;
|
||||
uint32_t samples_log2 = 0;
|
||||
bool goat = false;
|
||||
};
|
||||
constexpr std::array cases{
|
||||
FillCase{0x40404040u, {0, 0x3c000000u}},
|
||||
@@ -11506,7 +11541,7 @@ public:
|
||||
FillCase{0x40404040u, {0, 0x3c000000u}, true},
|
||||
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
|
||||
.layout = Prospero::ChannelLayout::k8_8_8_8,
|
||||
.type = Prospero::ChannelType::kUNorm},
|
||||
.type = Prospero::ChannelType::kUNorm, .goat = true},
|
||||
FillCase{.fill = 0, .texel = {0, 0}, .cmask = true,
|
||||
.eliminate_fast_clear = true},
|
||||
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
|
||||
@@ -11530,6 +11565,15 @@ public:
|
||||
FillCase{.fill = 0x20202020u, .texel = {0x0000ffffu},
|
||||
.layout = Prospero::ChannelLayout::k16_16,
|
||||
.type = Prospero::ChannelType::kUNorm, .clear_word = 0x0000ffffu},
|
||||
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
|
||||
.layout = Prospero::ChannelLayout::k8_8_8_8,
|
||||
.type = Prospero::ChannelType::kUNorm, .samples_log2 = 2},
|
||||
FillCase{.fill = 0, .texel = {0x804020ffu, 0}, .cmask = true,
|
||||
.layout = Prospero::ChannelLayout::k8_8_8_8,
|
||||
.type = Prospero::ChannelType::kUNorm, .eliminate_fast_clear = true, .samples_log2 = 2},
|
||||
FillCase{.fill = 0, .texel = {0, 0x3c000000u}, .cmask = true,
|
||||
.eliminate_fast_clear = true, .samples_log2 = 2},
|
||||
FillCase{.fill = 0x40404040u, .texel = {0, 0x3c000000u}, .eliminate_fast_clear = true},
|
||||
};
|
||||
EnsureRuntimeContext();
|
||||
// Astro's generic metadata fill, through S_ENDPGM; trailing debug data is omitted.
|
||||
@@ -11562,11 +11606,18 @@ public:
|
||||
}
|
||||
return words;
|
||||
}();
|
||||
// Goat Simulator 3's immediate-zero fill (b0a34a6ed90ebace).
|
||||
static constexpr std::array<uint32_t, 6> goat_fill{
|
||||
0xd7460000u, 0x04010c04u, 0x7e020280u,
|
||||
0xe0102000u, 0x80000100u, 0xbf810000u};
|
||||
for (const auto *shader : {&native_fill, &raw_fill}) {
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(shader->data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = sizeof(native_fill)});
|
||||
}
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(goat_fill.data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = sizeof(goat_fill)});
|
||||
int64_t direct_offset = -1;
|
||||
Require(name, "direct allocation",
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
@@ -11588,12 +11639,13 @@ public:
|
||||
const auto bytes_per_pixel = TextureGetRenderTargetFormat(
|
||||
layout, type, Prospero::ChannelOrder::kStandard).bytes_per_element;
|
||||
const uint32_t probe_width = bytes_per_pixel == 2 ? 2 : 1;
|
||||
const uint32_t selected_layer = fill_case.cmask || fill_case.eliminate_fast_clear ? 1 : 0;
|
||||
TileSizeAlign dcc_size{};
|
||||
(void)TileGetDccSize(512, 256, 1, bytes_per_pixel, 1,
|
||||
Prospero::TileMode::kRenderTarget, dcc_size, 0);
|
||||
const uint64_t dcc_address = base + (fill_case.eliminate_fast_clear ? 0x200000 :
|
||||
(void)TileGetDccSize(512, 256, selected_layer + 1, bytes_per_pixel, 1,
|
||||
Prospero::TileMode::kRenderTarget, dcc_size, fill_case.samples_log2);
|
||||
const uint64_t dcc_address = base + (fill_case.samples_log2 != 0 ? 0x800000 :
|
||||
fill_case.eliminate_fast_clear ? 0x200000 :
|
||||
fill_case.reuse_mips ? 0x160000 : 0x100000);
|
||||
const uint32_t selected_layer = fill_case.cmask ? 1 : 0;
|
||||
const uint64_t metadata_size = fill_case.cmask ? 0x2000 : dcc_size.size;
|
||||
const std::vector<u32> expected(
|
||||
fill_case.texel.begin(), fill_case.texel.begin() + std::max(bytes_per_pixel / 4u, 1u));
|
||||
@@ -11607,13 +11659,16 @@ public:
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
registers.SetColorBase(0, {.addr = base});
|
||||
registers.SetColorInfo(
|
||||
0, {.cmask_fast_clear_enable = !fill_case.reuse_unorm,
|
||||
0, {.fmask_compression_enable = fill_case.samples_log2 != 0,
|
||||
.cmask_fast_clear_enable = !fill_case.reuse_unorm,
|
||||
.dcc_compression_enable = !fill_case.cmask,
|
||||
.format = layout,
|
||||
.channel_type = type,
|
||||
.channel_order = Prospero::ChannelOrder::kStandard});
|
||||
registers.SetColorView(0, {.base_array_slice_index = selected_layer,
|
||||
.last_array_slice_index = selected_layer});
|
||||
registers.SetColorAttrib(0, {.num_samples = fill_case.samples_log2,
|
||||
.num_fragments = fill_case.samples_log2});
|
||||
registers.SetColorAttrib2(0, {.height = 255, .width = 511});
|
||||
registers.SetColorAttrib3(0,
|
||||
{.tile_mode = Prospero::TileMode::kRenderTarget,
|
||||
@@ -11621,6 +11676,7 @@ public:
|
||||
.metadata_pipe_aligned = true});
|
||||
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
||||
registers.SetColorCmask(0, {.addr = dcc_address});
|
||||
registers.SetColorFmask(0, {.addr = fill_case.samples_log2 != 0 ? base + 0xc00000 : 0});
|
||||
registers.SetColorClearWord0(0, {.word0 = fill_case.cmask ? expected[0] : fill_case.clear_word});
|
||||
registers.SetColorClearWord1(0, {.word1 = fill_case.cmask && expected.size() > 1u ? expected[1] : 0});
|
||||
registers.SetRenderTargetMask(0x0f);
|
||||
@@ -11674,27 +11730,39 @@ public:
|
||||
registers.SetColorAttrib2(0, single_mip);
|
||||
}
|
||||
const auto fill_metadata = [&](uint32_t count, bool raw = false) {
|
||||
const auto *shader = &native_fill;
|
||||
if (raw) {
|
||||
shader = &raw_fill;
|
||||
}
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(shader->data()),
|
||||
const bool goat = fill_case.goat && !raw;
|
||||
const auto *shader = goat ? goat_fill.data()
|
||||
: raw ? raw_fill.data() : native_fill.data();
|
||||
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(shader),
|
||||
.num_thread_x = 64, .num_thread_y = 1, .num_thread_z = 1,
|
||||
.wave_size = 64, .user_sgpr = 10, .tgid_x_en = true});
|
||||
.wave_size = 64, .user_sgpr = goat ? uint8_t{4} : uint8_t{10},
|
||||
.tgid_x_en = true});
|
||||
const std::array<uint32_t, 10> user_data{
|
||||
static_cast<uint32_t>(dcc_address),
|
||||
static_cast<uint32_t>(dcc_address >> 32u) | (4u << 16u),
|
||||
count, 0x00014004u, fill_case.fill, 0, 0x200, 0, count, 1};
|
||||
for (uint32_t i = 0; i < user_data.size(); i++) {
|
||||
for (uint32_t i = 0; i < (goat ? 4u : user_data.size()); i++) {
|
||||
shaders.SetCsUserSgpr(i, user_data[i], HW::UserSgprType::Unknown);
|
||||
}
|
||||
executor.DispatchDirect(0, scheduler.Current(), (count + 63) / 64, 1, 1, 0x41u, false);
|
||||
};
|
||||
const auto metadata_words = static_cast<uint32_t>(metadata_size / 4);
|
||||
if (fill_case.goat) {
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0xa5a5a5a5u);
|
||||
}
|
||||
fill_metadata(metadata_words);
|
||||
Require(name, "metadata before descriptor",
|
||||
!texture_cache.IsMeta(dcc_address),
|
||||
"the native fill guessed an unbound metadata allocation's type");
|
||||
if (fill_case.goat) {
|
||||
std::vector<uint32_t> filled_metadata(metadata_words);
|
||||
Require(name, "Goat metadata fill stays CPU-owned",
|
||||
!resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size) &&
|
||||
LibKernel::Memory::TryReadBacking(dcc_address, filled_metadata.data(), metadata_size) &&
|
||||
std::ranges::all_of(filled_metadata,
|
||||
[&](uint32_t word) { return word == fill_case.fill; }),
|
||||
"Goat's proven metadata fill required a GPU readback or lost its constant value");
|
||||
}
|
||||
|
||||
const auto check_expanded_metadata = [&] {
|
||||
context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false);
|
||||
@@ -11702,9 +11770,10 @@ public:
|
||||
Require(name, "expanded metadata readback",
|
||||
LibKernel::Memory::TryReadBacking(dcc_address, bytes.data(), bytes.size()),
|
||||
"native color metadata could not be read back");
|
||||
const auto selected = bytes.begin() + (fill_case.cmask ? 0x1000 : 0);
|
||||
const auto selected = bytes.begin() + metadata_size * selected_layer / (selected_layer + 1);
|
||||
Require(name, "selected metadata slice expansion",
|
||||
std::all_of(bytes.begin(), selected, [](uint8_t v) { return v == 0; }) &&
|
||||
std::all_of(bytes.begin(), selected,
|
||||
[&](uint8_t v) { return v == static_cast<uint8_t>(fill_case.fill); }) &&
|
||||
std::all_of(selected, bytes.end(), [](uint8_t v) { return v == 0xff; }),
|
||||
"materialization retained a clear key or changed an unselected slice");
|
||||
};
|
||||
@@ -11730,7 +11799,8 @@ public:
|
||||
color.image_id == preexisting_id &&
|
||||
texture_cache.FindTexture(color.image_id, sampled) != nullptr,
|
||||
"FCE failed to retain the sampled allocation");
|
||||
const std::vector<u32> stale = layout == Prospero::ChannelLayout::k11_11_10
|
||||
const std::vector<u32> stale = layout == Prospero::ChannelLayout::k8_8_8_8
|
||||
? std::vector<u32>{0xffff00ffu} : layout == Prospero::ChannelLayout::k11_11_10
|
||||
? std::vector<u32>{0x780003c0u} : layout == Prospero::ChannelLayout::k32_32
|
||||
? std::vector<u32>{0x3f800000u, 0u} : std::vector<u32>{0x00003c00u, 0x3c003c00u};
|
||||
Require(name, "FCE before graphics shader or target binding",
|
||||
@@ -11747,7 +11817,7 @@ public:
|
||||
Require(name, "repeated indexed FCE preserves expanded color",
|
||||
ReadCachedTexel(name, context, color.image_id, {}, {1, 1, 1}, 1) == stale,
|
||||
"FCE reapplied the consumed clear after rendering");
|
||||
WriteMetadata(context, dcc_address, metadata_size, 0);
|
||||
WriteMetadata(context, dcc_address, metadata_size, fill_case.fill);
|
||||
RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(),
|
||||
{.index_count = 3, .instance_count = 1});
|
||||
(void)texture_cache.FindTexture(color.image_id, sampled);
|
||||
@@ -11849,6 +11919,10 @@ public:
|
||||
retains_painted_texel(),
|
||||
"a partial same-value write reused stale clear bytes after rendering");
|
||||
fill_metadata(metadata_words, true);
|
||||
fill_metadata(metadata_words);
|
||||
Require(name, "already GPU-owned metadata stays ordered",
|
||||
resources.GetBufferCache().HasGpuDirtyBytes(dcc_address, metadata_size),
|
||||
"a fill discarded pending native writes to its metadata pages");
|
||||
bind();
|
||||
Require(name, "same-value raw native re-clear",
|
||||
read_texel() == expected,
|
||||
@@ -11861,6 +11935,11 @@ public:
|
||||
retains_painted_texel(),
|
||||
"a nonuniform metadata allocation cleared the whole target");
|
||||
fill_metadata(metadata_words);
|
||||
if (fill_case.goat) {
|
||||
Require(name, "repeated Goat fill stays CPU-owned",
|
||||
!resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size),
|
||||
"re-clearing expanded metadata required another GPU readback");
|
||||
}
|
||||
bind();
|
||||
Require(name, "complete fill after partial fill",
|
||||
read_texel() == expected,
|
||||
@@ -11926,7 +12005,7 @@ public:
|
||||
|
||||
ImageDesc previous_depth{};
|
||||
previous_depth.type = BindingType::DepthTarget;
|
||||
previous_depth.info.data = {base + 0x180000, 0x10000};
|
||||
previous_depth.info.data = {base + 0xa00000, 0x10000};
|
||||
previous_depth.info.pixel_format = vk::Format::eD32Sfloat;
|
||||
previous_depth.info.guest_format = Prospero::BufferFormat::k32Float;
|
||||
previous_depth.info.type = Prospero::ImageType::kColor2D;
|
||||
@@ -14920,13 +14999,49 @@ public:
|
||||
texture_cache.ClearMeta(high_slice_target.htile_data_base_addr),
|
||||
"single high slice was rejected or changed the backing layer layout");
|
||||
auto high_slice_readback = CreateHostBuffer(
|
||||
name, 4 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
|
||||
name, 7 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
|
||||
auto high_slice_metadata = CreateHostBuffer(
|
||||
name, 6 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {});
|
||||
const auto htile_slice_size = high_slice.desc.info.metadata.range.size / 87;
|
||||
const auto htile_slice_address =
|
||||
high_slice_target.htile_data_base_addr + 86 * htile_slice_size;
|
||||
Require(name, "ZMask support default", !Config::SupportZmaskEnabled(),
|
||||
"ZMask support should be disabled by default");
|
||||
const auto copy_htile = [&](uint32_t pass) {
|
||||
const auto program = make_buffer_program(
|
||||
ShaderType::Compute, {.read = true, .written = pass != 0});
|
||||
ShaderBufferResource descriptor{};
|
||||
descriptor.UpdateAddress48(high_slice_target.htile_data_base_addr);
|
||||
descriptor.fields[2] = high_slice.desc.info.metadata.range.size;
|
||||
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
||||
auto &value = snapshot.buffers.emplace_back();
|
||||
std::copy_n(descriptor.fields, 4, value.dwords.begin());
|
||||
value.dword_count = 4;
|
||||
ShaderStageRuntime runtime{&program, &snapshot};
|
||||
for (const bool support_zmask : {false, true}) {
|
||||
Config::Load({.support_zmask = support_zmask});
|
||||
PreparedBindings bindings;
|
||||
executor.PrepareBindings(runtime, bindings);
|
||||
std::array<PreparedBindings *, 1> stages{&bindings};
|
||||
executor.FindBuffers(stages);
|
||||
executor.RebindBuffers(bindings);
|
||||
const auto &source = bindings.buffers[0];
|
||||
const vk::BufferCopy copy{source.offset + 86 * htile_slice_size,
|
||||
(pass * 2 + support_zmask) * sizeof(uint32_t), sizeof(uint32_t)};
|
||||
scheduler.Current().Handle().copyBuffer(source.buffer,
|
||||
high_slice_metadata.buffer, 1, ©);
|
||||
}
|
||||
};
|
||||
for (uint32_t pass = 0; pass < 2; ++pass) {
|
||||
vk::ClearValue prior_depth{};
|
||||
prior_depth.depthStencil.depth = 0.625f;
|
||||
TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(),
|
||||
high_slice.image_id, {vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 87},
|
||||
prior_depth);
|
||||
if (pass == 1) {
|
||||
resources.GetBufferCache().FillBuffer(
|
||||
htile_slice_address, htile_slice_size, 0, false);
|
||||
}
|
||||
RenderDepthInfo acquired_high_slice{};
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
||||
executor, scheduler.Current(), acquired_high_slice);
|
||||
@@ -14946,15 +15061,67 @@ public:
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 85, 2}, {}, {1, 1, 1}};
|
||||
texture_cache.GetImage(high_slice.image_id).Download(
|
||||
std::span{©, 1}, high_slice_readback.buffer, 0, high_slice_readback.size);
|
||||
copy_htile(pass);
|
||||
}
|
||||
auto high_slice_storage = high_slice.desc;
|
||||
high_slice_storage.type = BindingType::Storage;
|
||||
high_slice_storage.info.pixel_format = vk::Format::eR32Sfloat;
|
||||
high_slice_storage.info.metadata = {};
|
||||
high_slice_storage.view_info.format = vk::Format::eR32Sfloat;
|
||||
high_slice_storage.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
high_slice_storage.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
||||
const auto high_slice_storage_id = texture_cache.FindImage(high_slice_storage);
|
||||
Require(name, "HTile depth replacement before raw buffer read",
|
||||
high_slice_storage_id != high_slice.image_id,
|
||||
"R32 storage binding retained the native depth backing");
|
||||
resources.GetBufferCache().FillBuffer(
|
||||
htile_slice_address, htile_slice_size, 0, false);
|
||||
copy_htile(2);
|
||||
Config::Load({});
|
||||
for (uint32_t pass = 0; pass < 3; ++pass) {
|
||||
if (pass < 2) {
|
||||
Require(name, "HTile clear across copied depth recreation",
|
||||
texture_cache.ClearMeta(high_slice_target.htile_data_base_addr),
|
||||
"copied depth lost HTile registration");
|
||||
}
|
||||
RenderDepthInfo copied_depth{};
|
||||
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
||||
executor, scheduler.Current(), copied_depth);
|
||||
if (pass == 2) {
|
||||
vk::ClearValue prior_depth{};
|
||||
prior_depth.depthStencil.depth = 0.625f;
|
||||
TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(),
|
||||
copied_depth.image_id, {vk::ImageAspectFlagBits::eDepth, 0, 1, 86, 1},
|
||||
prior_depth);
|
||||
}
|
||||
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
||||
executor, scheduler.Current(), &no_color, 0, copied_depth);
|
||||
Require(name, "copied depth consumes only subsequent HTile clears",
|
||||
copied_depth.image_id != high_slice_storage_id &&
|
||||
rendering.depth_stencil_attachment.depth_clear == (pass == 1),
|
||||
"copied depth was cleared again or ignored a subsequent metadata clear");
|
||||
scheduler.BeginRendering(rendering);
|
||||
scheduler.EndRendering();
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
const vk::BufferImageCopy copy{(4 + pass) * sizeof(uint32_t), 0, 0,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 86, 1}, {}, {1, 1, 1}};
|
||||
texture_cache.GetImage(copied_depth.image_id).Download(
|
||||
std::span{©, 1}, high_slice_readback.buffer, 0, high_slice_readback.size);
|
||||
}
|
||||
scheduler.Current().Handle().pipelineBarrier2(bounds_dependency);
|
||||
scheduler.Finish();
|
||||
Require(name, "high HTile slice contents and neighbor",
|
||||
ReadBuffer(name, high_slice_readback, 4) == std::vector<uint32_t>{
|
||||
ReadBuffer(name, high_slice_readback, 7) == std::vector<uint32_t>{
|
||||
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.375f),
|
||||
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.625f)},
|
||||
"layer86 clear missed, repeated, or changed adjacent layer85");
|
||||
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.625f),
|
||||
std::bit_cast<uint32_t>(0.625f), std::bit_cast<uint32_t>(0.375f),
|
||||
std::bit_cast<uint32_t>(0.625f)},
|
||||
"layer86 clear missed, repeated, or discarded copied depth");
|
||||
Require(name, "GPU-visible opt-in ZMask support",
|
||||
ReadBuffer(name, high_slice_metadata, 6) == std::vector<uint32_t>{0, 15, 0, 15, 0, 15},
|
||||
"raw HTile binding ignored the ZMask setting or lost it after depth replacement");
|
||||
DestroyBuffer(&high_slice_readback);
|
||||
DestroyBuffer(&high_slice_metadata);
|
||||
registers.SetDepthClearValue(0.0f);
|
||||
|
||||
// DB override copies run with depth and stencil tests disabled.
|
||||
@@ -16056,9 +16223,9 @@ public:
|
||||
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1},
|
||||
stencil_initial);
|
||||
Require(name, "stencil fill coverage",
|
||||
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
||||
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
|
||||
scheduler.Current(), 0, 1, 1, 0x41u) &&
|
||||
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
||||
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
|
||||
scheduler.Current(), 2, 1, 1, 0x41u),
|
||||
"incomplete or excessive stencil dispatch was consumed");
|
||||
auto &stencil_source = stencil_snapshot.buffers[0];
|
||||
@@ -16066,12 +16233,12 @@ public:
|
||||
stencil_source.dwords[0] = static_cast<uint32_t>(stencil_address);
|
||||
stencil_source.dwords[1] = static_cast<uint32_t>(stencil_address >> 32) | (4u << 16);
|
||||
Require(name, "stencil fill source alias",
|
||||
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
||||
!RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
|
||||
scheduler.Current(), 1, 1, 1, 0x41u),
|
||||
"an aliased scalar load was consumed as a uniform image clear");
|
||||
stencil_source = clean_source;
|
||||
Require(name, "stencil IMAGE_STORE clear",
|
||||
RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
||||
RenderExecutorTestAccess::TryConsumeComputeFill(executor, stencil_compute,
|
||||
scheduler.Current(), 1, 1, 1, 0x41u),
|
||||
"captured clear attempted an unsupported depth/stencil storage binding");
|
||||
auto stencil_readback = CreateHostBuffer(name, 8,
|
||||
@@ -48921,8 +49088,12 @@ int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) {
|
||||
CheckNativeMsaaState();
|
||||
CheckPs5DepthRegisterDecoding();
|
||||
CheckDepthHtileStencilCompatibility();
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckUnifiedTextureCacheFlow();
|
||||
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--dcc-clear-only") == 0) {
|
||||
|
||||
Reference in New Issue
Block a user