diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index d9377298d..f97400965 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -152,6 +152,10 @@ bool ReadbackLinearImagesEnabled() { return g_config->readback_linear_images; } +bool SyncRawImageBuffersEnabled() { + return g_config->sync_raw_image_buffers; +} + bool TessellationEnabled() { return g_config->tessellation_enabled; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 3ec719d76..c2bbddddd 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -73,6 +73,7 @@ struct ConfigOptions { bool gpu_assisted_validation_enabled = false; bool renderdoc_enabled = false; bool readback_linear_images = false; + bool sync_raw_image_buffers = false; bool tessellation_enabled = false; bool trophy_enabled = true; bool playgo_hack_enabled = false; @@ -124,6 +125,7 @@ bool GpuAssistedValidationEnabled(); bool RenderDocEnabled(); bool ReadbackLinearImagesEnabled(); +bool SyncRawImageBuffersEnabled(); bool TessellationEnabled(); bool TrophyEnabled(); bool PlayGoHackEnabled(); diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 4a017ba1d..c65f738f8 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -3,6 +3,7 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/common.h" +#include "common/emulatorConfig.h" #include "common/file.h" #include "common/logging/log.h" #include "common/profiler.h" @@ -127,7 +128,8 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource EXIT("storage buffer range is unsupported\n"); } auto [buffer, offset] = context.GetBufferCache().ObtainBuffer( - address, size, resource.written, resource.read || resource.formatted, id); + address, size, resource.written, + resource.formatted || (resource.read && Config::SyncRawImageBuffersEnabled()), id); 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 f7ecff72c..fd7bc896d 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -185,6 +185,12 @@ Skip boot logos + + + Can fix missing textures and incorrect transparency in some games. May reduce performance. + Accurate texture synchronization + + diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 7a57013be..047c043f2 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -119,6 +119,7 @@ public: bool fullscreen_enabled = false; bool hide_cursor_enabled = false; bool readback_linear_images = false; + bool sync_raw_image_buffers = false; bool tessellation_enabled = false; bool trophy_enabled = true; bool skip_notice_screen = false; @@ -153,6 +154,7 @@ public: fullscreen_enabled = other.fullscreen_enabled; hide_cursor_enabled = other.hide_cursor_enabled; readback_linear_images = other.readback_linear_images; + sync_raw_image_buffers = other.sync_raw_image_buffers; tessellation_enabled = other.tessellation_enabled; trophy_enabled = other.trophy_enabled; skip_notice_screen = other.skip_notice_screen; diff --git a/src/launcher/src/configuration.cpp b/src/launcher/src/configuration.cpp index 3429252c8..01f248dae 100644 --- a/src/launcher/src/configuration.cpp +++ b/src/launcher/src/configuration.cpp @@ -12,6 +12,7 @@ QVariantMap Configuration::GameSettings() const { KYTY_CFG_SET(fullscreen_enabled); KYTY_CFG_SET(hide_cursor_enabled); KYTY_CFG_SET(readback_linear_images); + KYTY_CFG_SET(sync_raw_image_buffers); KYTY_CFG_SET(tessellation_enabled); KYTY_CFG_SET(trophy_enabled); KYTY_CFG_SET(skip_notice_screen); @@ -56,6 +57,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) { KYTY_CFG_GET(fullscreen_enabled); KYTY_CFG_GET(hide_cursor_enabled); KYTY_CFG_GET(readback_linear_images); + KYTY_CFG_GET(sync_raw_image_buffers); KYTY_CFG_GET(tessellation_enabled); trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool(); KYTY_CFG_GET(skip_notice_screen); diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index 471f059d9..76e5ff13c 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -275,6 +275,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) { m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled); 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_tessellation->setChecked(info.tessellation_enabled); m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen); m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled); @@ -435,6 +436,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked(); 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.tessellation_enabled = ui.checkBox_tessellation->isChecked(); info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked(); info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked(); diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index f2c7c3894..200b7c869 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -245,6 +245,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) { args << "--hide-cursor"; } args << "--readback-linear-images" << BoolArg(info.readback_linear_images); + args << "--sync-raw-image-buffers" << BoolArg(info.sync_raw_image_buffers); args << "--trophy-notifications" << BoolArg(info.trophy_enabled); args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen); if (info.tessellation_enabled) { diff --git a/src/main.cpp b/src/main.cpp index 1aceaf108..045055fe6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,6 +84,8 @@ static void PrintUsage() { ::printf(" --spirv-debug-printf Enable SPIR-V debug printf.\n"); ::printf( " --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( " --trophy-notifications Show trophy unlock toasts and play their sound.\n"); ::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n"); @@ -426,6 +428,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 == "--sync-raw-image-buffers") { + if (!ParseBool(value, options.config.sync_raw_image_buffers)) { + ::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 df0c2befd..d2a32ff7b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -14022,8 +14022,14 @@ public: "backing"); RenderExecutorTestAccess::ResetBindings(executor); - for (const bool formatted : {false, true}) { - const uint32_t before = formatted ? 0x13579bdfu : 0x89abcdefu; + Require(name, "raw image synchronization default", + !Config::SyncRawImageBuffersEnabled(), + "raw image synchronization should be disabled by default"); + for (const auto [sync_raw, formatted] : + {std::pair{false, false}, {true, false}, {false, true}, {true, true}}) { + Config::Load({.sync_raw_image_buffers = sync_raw}); + const bool sync_image = sync_raw || formatted; + const uint32_t before = 0x13579bd0u | (uint32_t(sync_raw) << 1u) | uint32_t(formatted); constexpr uint32_t guard = 0x2468ace0u; constexpr uint32_t after = 0xa1b2c3d4u; vk::ClearValue clear{}; @@ -14065,8 +14071,9 @@ public: buffer.range == buffer_size && buffer_bindings.images.empty() && texture_cache.GetImage(target.image_id).backing.state.layout == - vk::ImageLayout::eTransferSrcOptimal, - "target discovery did not precede the image-to-buffer copy"); + (sync_image ? vk::ImageLayout::eTransferSrcOptimal + : vk::ImageLayout::eTransferDstOptimal), + "image-to-buffer synchronization ignored its setting or formatted reads"); auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {0, 0}); const std::array copies{ @@ -14104,8 +14111,9 @@ public: scheduler.EndRendering(); scheduler.Finish(); Require(name, "buffer alias observes prior GPU contents", - ReadBuffer(name, readback, 2) == std::vector{before, guard}, - "buffer acquisition copied stale guest bytes instead of the image"); + ReadBuffer(name, readback, 2) == + (sync_image ? std::vector{before, guard} : std::vector{0, 0}), + "buffer contents did not match the selected image synchronization mode"); DestroyBuffer(&readback); Require(name, "rendering after buffer acquisition", ReadCachedTexel(name, context, target.image_id) == std::vector{after} && @@ -14114,6 +14122,7 @@ public: "attachment rendering lost its new color or changed the untouched array layer"); RenderExecutorTestAccess::ResetBindings(executor); } + Config::Load({}); auto colliding_msaa_texture = array_texture; colliding_msaa_texture.fields[3] =