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] =