graphics: make raw image synchronization an opt-in setting

This commit is contained in:
nmzik
2026-10-07 20:39:11 +04:00
parent dd754065d8
commit 470669c677
10 changed files with 44 additions and 7 deletions
+4
View File
@@ -152,6 +152,10 @@ bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images; return g_config->readback_linear_images;
} }
bool SyncRawImageBuffersEnabled() {
return g_config->sync_raw_image_buffers;
}
bool TessellationEnabled() { bool TessellationEnabled() {
return g_config->tessellation_enabled; return g_config->tessellation_enabled;
} }
+2
View File
@@ -73,6 +73,7 @@ struct ConfigOptions {
bool gpu_assisted_validation_enabled = false; bool gpu_assisted_validation_enabled = false;
bool renderdoc_enabled = false; bool renderdoc_enabled = false;
bool readback_linear_images = false; bool readback_linear_images = false;
bool sync_raw_image_buffers = false;
bool tessellation_enabled = false; bool tessellation_enabled = false;
bool trophy_enabled = true; bool trophy_enabled = true;
bool playgo_hack_enabled = false; bool playgo_hack_enabled = false;
@@ -124,6 +125,7 @@ bool GpuAssistedValidationEnabled();
bool RenderDocEnabled(); bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled(); bool ReadbackLinearImagesEnabled();
bool SyncRawImageBuffersEnabled();
bool TessellationEnabled(); bool TessellationEnabled();
bool TrophyEnabled(); bool TrophyEnabled();
bool PlayGoHackEnabled(); bool PlayGoHackEnabled();
@@ -3,6 +3,7 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common.h" #include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h" #include "common/file.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/profiler.h" #include "common/profiler.h"
@@ -127,7 +128,8 @@ NativeStorageBuffer(RenderContext& context, const PreparedBindings::BufferSource
EXIT("storage buffer range is unsupported\n"); EXIT("storage buffer range is unsupported\n");
} }
auto [buffer, offset] = context.GetBufferCache().ObtainBuffer( 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 aligned_offset = Common::AlignDown(offset, alignment);
const auto adjustment = offset - aligned_offset; const auto adjustment = offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange; const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
@@ -185,6 +185,12 @@
<property name="text"><string>Skip boot logos</string></property> <property name="text"><string>Skip boot logos</string></property>
</widget> </widget>
</item> </item>
<item row="10" 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>
</layout> </layout>
</widget> </widget>
</item> </item>
+2
View File
@@ -119,6 +119,7 @@ public:
bool fullscreen_enabled = false; bool fullscreen_enabled = false;
bool hide_cursor_enabled = false; bool hide_cursor_enabled = false;
bool readback_linear_images = false; bool readback_linear_images = false;
bool sync_raw_image_buffers = false;
bool tessellation_enabled = false; bool tessellation_enabled = false;
bool trophy_enabled = true; bool trophy_enabled = true;
bool skip_notice_screen = false; bool skip_notice_screen = false;
@@ -153,6 +154,7 @@ public:
fullscreen_enabled = other.fullscreen_enabled; fullscreen_enabled = other.fullscreen_enabled;
hide_cursor_enabled = other.hide_cursor_enabled; hide_cursor_enabled = other.hide_cursor_enabled;
readback_linear_images = other.readback_linear_images; readback_linear_images = other.readback_linear_images;
sync_raw_image_buffers = other.sync_raw_image_buffers;
tessellation_enabled = other.tessellation_enabled; tessellation_enabled = other.tessellation_enabled;
trophy_enabled = other.trophy_enabled; trophy_enabled = other.trophy_enabled;
skip_notice_screen = other.skip_notice_screen; skip_notice_screen = other.skip_notice_screen;
+2
View File
@@ -12,6 +12,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(fullscreen_enabled); KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(hide_cursor_enabled); KYTY_CFG_SET(hide_cursor_enabled);
KYTY_CFG_SET(readback_linear_images); KYTY_CFG_SET(readback_linear_images);
KYTY_CFG_SET(sync_raw_image_buffers);
KYTY_CFG_SET(tessellation_enabled); KYTY_CFG_SET(tessellation_enabled);
KYTY_CFG_SET(trophy_enabled); KYTY_CFG_SET(trophy_enabled);
KYTY_CFG_SET(skip_notice_screen); KYTY_CFG_SET(skip_notice_screen);
@@ -56,6 +57,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(fullscreen_enabled); KYTY_CFG_GET(fullscreen_enabled);
KYTY_CFG_GET(hide_cursor_enabled); KYTY_CFG_GET(hide_cursor_enabled);
KYTY_CFG_GET(readback_linear_images); KYTY_CFG_GET(readback_linear_images);
KYTY_CFG_GET(sync_raw_image_buffers);
KYTY_CFG_GET(tessellation_enabled); KYTY_CFG_GET(tessellation_enabled);
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool(); trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
KYTY_CFG_GET(skip_notice_screen); KYTY_CFG_GET(skip_notice_screen);
@@ -275,6 +275,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled); m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled); m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
m_ui->checkBox_readback->setChecked(info.readback_linear_images); 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_tessellation->setChecked(info.tessellation_enabled);
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen); m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled); 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.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked(); info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
info.readback_linear_images = ui.checkBox_readback->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.tessellation_enabled = ui.checkBox_tessellation->isChecked();
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked(); info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked(); info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
+1
View File
@@ -245,6 +245,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--hide-cursor"; args << "--hide-cursor";
} }
args << "--readback-linear-images" << BoolArg(info.readback_linear_images); 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 << "--trophy-notifications" << BoolArg(info.trophy_enabled);
args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen); args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen);
if (info.tessellation_enabled) { if (info.tessellation_enabled) {
+7
View File
@@ -84,6 +84,8 @@ static void PrintUsage() {
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n"); ::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
::printf( ::printf(
" --readback-linear-images <true|false> Read back writable linear images on submit.\n"); " --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( ::printf(
" --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n"); " --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n");
::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\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()); ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
return false; 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") { } else if (arg == "--trophy-notifications") {
if (!ParseBool(value, options.config.trophy_enabled)) { if (!ParseBool(value, options.config.trophy_enabled)) {
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
+15 -6
View File
@@ -14022,8 +14022,14 @@ public:
"backing"); "backing");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
for (const bool formatted : {false, true}) { Require(name, "raw image synchronization default",
const uint32_t before = formatted ? 0x13579bdfu : 0x89abcdefu; !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 guard = 0x2468ace0u;
constexpr uint32_t after = 0xa1b2c3d4u; constexpr uint32_t after = 0xa1b2c3d4u;
vk::ClearValue clear{}; vk::ClearValue clear{};
@@ -14065,8 +14071,9 @@ public:
buffer.range == buffer_size && buffer.range == buffer_size &&
buffer_bindings.images.empty() && buffer_bindings.images.empty() &&
texture_cache.GetImage(target.image_id).backing.state.layout == texture_cache.GetImage(target.image_id).backing.state.layout ==
vk::ImageLayout::eTransferSrcOptimal, (sync_image ? vk::ImageLayout::eTransferSrcOptimal
"target discovery did not precede the image-to-buffer copy"); : vk::ImageLayout::eTransferDstOptimal),
"image-to-buffer synchronization ignored its setting or formatted reads");
auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t), auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t),
vk::BufferUsageFlagBits::eTransferDst, {0, 0}); vk::BufferUsageFlagBits::eTransferDst, {0, 0});
const std::array copies{ const std::array copies{
@@ -14104,8 +14111,9 @@ public:
scheduler.EndRendering(); scheduler.EndRendering();
scheduler.Finish(); scheduler.Finish();
Require(name, "buffer alias observes prior GPU contents", Require(name, "buffer alias observes prior GPU contents",
ReadBuffer(name, readback, 2) == std::vector<u32>{before, guard}, ReadBuffer(name, readback, 2) ==
"buffer acquisition copied stale guest bytes instead of the image"); (sync_image ? std::vector<u32>{before, guard} : std::vector<u32>{0, 0}),
"buffer contents did not match the selected image synchronization mode");
DestroyBuffer(&readback); DestroyBuffer(&readback);
Require(name, "rendering after buffer acquisition", Require(name, "rendering after buffer acquisition",
ReadCachedTexel(name, context, target.image_id) == std::vector<u32>{after} && ReadCachedTexel(name, context, target.image_id) == std::vector<u32>{after} &&
@@ -14114,6 +14122,7 @@ public:
"attachment rendering lost its new color or changed the untouched array layer"); "attachment rendering lost its new color or changed the untouched array layer");
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
} }
Config::Load({});
auto colliding_msaa_texture = array_texture; auto colliding_msaa_texture = array_texture;
colliding_msaa_texture.fields[3] = colliding_msaa_texture.fields[3] =