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) {