diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp
index 7705f5c61..c230cf249 100644
--- a/src/common/emulatorConfig.cpp
+++ b/src/common/emulatorConfig.cpp
@@ -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;
}
diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h
index 4387dafc9..6bde334af 100644
--- a/src/common/emulatorConfig.h
+++ b/src/common/emulatorConfig.h
@@ -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();
diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp
index 17ba542ea..edea24b48 100644
--- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp
+++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp
@@ -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;
diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui
index 74fc5b277..d00aa1ea9 100644
--- a/src/launcher/forms/configuration_edit_dialog.ui
+++ b/src/launcher/forms/configuration_edit_dialog.ui
@@ -174,12 +174,6 @@
-
-
- Can fix missing textures and incorrect transparency in some games. May reduce performance.
- Accurate texture synchronization
-
-
- -
Enable HDR10 output for supported games. Requires HDR enabled on the host display.
Enable HDR display support
@@ -483,6 +477,25 @@
+ -
+
+ Rendering compatibility
+
+
-
+
+ Can fix missing textures and incorrect transparency (Resident Evil 2).
+ Accurate texture synchronization
+
+
+ -
+
+ Can restore missing geometry (High on Life 2).
+ Support ZMask
+
+
+
+
+
-
Qt::Vertical
diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h
index 8a3a820d1..f45b7b936 100644
--- a/src/launcher/include/configuration.h
+++ b/src/launcher/include/configuration.h
@@ -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;
diff --git a/src/launcher/src/configuration.cpp b/src/launcher/src/configuration.cpp
index a6e63dfba..5e53416c4 100644
--- a/src/launcher/src/configuration.cpp
+++ b/src/launcher/src/configuration.cpp
@@ -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);
diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp
index dcfcdfeb5..525f101ec 100644
--- a/src/launcher/src/configurationEditDialog.cpp
+++ b/src/launcher/src/configurationEditDialog.cpp
@@ -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();
diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp
index e33373166..d3ca29313 100644
--- a/src/launcher/src/mainDialog.cpp
+++ b/src/launcher/src/mainDialog.cpp
@@ -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);
diff --git a/src/main.cpp b/src/main.cpp
index fec8b2cfb..1c3bdbb91 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -91,6 +91,8 @@ static void PrintUsage() {
" --readback-linear-images Read back writable linear images on submit.\n");
::printf(
" --sync-raw-image-buffers Synchronize raw reads of GPU images. Default: false.\n");
+ ::printf(
+ " --support-zmask Fill HTILE buffers with uncompressed ZMask. Default: false.\n");
::printf(
" --trophy-notifications 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());
diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp
index 4aa574598..ee565bcb7 100644
--- a/tests/ShaderRecompilerComputeTests.cpp
+++ b/tests/ShaderRecompilerComputeTests.cpp
@@ -14943,12 +14943,48 @@ public:
"single high slice was rejected or changed the backing layer layout");
auto high_slice_readback = CreateHostBuffer(
name, 4 * 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 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);
@@ -14968,7 +15004,22 @@ 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;
+ Require(name, "HTile depth replacement before raw buffer read",
+ texture_cache.FindImage(high_slice_storage) != 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({});
scheduler.Current().Handle().pipelineBarrier2(bounds_dependency);
scheduler.Finish();
Require(name, "high HTile slice contents and neighbor",
@@ -14976,7 +15027,11 @@ public:
std::bit_cast(0.625f), std::bit_cast(0.375f),
std::bit_cast(0.625f), std::bit_cast(0.625f)},
"layer86 clear missed, repeated, or changed adjacent layer85");
+ Require(name, "GPU-visible opt-in ZMask support",
+ ReadBuffer(name, high_slice_metadata, 6) == std::vector{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.
@@ -48945,6 +49000,7 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedTextureCacheFlow();
+ vulkan.CheckRenderExecutorStencilBindingDiscovery();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--dcc-clear-only") == 0) {